dsh-chat-background 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/LICENSE +21 -0
- package/README.en.md +130 -0
- package/README.md +125 -2
- package/assets/screenshots/main-ui.png +0 -0
- package/assets/screenshots/settings-page.png +0 -0
- package/client.js +3582 -0
- package/cordis.patch.yml +4 -0
- package/docs/ARCHITECTURE.md +233 -0
- package/docs/CONVENTIONS.md +183 -0
- package/docs/DEVELOPMENT.md +127 -0
- package/docs/PUBLISHING.md +109 -0
- package/docs/TESTING.md +129 -0
- package/docs/TROUBLESHOOTING.md +121 -0
- package/host/constants.js +87 -0
- package/host/media.js +153 -0
- package/host/paths.js +43 -0
- package/host/routes.js +504 -0
- package/host/token-usage.js +171 -0
- package/icon.svg +27 -0
- package/index.js +132 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +88 -4
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# 架构说明
|
|
2
|
+
|
|
3
|
+
> 本文说明 `dsh-chat-background` 的两半结构、数据流、关键不变量与扩展方式;修改代码前请先读本文。
|
|
4
|
+
> 相关:[CONVENTIONS.md](CONVENTIONS.md) · [DEVELOPMENT.md](DEVELOPMENT.md) · [TESTING.md](TESTING.md)
|
|
5
|
+
|
|
6
|
+
## 1. 插件是什么
|
|
7
|
+
|
|
8
|
+
`dsh-chat-background` 是一个 **DSH bundle 插件**(Cordis loader 的 patch 条目 + 宿主半 + 浏览器半),
|
|
9
|
+
给 DeepSeek Harness 桌面端的 Web 界面加一层可配置的视觉背景:
|
|
10
|
+
|
|
11
|
+
- 背景图片(来自 `~/.dsh/backgrounds/`)铺满界面;左侧栏、中间主窗口、右侧边栏各有独立的 0–100 遮罩层;
|
|
12
|
+
- 输入栏下方一条与输入卡片等宽、包住用量数据的背景条;
|
|
13
|
+
- 输入栏上方的任务进度条;
|
|
14
|
+
- 侧栏账号行上方的全会话 Token 总用量。
|
|
15
|
+
|
|
16
|
+
设置入口是 **设置 → 页面设置**(本插件独占的分区):**背景**标签 6 行 + **UI**标签 3 组卡片。
|
|
17
|
+
|
|
18
|
+
## 2. 两半结构
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
┌──────────────────────────── DSH Host (Node / Electron main) ────────────────────────────┐
|
|
22
|
+
│ index.js apply(ctx) → 注册 5 条 Web 路由(ctx.webServer.register) │
|
|
23
|
+
│ host/constants.js 路由路径、图片缓存策略、token 缓存窗口 │
|
|
24
|
+
│ host/paths.js DSH_HOME / backgrounds 目录解析(支持环境变量覆盖) │
|
|
25
|
+
│ host/media.js 可接受的图片类型、文件名校验、目录清单(name/bytes/mtime/rev) │
|
|
26
|
+
│ host/token-usage.js 扫描 session_projcache/*.json,汇总四个 token 桶 │
|
|
27
|
+
│ host/routes.js HTTP 处理器:list / files / folder / token-total │
|
|
28
|
+
└──────────────────────────────────────────┬─────────────────────────────────────────────┘
|
|
29
|
+
│ 本机回环 HTTP(/chat-backgrounds/*)
|
|
30
|
+
┌──────────────────────────────────────────┴─────────────────────────────────────────────┐
|
|
31
|
+
│ DSH Web 客户端(Electron renderer) │
|
|
32
|
+
│ client.js(由 src/ 构建) window.__ModuleLoader__.load({ id, factory }) │
|
|
33
|
+
│ src/index.js 入口:绑定 React → createPlugin → apply │
|
|
34
|
+
│ src/plugin.js 创建设置实例、应用持久状态、注册槽位/观察者/轮询 │
|
|
35
|
+
│ src/background.js 应用图片/侧栏/滑杆,测量不透明容器,rAF 合并 flush │
|
|
36
|
+
│ src/strip.js 输入栏下方背景条(两锚点测量 + ResizeObserver) │
|
|
37
|
+
│ src/progress.js 任务进度条(todos 投影 / 本轮步数估算) │
|
|
38
|
+
│ src/token-total.js 侧栏 Token 行注入与轮询 │
|
|
39
|
+
│ src/rows.js 背景 6 行 + UI 3 组卡片(Row / SwitchRow / ColorRow / Slider) │
|
|
40
|
+
│ src/storage.js localStorage 读写 + createSwitch / createColorSetting │
|
|
41
|
+
│ src/styles.js 本插件全部 CSS 规则 │
|
|
42
|
+
│ src/i18n.js / react.js 中英文案与共享语言观察者 / React 绑定 │
|
|
43
|
+
│ src/constants.js 路由、存储键、属性名、滑杆范围 │
|
|
44
|
+
└─────────────────────────────────────────────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 3. 数据流
|
|
48
|
+
|
|
49
|
+
**持久状态**:全部存在浏览器 `localStorage`(键前缀 `dsh-chat-background:`),宿主半不保存用户设置。
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
设置行交互 ──写──▶ localStorage ──读──▶ applyBackground / renderComposerStrip / renderTokenTotal
|
|
53
|
+
│ │
|
|
54
|
+
└────────── 同步调用 ────────────────────┘ (写入后立即重绘,不等待 DOM 观察)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**DOM 发布**:设置值最终落到三类位置:
|
|
58
|
+
|
|
59
|
+
1. `<html>` 上的属性与内联 CSS 变量(`--dsh-chat-background-image`、三个 `--dsh-chat-background-scrim-*`、
|
|
60
|
+
`--dsh-chat-strip-bg`、`--dsh-progress-color`、`--dsh-token-size`);
|
|
61
|
+
2. 注入节点:`[data-dsh-chat-background-layer]`(背景图 `<img>`)、`[data-dsh-chat-strip]`(composer 包装层内)、
|
|
62
|
+
`[data-dsh-token-total]`(侧栏账号行前);
|
|
63
|
+
3. 静态样式表:`style[data-plugin-css="dsh-chat-background/background.css"]` 与测量生成的
|
|
64
|
+
`dsh-chat-background/measured.css`。
|
|
65
|
+
|
|
66
|
+
**重绘触发**:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
MutationObserver(body 子树 / dark-theme 属性) ─┐
|
|
70
|
+
window resize ────────────────────────────────┼─▶ requestAnimationFrame 合并 ─▶ flushBackground()
|
|
71
|
+
composer 包装层 ResizeObserver(纯样式尺寸) ──┘ ├─ applyBackground(readSelection())
|
|
72
|
+
├─ renderComposerStrip()
|
|
73
|
+
└─ renderTokenTotal()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`applyBackground` 在稳态下很便宜:只有「`[data-slot]` 数量」或「会话容器身份」变化时才重新测量不透明容器;
|
|
77
|
+
稳态下不写任何属性(状态快照一致时直接返回)。
|
|
78
|
+
|
|
79
|
+
## 4. 宿主半契约
|
|
80
|
+
|
|
81
|
+
五条路由(路径常量集中在 `host/constants.js`,浏览器半在 `src/constants.js` 镜像):
|
|
82
|
+
|
|
83
|
+
| 路由 | 方法 | 返回 |
|
|
84
|
+
| ------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `GET /chat-backgrounds/list` | GET / HEAD | `{ folder, accepted, images: [{ name, bytes, mtimeMs, rev }] }` |
|
|
86
|
+
| `GET /chat-backgrounds/files/<name>` | GET / HEAD | 图片字节;`ETag`、`cache-control: private, max-age=0, must-revalidate`(SEC-06) |
|
|
87
|
+
| `POST /chat-backgrounds/folder` | POST | `{ folder, opened: true }`;在资源管理器中打开目录 |
|
|
88
|
+
| `GET /chat-backgrounds/token-total` | GET / HEAD | `{ total, uncachedInputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, sessions, countedSessions, scannedAt, source, missing? }` |
|
|
89
|
+
| `GET /chat-backgrounds/health` | GET / HEAD | `{ ok, version, folder, accepted, limits, fence }` |
|
|
90
|
+
|
|
91
|
+
失败语义:
|
|
92
|
+
|
|
93
|
+
- 未知图片 / 畸形百分号编码 / 路径穿越 → `404`;
|
|
94
|
+
- 方法不支持 → `405` + `Allow`;
|
|
95
|
+
- 目录或投影缓存读失败 → `500`,日志走 `ctx.logger.warn`;
|
|
96
|
+
- 每条路由都经过请求栅栏(Host/Origin/Sec-Fetch,见 §8);错误响应同样带安全头;
|
|
97
|
+
- 图片超过 64 MB 返回 `413`;/files 只服务当前目录清单中的真实文件名;
|
|
98
|
+
- token 扫描优先走宿主服务,回退到磁盘时缓存 10 秒;文件夹在首次请求时按需创建;
|
|
99
|
+
- token 扫描对单个投影文件有 **8 MB 上限**(`MAX_PROJECTION_BYTES`),超大的异常文件按不参与统计处理。
|
|
100
|
+
|
|
101
|
+
设计约束:宿主半**只 import `node:` 内置模块**。profile 安装的包解析不到 DSH 安装目录里的
|
|
102
|
+
`@deepseek-ai/*` 包,任何这类 import 都会在加载期失败。
|
|
103
|
+
|
|
104
|
+
## 5. 浏览器半契约
|
|
105
|
+
|
|
106
|
+
- 入口:`window.__ModuleLoader__.load({ id: <package name>, factory(require) { ... } })`;
|
|
107
|
+
`id` 由构建脚本从 `package.json` 注入,必须与包名一致。
|
|
108
|
+
- 注入服务:`inject: ['slots', 'locale']`;组件通过 `ctx.slots.inject` / `ctx.slots.register` 挂载。
|
|
109
|
+
- 设置分区:`settings.section` 槽,`id: chat-background`、`order: 5`(通用 0 / 模型 10 之间),
|
|
110
|
+
只使用文档化的 `id/order/label/locale` 注册选项;背景标签 6 行 + UI 标签 3 组卡片由 `PageSection` 直接组合渲染,不再声明子槽。
|
|
111
|
+
- 进度条:注册进 `conversation.input.dock`(composer 卡片上方的整行条目),`id: chat-background-progress`、
|
|
112
|
+
`order: 12`(避开宿主 GoalDock 与队列条占位);空闲(无清单、未运行)时不渲染。
|
|
113
|
+
- Token 行:不是槽位,而是运行时注入 `<div data-dsh-token-total>` 到账号行前;锚点按
|
|
114
|
+
`[class*="settingsArea"]` → `[data-slot="sidebar.settings"]` 父行 → footer 容器 依次尝试并校验位置。
|
|
115
|
+
- 清理:所有副作用都通过 `ctx.effect(...)` 注册清理函数(样式表、观察者、interval、ResizeObserver)。
|
|
116
|
+
|
|
117
|
+
### 5.0 背景区块、三区遮罩与原生标题栏
|
|
118
|
+
|
|
119
|
+
- 设置页内部有「背景 / UI」两个 pill 标签页;切换是组件内 `useState`,不产生额外路由。
|
|
120
|
+
- 所有设置行都经 `Row` 组件渲染:两列 grid(说明 / 控件模块)。控件组保持自身宽度并右对齐,所以语言切换、字体缩放、不同 DPI 或窗口变窄时只有位置变化,不会互相挤压或错位;滑块尺寸由变量推导,不写死像素偏移。
|
|
121
|
+
- **背景**标签:大图预览、当前文件名下拉框 + 「打开文件夹」「重新扫描」按钮,下方三个遮罩层滑杆;区块与行之间用默认分隔线。
|
|
122
|
+
- **UI**标签:背景条、进度条、Token 三组设置各用一张 `1px` 细边框 + 大圆角卡片包住,组内两行之间用默认分隔线。
|
|
123
|
+
- 右栏 panel 通过 `markRightPanel()` 打标:普通状态由右栏列的 `::after` 画遮罩(随列宽收缩);全屏时**左侧栏保持可见**,只隐藏主窗口列,panel 用 offset/width 变量从左侧栏右缘开始覆盖「主窗口 + 原右栏」并绘制统一遮罩;布局 frame 通过 `markLayoutFrame()` 打标,仅在展开/收起点击的 340ms 窗口内启用 300ms `grid-template-columns` 过渡(与 app 内容动画同步),拖动宽度时不启用过渡。
|
|
124
|
+
- 三个遮罩层没有开关,只有 0–100 的不透明度:0 全透明、100 完全不透明;侧栏/右栏的表面始终让位于图片,由遮罩决定遮挡程度。
|
|
125
|
+
- **主窗口遮罩按对话轮次绘制**:不再给整个主窗口列铺一层,而是由 `src/turn-masks.js` 读取 `[data-chat-turn]`,把同一轮的用户消息与回复合成一张绝对定位的卡片,
|
|
126
|
+
宽度 = 阅读列宽 + 两侧各 24px 留白(正好到阅读列宽度手柄的净空,跟随手柄自动变化),轮次之间保留约 20px 壁纸缝;同时把每轮第一个条目的宿主变量 `--dsh-chat-flow-gap` 写成 28px(原生 12px),把轮次之间的**真实布局间距**调成舒适档,关闭背景图或卸载时还原。卡片本身只写 `top/height`,不影响会话布局;卡片层会把宿主的 flow-gap margin 清零,否则整个层会随 flow 间距下移、用户气泡顶边露在遮罩外。几何计算在
|
|
127
|
+
`src/lib/turn-masks.js`,有单元测试覆盖;DOM 线程只在布局变化时重算。
|
|
128
|
+
|
|
129
|
+
- 左侧栏/主窗口/右侧栏由稳定槽位(`[data-slot="sidebar"]`、`[data-slot="main"]`、`[data-slot="rightbar"]`)的父节点打标,
|
|
130
|
+
各自建立 `position: relative` + `isolation: isolate` 的层叠上下文,再用 `::before`/`::after` 在 `z-index: -1` 画独立遮罩;
|
|
131
|
+
因此调暗侧栏不会影响正文,反之亦然。
|
|
132
|
+
- app 会把 base surface 画在布局 frame、主窗口列和右侧栏上:Windows 标题栏模式还会给主窗口列铺不透明底色和 16px 圆角。
|
|
133
|
+
这些表面通过**槽位标记**(`data-dsh-chat-background-main/right/frame`)清成透明,而不是只依赖一次性测量出的 `lift` 标记,
|
|
134
|
+
所以列被重建、标题栏重新主题化或窗口从遮挡恢复后都不会重新变白。测量标记丢失、`data-windows-titlebar`/`data-platform`
|
|
135
|
+
变化、窗口 `focus` 或 `visibilitychange` 变可见时会主动重新测量,见 `src/background.js` 的 `invalidatePainters()`。
|
|
136
|
+
- 窗口控制条:桌面预加载用隐藏 probe 的 `background-color: var(--dsw-specific-sidebar-fill)` 采样原生 titleBarOverlay 颜色;
|
|
137
|
+
插件只在该 probe 上覆盖为透明(不改 token),并通过改写 body 自定义属性触发预加载的 observer 重新发送,见 `src/background.js`。
|
|
138
|
+
|
|
139
|
+
### 5.0.1 背景轮换
|
|
140
|
+
|
|
141
|
+
- **背景图由固定 `<img data-dsh-chat-background-layer>` 绘制**(`object-fit: cover`,`z-index: -1`,位于列与遮罩之下);不使用 `<html>` 背景,因为 Chromium 会把根元素上的 GIF/WebP/APNG 冻结在首帧;
|
|
142
|
+
- `src/rotation.js` 管理轮换定时器、图片列表拉取、分组过滤和过渡层;`plugin.js` 挂载时安装,卸载时清定时器与过渡层;
|
|
143
|
+
- 普通/全屏右栏都由 `--dsh-chat-background-image` 对应的 `<img>` 背景层显示当前图;切换时把旧图放到 `body` 下固定、`z-index: -1` 的 `.dshcb-rotate-layer` 上,旧图淡出/主题底色层完成渐变,层位于 app 列与列遮罩之下;
|
|
144
|
+
- 设置行在图片选择下方依次为:背景显示方式、轮换设置(特效/时长/间隔)、轮换分组;分组为空即轮换全部。
|
|
145
|
+
|
|
146
|
+
### 5.1 设置开关与工厂
|
|
147
|
+
|
|
148
|
+
| 工厂 | 产出 | 复用者 |
|
|
149
|
+
| ---------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------- |
|
|
150
|
+
| `createSwitch({ key, onChange })` | `{ read, write, useEnabled }`;`'off'` 为关,缺省为开 | 背景条 / 进度条 / Token 三个开关 |
|
|
151
|
+
| `createColorSetting({ key, property, fallback, translucent, defaultToken })` | `{ read, apply, useColor }`;写 `<html>` 内联变量 | 背景条颜色 / 进度条颜色 |
|
|
152
|
+
| `ActiveSliderRow` | 滑杆 + 数值 + 副作用 | 三区遮罩 / Token 字号 |
|
|
153
|
+
|
|
154
|
+
设置实例在 `plugin.js` 的 `apply()` 中创建,经 `src/settings.js` 晚绑定给各功能模块;
|
|
155
|
+
这样模块导入期没有副作用,单元测试可以直接 import 纯函数模块。
|
|
156
|
+
|
|
157
|
+
## 6. 关键不变量(改代码前必读)
|
|
158
|
+
|
|
159
|
+
1. **零布局影响**:背景条、进度条、Token 行都不改 app 已算好的布局盒(绝对定位 / 独立行 / 前插节点)。
|
|
160
|
+
给 composer 内的行加 `padding` / `margin` 会被座位裁剪吃掉内容。
|
|
161
|
+
2. **层叠顺序**:「背景图之上、所有 UI 之下」= 容器 `isolation: isolate` + 伪元素 `z-index: -1`。
|
|
162
|
+
`isolation` 只建立层叠上下文,**不会**成为 `position: fixed` 后代的包含块。
|
|
163
|
+
3. **禁止 transform/filter/backdrop-filter**:它们会让元素成为 fixed 后代的包含块,破坏 app 的悬浮气泡定位。
|
|
164
|
+
4. **全量扫描要缓存**:`MutationObserver` 在流式输出时每帧触发,锚点查找必须缓存(节点离开文档才失效)。
|
|
165
|
+
5. **纯样式尺寸变化**:`MutationObserver` 看不到,composer 包装层另挂 `ResizeObserver`。
|
|
166
|
+
6. **主题 token 在 `body` 上**:`--dsw-alias-*` 由主题服务发布在 `body`,读默认色时要读 `body`。
|
|
167
|
+
7. **不要写死端口 / token**:验证时从 CLI 输出取 URL,不要假设桌面端端口。
|
|
168
|
+
|
|
169
|
+
## 7. 构建与产物
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
src/*.js + src/lib/*.js ──esbuild(bundle, iife, platform=browser)──▶ client.js
|
|
173
|
+
host/*.js + index.js ──── 直接作为 ESM 发布(Node 原生支持多文件)──▶ npm 包
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **为什么浏览器半必须单文件**:DSH 客户端模块加载器按包加载一个 `client.js` 入口;
|
|
177
|
+
相对多文件 `import` 不会被解析(`require.async` chunk 是构建器产出的特例)。
|
|
178
|
+
- **为什么 React 不打包**:React 是宿主平台共享模块,由 factory 的 `require` 提供;
|
|
179
|
+
`src/react.js` 在 factory 执行时绑定,组件的 hook 全部经该绑定调用。
|
|
180
|
+
- **为什么提交构建产物**:用户可能用 `dsh plugin add github:...` 直接安装 Git 仓库;
|
|
181
|
+
提交 `client.js` 后无需在其机器上执行构建。CI 会校验产物与 `src/` 一致(构建确定性输出)。
|
|
182
|
+
- **为什么用 esbuild**:单一 devDependency、无运行时依赖、可确定性输出;`npm run build` 即可复现。
|
|
183
|
+
|
|
184
|
+
## 8. 兼容与校准点
|
|
185
|
+
|
|
186
|
+
DSH 升级可能改变 DOM,插件的脆弱点与处理方式如下:
|
|
187
|
+
|
|
188
|
+
| 校准点 | 位置 | 失效表现 | 处理 |
|
|
189
|
+
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ---------------------------- | ----------------------------- |
|
|
190
|
+
| 用量数据行(稳定钩子 `[data-composer-dock]` + `[data-composer-stat]`;`display: contents` 包装层用子元素矩形并集度量) | `src/strip.js` 的 `findUsageRow` / `findStableUsageRow` | 背景条不出现或位置不对 | 按真实 DOM 调整钩子与形状约束 |
|
|
191
|
+
| 输入卡片启发式(含编辑器 + 有背景的最大盒子) | `src/strip.js` 的 `findInputCard` | 背景条宽度/位置不对 | 按真实 DOM 调整 |
|
|
192
|
+
| 侧栏/会话容器的 CSS 模块名后缀(`sidebarCol`、`fade`、`composerSeat` 等) | `src/styles.js` | 侧栏透明、渐隐或底部白边异常 | 重新核对类名后缀 |
|
|
193
|
+
| 全屏右栏遮罩的层叠假设 | `src/styles.js`(panel 的 `z-index` 与 offset 变量) | 遮罩盖住或漏盖 UI | 检查 panel 层叠上下文 |
|
|
194
|
+
|
|
195
|
+
- **不硬编码会话根的哈希类名**:不透明容器由 `markPainters()` 逐层测量后打标,会话切换时自动重新测量;
|
|
196
|
+
哈希变化不会留下死规则。
|
|
197
|
+
- **平台表面**:Windows 标题栏 `html[data-windows-titlebar] [class*=frame]::before`、macOS 侧栏
|
|
198
|
+
`background-image` 渐变、`forced-colors` / `prefers-reduced-transparency` 分别有专门规则(`src/styles.js`)。
|
|
199
|
+
- **可观测性**:宿主 `/health` 与浏览器 `window.__dshcb.diagnostics()` 报告版本、限制、栅栏与各锚点选择结果;
|
|
200
|
+
功能静默降级时可据此定位(`src/diagnostics.js`)。
|
|
201
|
+
- **兼容声明**只有 `engines.dsh`(`>=0.2.1-rc.3`);在未声明兼容的新版本上验证通过后,再扩大范围。
|
|
202
|
+
|
|
203
|
+
## 9. 扩展指南
|
|
204
|
+
|
|
205
|
+
**新增一行设置**:`src/constants.js` 加键 → `src/i18n.js` 加中英文案 → `src/rows.js` 写行组件 →
|
|
206
|
+
加入 `BACKGROUND_ROWS` 或 `UI_GROUPS`(按标签页/卡片分组)→ 若引入新状态,在 `src/storage.js` 加读写。
|
|
207
|
+
|
|
208
|
+
**新增宿主路由**:`host/constants.js` 加路径 → `host/routes.js` 加处理器并导出 → `index.js` 的 `apply` 注册
|
|
209
|
+
→ `src/constants.js` 镜像 URL → 补单元测试。
|
|
210
|
+
|
|
211
|
+
**新增注入型视觉元素**:优先用「静态 CSS + `<html>` 属性/内联变量」表达;需要节点时由 `flushBackground`
|
|
212
|
+
统一调度并在 `ctx.effect` 中清理;先写单元测试覆盖纯逻辑,再用 E2E 验证真实几何。
|
|
213
|
+
|
|
214
|
+
## 10. 决策记录(简版 ADR)
|
|
215
|
+
|
|
216
|
+
| 决策 | 理由 |
|
|
217
|
+
| ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
218
|
+
| 背景图走宿主本地 HTTP 路由,而不是 `file://` 或 base64 | 浏览器半读不到磁盘;大图 base64 会撑爆 localStorage/DOM |
|
|
219
|
+
| 设置存 localStorage,不入宿主配置 | 纯浏览器偏好、即时生效、卸载残留可忽略;避免改 profile 配置 |
|
|
220
|
+
| 图片缓存改为「必须重验证 + ETag」 | 用户可能覆盖同名图片,`immutable` 会让旧图停留一年 |
|
|
221
|
+
| 单文件 `client.js` 产物提交入库 | 兼容 `dsh plugin add github:` 直接安装,无需用户构建 |
|
|
222
|
+
| 浏览器半用纯 JS + esbuild,而不是 TypeScript | 代码量适中;避免为发布引入重型构建链,产物即源码可读 |
|
|
223
|
+
| 设置分区独立成「页面设置」 | 与内置「通用设置」同级,避免污染通用分区 |
|
|
224
|
+
| 本地 HTTP 路由自带请求栅栏 | 宿主只提供进程边界、不提供来源策略;与 `/api` 的 Host/Origin/Sec-Fetch 模型对齐 |
|
|
225
|
+
| SVG 保留但以 CSP 沙箱提供 | 用户可能用矢量壁纸;`sandbox` + `default-src none` 让顶层打开也不执行脚本 |
|
|
226
|
+
| token 聚合优先宿主服务、文件扫描回退 | 磁盘布局是内部实现细节;服务不可用时仍有降级路径 |
|
|
227
|
+
| 设置行由组件直接组合,不声明子槽 | `settings.section` 的文档化注册选项只有 id/order/label;去掉对 children 的未文档化依赖 |
|
|
228
|
+
|
|
229
|
+
## 相关文档
|
|
230
|
+
|
|
231
|
+
- [CONVENTIONS.md](CONVENTIONS.md)
|
|
232
|
+
- [DEVELOPMENT.md](DEVELOPMENT.md)
|
|
233
|
+
- [TESTING.md](TESTING.md)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# 格式与风格规范
|
|
2
|
+
|
|
3
|
+
本文件是 `dsh-chat-background` 仓库的**唯一格式标准**。新增代码、文档、提交与目录结构都遵循这里的规定;
|
|
4
|
+
不一致的地方视为缺陷,由独立审查与 CI 指出。
|
|
5
|
+
|
|
6
|
+
> 适用范围:仓库根目录下所有源码、脚本、配置与文档。
|
|
7
|
+
> 强制执行方式:Prettier(`npm run format:check`)+ 静态检查(`npm run check`)+ CI(`.github/workflows/ci.yml`)。
|
|
8
|
+
|
|
9
|
+
## 1. 通用约定
|
|
10
|
+
|
|
11
|
+
| 项 | 规定 |
|
|
12
|
+
| -------- | ---------------------------------------------------------------------------------------- |
|
|
13
|
+
| 编码 | UTF-8,无 BOM |
|
|
14
|
+
| 换行符 | LF(`.gitattributes` 强制,Windows 检出也保持 LF) |
|
|
15
|
+
| 文件末尾 | 恰好一个换行 |
|
|
16
|
+
| 行尾空白 | 不允许(Markdown 有意换行除外) |
|
|
17
|
+
| 缩进 | 2 空格,不用 Tab |
|
|
18
|
+
| 最大行宽 | 120 列 |
|
|
19
|
+
| 语言 | 代码注释 / JSDoc / commit message 用英文;面向用户的文档与界面文案用中文(英文另有对应) |
|
|
20
|
+
|
|
21
|
+
## 2. 目录与文件命名
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
根目录 只放入口、清单与社区文件(index.js / client.js / package.json / README 等)
|
|
25
|
+
src/ 浏览器半源码,ESM,按功能拆文件,kebab-case 文件名
|
|
26
|
+
src/lib/ 纯函数工具(不触碰 DOM,可直接被单元测试导入)
|
|
27
|
+
host/ 宿主半源码,ESM,kebab-case 文件名
|
|
28
|
+
scripts/ 开发/构建/检查脚本,*.mjs
|
|
29
|
+
scripts/e2e/ 端到端测试编排与 CDP 驱动
|
|
30
|
+
test/unit/ 单元测试,*.test.mjs
|
|
31
|
+
test/e2e/ 测试步骤数据,*.json
|
|
32
|
+
docs/ 维护者文档,UPPER_SNAKE_CASE.md(如 ARCHITECTURE.md)
|
|
33
|
+
assets/ 截图与品牌资源
|
|
34
|
+
.github/ GitHub 社区模板与工作流
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- 一个文件只做一件事;文件名用名词短语(`token-usage.js`、`recent-messages.js`)。
|
|
38
|
+
- 入口文件固定为 `index.js`(宿主半)与 `client.js`(浏览器半产物)。
|
|
39
|
+
- 不要新建与既有目录职责重叠的目录;不确定时先读 [ARCHITECTURE.md](ARCHITECTURE.md)。
|
|
40
|
+
|
|
41
|
+
## 3. JavaScript / ESM
|
|
42
|
+
|
|
43
|
+
### 3.1 基本格式
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// ✓ 正确
|
|
47
|
+
export function readPercent(key, fallback, minimum, maximum) {
|
|
48
|
+
return parsePercent(readString(key), fallback, minimum, maximum)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ✗ 错误:分号、双引号、超宽行
|
|
52
|
+
export function readPercent(key, fallback, minimum, maximum) {
|
|
53
|
+
return parsePercent(readString(key), fallback, minimum, maximum)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- 2 空格缩进、单引号、**不加分号**、行宽 ≤ 120、对象/数组末尾不加尾逗号(由 Prettier 保证)。
|
|
58
|
+
- 箭头函数参数始终带括号:`(value) => value + 1`。
|
|
59
|
+
- 不允许 `console.log` / `console.warn` 等:浏览器半用界面反馈,宿主半用 `ctx.logger`。
|
|
60
|
+
- 空 `catch` 块必须写注释说明为什么可以忽略。
|
|
61
|
+
|
|
62
|
+
### 3.2 import 顺序
|
|
63
|
+
|
|
64
|
+
按三组、组内按字母序,组间空一行:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { readFile } from 'node:fs/promises' // 1. Node 内置(node: 前缀)
|
|
68
|
+
|
|
69
|
+
import { build } from 'esbuild' // 2. 外部依赖(本项目仅开发脚本使用)
|
|
70
|
+
|
|
71
|
+
import { readString } from './storage.js' // 3. 项目内相对路径
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- 相对导入必须带 `.js` 后缀(ESM 要求)。
|
|
75
|
+
- 不直接 `import react`:浏览器半的 React 由 `src/react.js` 在 factory 内绑定。
|
|
76
|
+
|
|
77
|
+
### 3.3 文档注释(JSDoc)
|
|
78
|
+
|
|
79
|
+
- **每个导出的函数 / 常量组 / 组件**必须有 JSDoc:一句话职责 + `@param` / `@returns`(类型用文字描述即可)。
|
|
80
|
+
- 模块顶部必须有 `/** ... */` 文件头,说明这个文件负责什么、与谁协作、有什么不变量。
|
|
81
|
+
- 复杂选择器、启发式、层叠规则必须在注释里写**为什么**,而不是重复代码在做什么。
|
|
82
|
+
- 注释用英文;`src/i18n.js` 里的界面文案是唯一的中文代码内容。
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
/**
|
|
86
|
+
* One durable on/off switch. `'off'` is the stored off state and an absent key
|
|
87
|
+
* means on, so a switch costs nothing until it is turned off.
|
|
88
|
+
* @param options - storage key, plus an optional side effect to run on change.
|
|
89
|
+
* @returns reader, writer and the React hook for that switch.
|
|
90
|
+
*/
|
|
91
|
+
export function createSwitch({ key, onChange }) { ... }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3.4 命名
|
|
95
|
+
|
|
96
|
+
| 种类 | 规则 | 示例 |
|
|
97
|
+
| --------------- | ----------------------------- | -------------------------------------- |
|
|
98
|
+
| 变量 / 函数 | camelCase | `readSelection`、`renderComposerStrip` |
|
|
99
|
+
| React 组件 | PascalCase | `ProgressDock`、`StripColorRow` |
|
|
100
|
+
| 模块级常量 | UPPER_SNAKE_CASE | `TOKEN_POLL_MS`、`STRIP_ATTRIBUTE` |
|
|
101
|
+
| 布尔 | `is` / `has` / `can` 前缀 | `isDark`、`hasEditor` |
|
|
102
|
+
| 事件回调 | `on` + 事件 | `onResize`、`onChange` |
|
|
103
|
+
| localStorage 键 | `dsh-chat-background:<kebab>` | `dsh-chat-background:strip-color` |
|
|
104
|
+
|
|
105
|
+
## 4. CSS
|
|
106
|
+
|
|
107
|
+
- 所有规则集中在 `src/styles.js` 的一个模板字符串里,不新增 `<style>` 注入点。
|
|
108
|
+
- 每条规则单独成块;属性顺序:定位/布局 → 盒模型 → 排版 → 视觉 → 动画。
|
|
109
|
+
- 选择器按「背景 → 侧栏 → composer → 设置 → 控件 → 进度条 → token 行」分组,组间用 `/* ---- 组名 ---- */` 分隔。
|
|
110
|
+
- 不给宿主元素打标时优先用稳定的 `data-slot` / `data-*` 钩子;确实没有稳定钩子时,才允许用 CSS 模块名后缀选择器(如 `[class*="sidebarCol"]`),并在注释里写明这是升级敏感点。
|
|
111
|
+
- 规则若依赖某个层叠/包含块不变量,必须写注释解释原因。
|
|
112
|
+
- 设置行统一是两列 grid 模块:左侧说明、右侧控件整体成块(`flex: none`、固定槽位、右对齐),控件尺寸不随语言、字体缩放、DPI 或窗口宽度漂移;只有窄屏(≤640px)才把控件堆到说明下方。
|
|
113
|
+
- 原生控件的尺寸不要用手调像素偏移去对齐(如滑块的 `margin-top: -50.5px`),用尺寸变量推导(`calc((track - thumb) / 2)`),否则在别的缩放/DPI 下会漂移。
|
|
114
|
+
|
|
115
|
+
## 5. JSON / YAML
|
|
116
|
+
|
|
117
|
+
- 2 空格缩进、LF、文件末尾一个换行;JSON 键按「身份 → 元数据 → 依赖 → 配置」顺序排列,不强制字母序。
|
|
118
|
+
- YAML 字符串优先用单引号;数组与对象保持块状,不用行内 `{}` 超过两项。
|
|
119
|
+
- 步骤脚本(`test/e2e/steps/*.json`)里每一步只做一件事,`check` 的 `label` 用简短中文描述期望。
|
|
120
|
+
|
|
121
|
+
## 6. Markdown
|
|
122
|
+
|
|
123
|
+
- 每篇文档**只有一个 H1**,紧随一行 `>` 元信息(适用范围 / 更新日期 / 相关文档),再进正文。
|
|
124
|
+
- 章节用 `## 1. 标题` 编号(长文档必须编号);子节 `### 1.1 标题`。
|
|
125
|
+
- 表格必须对齐竖线、表头与单元格两侧留空格;长表格前留一个空行。
|
|
126
|
+
- 代码块必须标注语言(`bash` / `js` / `json` / `text` / `powershell`)。
|
|
127
|
+
- 列表、代码块、表格前后各留一个空行。
|
|
128
|
+
- 中文与英文/数字之间留一个半角空格;中文句子用全角标点,代码、键名、路径用反引号包裹。
|
|
129
|
+
- 站内链接用相对路径(如 `docs/TESTING.md`),指向外部资源用完整 URL。
|
|
130
|
+
- 图片语法 ``,描述写清楚画面内容。
|
|
131
|
+
- 文档末尾必须有一节 `## 相关文档`,列出 1–5 个最相关的文档链接。
|
|
132
|
+
|
|
133
|
+
### 6.1 文档模板
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
# 文档标题
|
|
137
|
+
|
|
138
|
+
> 一句话说明本文档解决什么问题 / 适用对象。
|
|
139
|
+
> 相关:[ARCHITECTURE.md](ARCHITECTURE.md) · [TESTING.md](TESTING.md)
|
|
140
|
+
|
|
141
|
+
## 1. 第一节
|
|
142
|
+
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
## 相关文档
|
|
146
|
+
|
|
147
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 7. 提交与分支
|
|
151
|
+
|
|
152
|
+
- 提交信息:`type(scope): subject`,英文、祈使句、≤ 72 字符首行;正文说明动机与验证方式。
|
|
153
|
+
- `type` 见 [CONTRIBUTING.md](../CONTRIBUTING.md);`scope` 取 `host` / `client` / `styles` / `e2e` / `docs` / `repo`。
|
|
154
|
+
- 一个提交只做一件事;`client.js` 构建产物与对应源码改动放在同一个提交里。
|
|
155
|
+
- 分支命名:`feat/`、`fix/`、`docs/`、`chore/` + 简短英文短语。
|
|
156
|
+
|
|
157
|
+
## 8. i18n 文案
|
|
158
|
+
|
|
159
|
+
- 所有用户可见文案集中在 `src/i18n.js` 的 `zh` / `en` 两个对象里,键集合必须完全一致(`scripts/check.mjs` 校验)。
|
|
160
|
+
- 键名 camelCase、以语义命名(`stripColorTitle`),不按出现顺序编号。
|
|
161
|
+
- 带占位符的文案统一用 `{name}` 形式,例如 `{steps}`、`{exact}`。
|
|
162
|
+
- 新增设置行的同时补 `locale/zh.json` 与 `locale/en.json` 的 `meta`(仅当包级展示信息变化时需要)。
|
|
163
|
+
|
|
164
|
+
## 9. 自动执行矩阵
|
|
165
|
+
|
|
166
|
+
| 规则 | 执行者 | 命令 |
|
|
167
|
+
| -------------------------------------------------------------------- | ------------------- | ----------------------------------------- |
|
|
168
|
+
| 缩进/引号/分号/行宽/Markdown 表格 | Prettier | `npm run format` / `npm run format:check` |
|
|
169
|
+
| 文件存在性、清单字段、locale、构建产物新鲜度、编码/换行、i18n 键一致 | `scripts/check.mjs` | `npm run check` |
|
|
170
|
+
| 纯函数行为 | Node 内置测试 | `npm run test:unit` |
|
|
171
|
+
| 真实界面行为 | CDP 端到端套件 | `npm run test:e2e` |
|
|
172
|
+
| 全部上游检查 | CI | `.github/workflows/ci.yml` |
|
|
173
|
+
|
|
174
|
+
## 10. 例外
|
|
175
|
+
|
|
176
|
+
- `assets/screenshots/` 是二进制证据,不做格式化。
|
|
177
|
+
- 历史 `client.js` 产物由构建脚本生成,格式由 esbuild 决定,不手工整理。
|
|
178
|
+
|
|
179
|
+
## 相关文档
|
|
180
|
+
|
|
181
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
182
|
+
- [DEVELOPMENT.md](DEVELOPMENT.md)
|
|
183
|
+
- [CONTRIBUTING.md](../CONTRIBUTING.md)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# 开发指南
|
|
2
|
+
|
|
3
|
+
> 面向修改本插件代码的维护者:环境、热更新规则、调试方法与常见陷阱。
|
|
4
|
+
> 相关:[ARCHITECTURE.md](ARCHITECTURE.md) · [TESTING.md](TESTING.md) · [CONVENTIONS.md](CONVENTIONS.md)
|
|
5
|
+
|
|
6
|
+
## 1. 环境准备
|
|
7
|
+
|
|
8
|
+
| 依赖 | 版本 | 用途 |
|
|
9
|
+
| -------------- | ------------------------ | ------------------------ |
|
|
10
|
+
| Node.js | `^22.19.0 \|\| >=24.0.0` | 构建、检查、测试 |
|
|
11
|
+
| npm | 随 Node | 安装 esbuild / prettier |
|
|
12
|
+
| DSH 桌面端 | `>= 0.2.1-rc.3` | 真实环境验证、端到端测试 |
|
|
13
|
+
| Edge 或 Chrome | 任意近期版本 | 无头端到端测试 |
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm run build
|
|
18
|
+
npm run check
|
|
19
|
+
npm run test:unit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. 仓库结构与修改规则
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
src/ 浏览器半源码(改这里)→ npm run build → client.js
|
|
26
|
+
host/ index.js 宿主半源码(改这里,无需构建)
|
|
27
|
+
client.js 构建产物,不手工编辑;与源码改动一起提交
|
|
28
|
+
scripts/ 构建/检查/E2E 编排(改这里用 node --check 验证)
|
|
29
|
+
test/unit/ 单元测试(node:test,直接 import 源码)
|
|
30
|
+
test/e2e/steps/ 端到端步骤数据(JSON,不写逻辑代码)
|
|
31
|
+
docs/ 文档;改动遵循 CONVENTIONS 的文档模板
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- 新增浏览器功能:在 `src/` 加模块 → 被 `src/index.js` 的导入链引用(否则不会进产物)→ `npm run build`。
|
|
35
|
+
- 新增宿主逻辑:放进 `host/` 并由 `index.js` 或其余 host 模块 import;宿主半只能 import `node:` 内置模块。
|
|
36
|
+
- 浏览器半不要 import `react`:通过 `src/react.js` 的 `h` / hook 包装使用平台 React。
|
|
37
|
+
- 新增文案:`src/i18n.js` 的 `zh` 与 `en` 同时加,键必须一致(`npm run check` 会拦)。
|
|
38
|
+
|
|
39
|
+
## 3. 热更新规则(最重要)
|
|
40
|
+
|
|
41
|
+
| 改了什么 | 生效方式 |
|
|
42
|
+
| --------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `src/` 下的浏览器代码 | `npm run build` 后把 `client.js` 同步到已安装目录;DSH 客户端轮询产物并热替换,**无需重启** |
|
|
44
|
+
| `host/`、`index.js` | 宿主半模块,**需要重启 DSH 桌面端**(或依赖 DSH HMR 的 profile 重新组合) |
|
|
45
|
+
| `package.json` 的 `dsh.client` / `dsh.bundle` | 需要重启才能重新组合 |
|
|
46
|
+
| `locale/`、`icon.svg`、README | 只影响展示,无需重启 |
|
|
47
|
+
|
|
48
|
+
开发期同步到本机已安装目录(以桌面 profile 为例):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 浏览器半:改完立即热替换
|
|
52
|
+
node -e "require('node:fs').cpSync('client.js', process.env.USERPROFILE + '/.dsh/profiles/desktop/node_modules/dsh-chat-background/client.js')"
|
|
53
|
+
|
|
54
|
+
# 宿主半(需要重启应用才生效)
|
|
55
|
+
node -e "require('node:fs').cpSync('host', process.env.USERPROFILE + '/.dsh/profiles/desktop/node_modules/dsh-chat-background/host', { recursive: true })"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> 不推荐把工作区直接链接进 profile:改动宿主半会触发重新组合,可能中断正在使用的会话。
|
|
59
|
+
> 用 `npm run test:e2e` 在隔离实例上验证是更安全的方式。
|
|
60
|
+
|
|
61
|
+
### 3.1 安装到本机 profile
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node scripts/install-local.mjs --profile desktop # dry-run
|
|
65
|
+
node scripts/install-local.mjs --profile desktop --apply # 写配置 + 拷贝文件 + 自动备份
|
|
66
|
+
node scripts/check-install.mjs --profile desktop --url http://127.0.0.1:<端口>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
安装完成后**完全退出并重新打开**桌面端;确认新版本正常后再清理旧文件(profile 备份、.tmp/_scratch 等):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node scripts/cleanup-local.mjs --profile desktop # 先看清单
|
|
73
|
+
node scripts/cleanup-local.mjs --profile desktop --apply # 删除(可加 --keep-artifacts 保留 E2E 产物)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 4. 调试
|
|
77
|
+
|
|
78
|
+
- **看浏览器侧状态**:DSH 页面 DevTools(`Ctrl+Shift+I`)里检查:
|
|
79
|
+
- `document.documentElement` 的 `data-dsh-chat-background` 与 `data-dsh-chat-background-right-fullscreen` 属性;
|
|
80
|
+
- `<html>` / 背景层上的 `--dsh-chat-background-*`、`--dsh-chat-strip-bg`、`--dsh-progress-color`、`--dsh-token-size` 变量;
|
|
81
|
+
- `localStorage` 里 `dsh-chat-background:` 开头的键;
|
|
82
|
+
- `style[data-plugin-css="dsh-chat-background/background.css"]` 与 `.../measured.css` 两个样式表。
|
|
83
|
+
- **看宿主侧**:`GET /chat-backgrounds/list`、`/chat-backgrounds/token-total` 直接 curl 即可(公开路由);
|
|
84
|
+
端口从运行环境上下文取,不要写死。
|
|
85
|
+
- **看构建产物**:`client.js` 开头有 banner(版本、许可证);`npm run check` 会验证它与 `src/` 一致。
|
|
86
|
+
- **看端到端证据**:`npm run test:e2e -- --keep` 后,`.tmp/e2e-artifacts/<suite>/report.json` 含每一步结果、
|
|
87
|
+
页面事实、截图与控制台错误。
|
|
88
|
+
|
|
89
|
+
## 5. 常见陷阱(改位置/层叠前必读)
|
|
90
|
+
|
|
91
|
+
1. `transform` / `filter` / `backdrop-filter` 会让元素成为 `position: fixed` 后代的包含块,**禁止**在 composer 区域使用。
|
|
92
|
+
2. 「背景之上、所有 UI 之下」= 容器 `isolation: isolate` + 伪元素 `z-index: -1`;`isolation` 不会创建 fixed 包含块。
|
|
93
|
+
3. 不要给 app 已算好的布局盒加 `padding` / `margin`:会被座位裁剪吃掉内容。背景条是绝对定位、零布局影响。
|
|
94
|
+
4. `MutationObserver` 看不到纯样式尺寸变化;composer 包装层另有 `ResizeObserver`。
|
|
95
|
+
5. 层叠/裁剪类改动必须用真实 DOM 量,不要用自摆假体验证。
|
|
96
|
+
6. 主题 token 发布在 `body` 上(`--dsw-alias-*`),不是 `html`。
|
|
97
|
+
7. 卸载时要把 `<html>` 属性、内联变量、注入节点、测量样式表与待执行 rAF 一起清理(`teardownVisuals()`)。
|
|
98
|
+
|
|
99
|
+
## 6. 新增一行设置的步骤
|
|
100
|
+
|
|
101
|
+
1. `src/constants.js`:加存储键(如 `FOO_KEY`)与默认值;
|
|
102
|
+
2. `src/i18n.js`:加 `zh` / `en` 的标题与描述(键名 camelCase);
|
|
103
|
+
3. `src/rows.js`:写一个行组件(复用 `Row` / `SwitchRow` / `ColorRow` / `ActiveSliderRow`);
|
|
104
|
+
4. `src/rows.js`:把组件加入 `BACKGROUND_ROWS` 或 `UI_GROUPS`(按标签页/卡片分组);
|
|
105
|
+
5. 若引入新状态:在 `src/storage.js` 加读写,或建 `createSwitch` / `createColorSetting` 实例;
|
|
106
|
+
6. `npm run format && npm run build && npm run check && npm run test:unit`;
|
|
107
|
+
7. 在 `test/e2e/steps/` 的相关套件里加断言(至少验证行出现、控件类型、写入设置后 DOM/存储变化)。
|
|
108
|
+
|
|
109
|
+
## 7. 新增宿主路由的步骤
|
|
110
|
+
|
|
111
|
+
1. `host/constants.js` 加路径;
|
|
112
|
+
2. `host/routes.js` 写处理器(方法校验、错误响应、必要时缓存)并加入返回对象;
|
|
113
|
+
3. `index.js` 用 `ctx.effect(() => ctx.webServer.register(...))` 注册;
|
|
114
|
+
4. `src/constants.js` 镜像 URL;
|
|
115
|
+
5. `test/unit/routes.test.mjs` 覆盖成功与失败路径;
|
|
116
|
+
6. 涉及副作用的写操作必须加 CSRF 防护(参考 `FOLDER_HEADER` 的自定义头方案)。
|
|
117
|
+
|
|
118
|
+
## 8. 发布
|
|
119
|
+
|
|
120
|
+
见 [PUBLISHING.md](PUBLISHING.md)。
|
|
121
|
+
|
|
122
|
+
## 相关文档
|
|
123
|
+
|
|
124
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
125
|
+
- [TESTING.md](TESTING.md)
|
|
126
|
+
- [CONVENTIONS.md](CONVENTIONS.md)
|
|
127
|
+
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|