feat: support custom instance icons and refresh release

This commit is contained in:
shiyue
2026-08-25 22:11:07 +08:00
parent bd20a79d4b
commit 05480e511d
20 changed files with 1104 additions and 23 deletions

View File

@@ -0,0 +1,6 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "前端入口与运行时更新验收"}
{"file": ".trellis/spec/backend/error-handling.md", "reason": "服务端错误与降级验收"}
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "跨层契约验收"}
{"file": ".trellis/tasks/08-25-instance-custom-icon/research/frontend-icon-chain.md", "reason": "前端影响面核对"}
{"file": ".trellis/tasks/08-25-instance-custom-icon/research/backend-settings-storage.md", "reason": "后端安全与持久化核对"}

View File

@@ -0,0 +1,8 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/spec/frontend/index.md", "reason": "前端设置页与图标消费点规范入口"}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "入口资源和运行时更新质量要求"}
{"file": ".trellis/spec/backend/index.md", "reason": "服务端规范入口"}
{"file": ".trellis/spec/backend/error-handling.md", "reason": "HTTP 和配置保存错误处理"}
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "跨前后端契约与数据流"}
{"file": ".trellis/tasks/08-25-instance-custom-icon/research/frontend-icon-chain.md", "reason": "前端图标与设置链路调研"}
{"file": ".trellis/tasks/08-25-instance-custom-icon/research/backend-settings-storage.md", "reason": "后端配置与存储调研"}

View File

@@ -0,0 +1,84 @@
# 实例自定义图标技术设计
## 数据流
```text
本地 PNG/JPEG/WebP
→ 浏览器解码并居中裁剪为 512×512 PNG
→ POST /api/instance-iconBearer二进制≤4 MiB
→ 服务端验证 Content-Type、PNG 签名、IHDR 尺寸
→ CONFIG_DIR/.instance-icon.png.tmp 原子 rename
→ 返回 { custom, version, updatedAt, url }
→ 前端更新 favicon、Apple Touch、登录 Logo、通知图标与设置预览
```
## 持久化与升级
- 正式文件:`CONFIG_DIR/instance-icon.png`
- 临时文件:`CONFIG_DIR/.instance-icon.png.tmp`,失败时清理。
- 默认 `CONFIG_DIR``APP_DIR/config``.gitignore` 精确忽略这两个文件。
- 如果部署设置 `CC_WEB_CONFIG_DIR` 到仓库外,图标自动跟随外置配置目录。
- 不覆盖任何 `public/*.png`,源码升级只更新默认兜底图。
## HTTP 契约
### GET /api/instance-icon/config
公开返回:
```json
{
"custom": true,
"version": "sha256-short-hash",
"updatedAt": "ISO-8601",
"url": "/api/instance-icon?v=sha256-short-hash"
}
```
未配置时 `custom=false``version=default`URL 仍指向统一读取接口。
### GET /api/instance-icon
- 自定义存在且有效:返回该 PNG。
- 未配置或运行文件失效:返回 `public/icon-192.png`
- `Content-Type: image/png``X-Content-Type-Options: nosniff`
- 使用 ETag/no-cache版本化 URL 负责同页缓存刷新。
### POST /api/instance-icon
- 必须通过现有 Bearer token 鉴权。
-`Content-Type: image/png`body 非空且不超过 4 MiB。
- 必须是标准 PNG 签名IHDR 宽高均为 512。
- 固定路径原子写入;返回最新配置对象。
### DELETE /api/instance-icon
- 必须通过现有 Bearer token 鉴权。
- 删除实例运行文件,幂等返回默认配置对象。
### GET /api/site.webmanifest
- 未自定义:保留现有 192/512 默认图标声明。
- 已自定义:使用版本化 `/api/instance-icon`,声明 512×512。
## 前端契约
- head 中 favicon、Apple Touch 与 Manifest 从首屏即指向动态 API默认服务端兜底避免闪烁。
- 需要动态更新的图片用 `data-instance-icon` 标识;图标 link 用 `data-instance-icon-link` 标识。
- `instanceIconUrl(config)` 是唯一 URL 生成入口;浏览器通知和 Service Worker 默认也用 `/api/instance-icon`
- 外观设置中新增“实例图标”区:预览、选择图标、恢复默认、状态文字。
- 客户端拒绝非 PNG/JPEG/WebP使用 canvas 居中裁剪,不拉伸;上传失败保留旧图标。
## 错误语义
- 401未鉴权写操作。
- 400空内容、错误 MIME、非法 PNG、非 512×512。
- 413超过 4 MiB。
- 500原子保存/读取不可恢复错误;前端显示服务端中文消息。
## 测试顺序
1. 增加聚焦服务集成测试并确认在实现前失败。
2. 覆盖默认读取、鉴权、非法输入、上传、覆盖版本、恢复默认与 Git 外置路径。
3. 增加前端静态/DOM 契约测试,覆盖设置 UI 和所有图标消费点。
4. 实现最小代码使测试通过,再运行总回归。

View File

@@ -0,0 +1,40 @@
# 实例自定义图标需求
## 背景
用户在多台机器部署 cc-web。当前各实例使用相同图标浏览器和 cc-web 界面中难以快速区分,需要每个实例可独立设置图标。
## 用户故事
作为 cc-web 管理者,我希望在设置页从本机选择实例图标,以便多实例并行使用时能直观看出当前机器;升级源码后,该选择仍应保留。
## 功能要求
1. 设置页新增实例图标设置项,展示当前图标。
2. 用户可选择受支持的本地图片并保存,保存成功后当前页面使用新图标。
3. 用户可恢复默认图标。
4. 未配置自定义图标的安装保持当前默认外观。
5. 自定义图标文件及选择状态存储在 Git 管理之外,`git pull` 不覆盖。
6. 服务端校验文件类型与大小,拒绝非法或超限输入并返回明确错误。
7. 图标更新后避免浏览器继续显示旧缓存。
## 非目标
- 不实现在线图标市场、URL 抓取、裁剪编辑器或多套图标历史。
- 不改变现有主题系统和品牌名称。
- 不自动修改操作系统桌面快捷方式或已安装 PWA 的原生图标。
## 验收标准
- 默认、上传、刷新后持久化、覆盖更新、恢复默认、非法类型和超限文件均有自动化覆盖。
- 侧栏/品牌入口与浏览器页签等现有运行时图标消费点按实际架构统一接入。
- 自定义图标数据未出现在 `git status` 的已跟踪/未跟踪源码改动中。
- 相关测试与构建通过,未破坏既有设置保存行为。
## 技术约束
- 浏览器接受 PNG/JPEG/WebP 选择,居中裁剪为 512×512 PNG 后上传;服务端最大接收 4 MiB。
- 自定义图片固定写入 `CC_WEB_CONFIG_DIR/instance-icon.png`(未设置环境变量时为 `config/instance-icon.png`),通过原子替换保存。
- 公开读取使用 `/api/instance-icon`;写入和恢复默认必须 Bearer 鉴权。
- 图标内容哈希作为版本,设置成功后更新 favicon、登录 Logo、通知图标和动态 Manifest。
- 默认行为继续使用现有源码图标;不保证操作系统对已安装 PWA 图标的即时刷新。

View File

@@ -0,0 +1,33 @@
# 服务端设置与外置存储调研
## 架构与持久化
- `server.js` 使用 Node 原生 HTTP 与 `ws`,路由集中在 HTTP 回调和 WebSocket 消息 switch。
- `CONFIG_DIR = CC_WEB_CONFIG_DIR || APP_DIR/config`,现有运行配置均从该目录读写;测试会把它指向临时目录。
- 默认 `config/` 位于仓库内,但 `.gitignore` 对运行文件逐项忽略;实例图标必须新增精确规则,生产还可通过 `CC_WEB_CONFIG_DIR` 完全外置。
## 图片上传先例
- `POST /api/attachments` 已采用原始二进制 body不使用 multipart通过 Bearer token 鉴权。
- 既有逻辑包含大小上限、MIME 白名单、空内容拒绝、固定/清理后的文件名与读取鉴权。
- 实例图标上传可沿用“二进制 REST + 鉴权”,不应把大图片转 base64 塞入 WebSocket 配置消息。
## 静态响应与安全
- 现有静态资源响应使用 `no-store`,并检查解析后的路径仍位于 `PUBLIC_DIR`
- 实例图标应只使用服务端固定文件名,写入临时文件后原子 rename不接受客户端路径。
- 推荐只保存规范化 PNG服务端检查 PNG 签名与 IHDR 尺寸,避免依赖声明 MIME。
## 推荐接口
- `GET /api/instance-icon/config`:返回是否自定义、版本、更新时间和图标 URL不含敏感信息。
- `GET /api/instance-icon`:公开返回自定义 PNG未配置时返回默认 `public/icon-192.png`
- `POST /api/instance-icon`Bearer 鉴权,接收固定尺寸 PNG校验后原子写入。
- `DELETE /api/instance-icon`Bearer 鉴权,恢复默认图标。
- `GET /api/site.webmanifest`:根据是否自定义返回默认或实例图标声明。
## 验证重点
- 默认回退、未鉴权写入、非法 MIME/签名/尺寸、超限、覆盖更新、恢复默认。
- 运行文件位于 `CONFIG_DIR`,不进入 `PUBLIC_DIR` 且被 Git 忽略。
- 自定义 URL 带内容版本,避免同页更新仍显示旧图。

View File

@@ -0,0 +1,26 @@
# 前端图标与设置链路调研
## 图标消费点
- `public/index.html`favicon、32px favicon、Apple Touch 图标、Manifest以及登录页 `icon-192.png`
- `public/app.js`:浏览器通知固定使用 `/icon-192.png`,强制修改密码面板再次渲染登录 Logo。
- `public/sw.js`Service Worker 通知默认使用 `/icon-192.png`
- `public/site.webmanifest`PWA 安装图标使用 `icon-192.png``icon-512.png`
## 设置链路
- `#settings-btn` 调用 `showSettingsPanel()`Codex 与 Claude 设置页都复用 `buildAppearanceSettingsHtml()` / `mountAppearanceSettings()`
- 设置读取与保存统一走 WebSocket 消息;服务端在消息 switch 中分发 `get_*_config` / `save_*_config` 并回推配置。
- 新功能适合增加独立的实例图标配置消息,避免混入 Codex/Claude 模型配置。
## 静态资源与缓存
- 静态资源由 `server.js``PUBLIC_DIR` 返回,响应使用 `Cache-Control: no-store, max-age=0`
- 默认图标未参与前端资产 hash自定义运行时 URL 应带版本参数或 ETag确保同页更新。
- PWA/操作系统已安装图标受平台缓存控制,即使动态 Manifest 更新也不保证立即刷新;浏览器页签、登录 Logo 与通知图标可可靠动态更新。
## 测试与风险
- 总回归入口是 `npm run regression``scripts/regression.js` 已覆盖设置页消息与静态结构。
- 必须覆盖默认回退、设置页上传/恢复消息、通知图标与登录 Logo 使用统一 URL。
- 不应修改主题级设置按钮图标;实例品牌图标与主题装饰资产是不同职责。

View File

@@ -0,0 +1,26 @@
{
"id": "instance-custom-icon",
"name": "instance-custom-icon",
"title": "实例自定义图标",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "shiyue",
"assignee": "shiyue",
"createdAt": "2026-08-25",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}