快速开始
Tweakcn 自定义主题导入设计
Tweakcn 自定义主题导入设计
Status:已于 2026-04-22 在终端批准
摘要
添加正好一个浏览器本地的自定义 Control UI 主题槽,可从 tweakcn 分享链接导入。现有内置主题系列仍为 claw、knot 和 dash。新的 custom 系列表现得像普通的 OpenClaw 主题系列,并且在导入的 tweakcn 载荷包含明暗两套 token 集时,支持 light、dark 和 system 模式。
导入的主题仅与其余 Control UI 设置一起存储在当前浏览器配置文件中。它不会写入 Gateway 网关配置,也不会跨设备或浏览器同步。
问题
Control UI 主题系统目前被限定在三个硬编码主题系列内:
ui/src/ui/theme.tsui/src/ui/views/config.tsui/src/styles/base.css
用户可以在内置系列和模式变体之间切换,但如果不编辑仓库 CSS,就无法引入 tweakcn 主题。请求的结果小于通用主题系统:保留三个内置主题,并添加一个由用户控制的导入槽,可从 tweakcn 链接替换。
目标
- 保持现有内置主题系列不变。
- 添加正好一个导入的自定义槽,而不是主题库。
- 接受 tweakcn 分享链接或直接的
https://tweakcn.com/r/themes/{id}URL。 - 仅在浏览器本地存储中持久化导入的主题。
- 让导入槽与现有
light、dark和system模式控件配合工作。 - 保持故障行为安全:错误导入绝不会破坏当前活动 UI 主题。
非目标
- 不提供多主题库或浏览器本地导入列表。
- 不提供 Gateway 网关侧持久化或跨设备同步。
- 不提供任意 CSS 编辑器或原始主题 JSON 编辑器。
- 不从 tweakcn 自动加载远程字体资产。
- 不尝试支持只暴露一种模式的 tweakcn 载荷。
- 不进行超出 Control UI 所需接缝之外的仓库级主题重构。
已做出的用户决策
- 保留三个内置主题。
- 添加一个由 tweakcn 驱动的导入槽。
- 将导入的主题存储在浏览器中,而不是 Gateway 网关配置中。
- 为导入槽支持
light、dark和system。 - 用下一次导入覆盖自定义槽是预期行为。
推荐方案
向 Control UI 主题模型添加第四个主题系列 ID:custom。只有存在有效的 tweakcn 导入时,custom 系列才可选择。导入的载荷会规范化为 OpenClaw 专用的自定义主题记录,并与其余 UI 设置一起存储在浏览器本地存储中。
运行时,OpenClaw 渲染一个受管理的 <style> 标签,用于定义解析后的自定义 CSS 变量块:
:root[data-theme="custom"] { ... }
:root[data-theme="custom-light"] { ... }
这会将自定义主题变量限定在 custom 系列内,并避免内联 CSS 变量泄漏到内置系列中。
架构
主题模型
更新 ui/src/ui/theme.ts:
- 扩展
ThemeName以包含custom。 - 扩展
ResolvedTheme以包含custom和custom-light。 - 扩展
VALID_THEME_NAMES。 - 更新
resolveTheme(),使custom映射现有系列行为:custom + dark->customcustom + light->custom-lightcustom + system-> 根据操作系统偏好得到custom或custom-light
不会为 custom 添加旧版别名。
持久化模型
在 ui/src/ui/storage.ts 中扩展 UiSettings 持久化,加入一个可选的自定义主题载荷:
customTheme?: ImportedCustomTheme
推荐的存储形态:
type ImportedCustomTheme = {
sourceUrl: string;
themeId: string;
label: string;
importedAt: string;
light: Record<string, string>;
dark: Record<string, string>;
};
说明:
sourceUrl存储规范化后的原始用户输入。themeId是从 URL 中提取的 tweakcn 主题 ID。label是存在时的 tweakcnname字段,否则为Custom。light和dark是已规范化的 OpenClaw token 映射,而不是原始 tweakcn 载荷。- 导入的载荷与其他浏览器本地设置并列存在,并序列化在同一个本地存储文档中。
- 如果加载时存储的自定义主题数据缺失或无效,则忽略该载荷;当持久化的系列为
custom时,回退到theme: "claw"。
运行时应用
在 Control UI 运行时中添加一个窄范围的自定义主题样式表管理器,归属位置靠近 ui/src/ui/app-settings.ts 和 ui/src/ui/theme.ts。
职责:
- 在
document.head中创建或更新一个稳定的<style id="openclaw-custom-theme">标签。 - 仅当存在有效自定义主题载荷时输出 CSS。
- 清除载荷时移除该样式标签内容。
- 将内置系列 CSS 保留在
ui/src/styles/base.css中;不要把导入的 token 拼接进已检入的样式表。
此管理器在设置加载、保存、导入或清除时运行。
浅色模式选择器
实现应优先使用 data-theme-mode="light" 来处理跨系列浅色样式,而不是为 custom-light 做特殊分支。如果现有选择器固定为 data-theme="light",且需要应用到每个浅色系列,则应作为这项工作的一部分扩展它。
导入 UX
更新 ui/src/ui/views/config.ts 的 Appearance 部分:
- 在
Claw、Knot和Dash旁边添加一张Custom主题卡片。 - 当不存在已导入的自定义主题时,将卡片显示为禁用。
- 在主题网格下方添加一个导入面板,包含:
- 一个用于 tweakcn 分享链接或
/r/themes/{id}URL 的文本输入 - 一个
Import按钮 - 当自定义载荷已存在时提供一条
Replace路径 - 当自定义载荷已存在时提供一个
Clear操作
- 一个用于 tweakcn 分享链接或
- 当载荷存在时,显示导入的主题标签和来源主机。
- 如果当前活动主题是
custom,导入替换会立即应用。 - 如果当前活动主题不是
custom,导入只会存储新的载荷,直到用户选择Custom卡片。
ui/src/ui/views/config-quick.ts 中的快速设置主题选择器也应仅在载荷存在时显示 Custom。
URL 解析和远程获取
浏览器导入路径接受:
https://tweakcn.com/themes/{id}https://tweakcn.com/r/themes/{id}
实现应将两种形式都规范化为:
https://tweakcn.com/r/themes/{id}
然后浏览器直接获取规范化后的 /r/themes/{id} 端点。
对外部载荷使用窄范围 schema 校验器。因为这是不可信的外部边界,优先使用 zod schema。
必需的远程字段:
- 顶层
name,可选字符串 cssVars.theme,可选对象cssVars.light,对象cssVars.dark,对象
如果缺少 cssVars.light 或 cssVars.dark 中任意一个,则拒绝导入。这是有意为之:已批准的产品行为是完整模式支持,而不是尽力合成缺失的一侧。
Token 映射
不要盲目镜像 tweakcn 变量。将有界子集规范化为 OpenClaw token,并在辅助函数中派生其余 token。
直接导入的 token
从每个 tweakcn 模式块中:
backgroundforegroundcardcard-foregroundpopoverpopover-foregroundprimaryprimary-foregroundsecondarysecondary-foregroundmutedmuted-foregroundaccentaccent-foregrounddestructivedestructive-foregroundborderinputringradius
从共享的 cssVars.theme 中(如果存在):
font-sansfont-mono
如果某个模式块覆盖 font-sans、font-mono 或 radius,则模式本地值优先。
为 OpenClaw 派生的 token
导入器从导入的基础颜色派生 OpenClaw 专用变量:
--bg-accent--bg-elevated--bg-hover--panel--panel-strong--panel-hover--chrome--chrome-strong--text--text-strong--chat-text--muted--muted-strong--accent-hover--accent-muted--accent-subtle--accent-glow--focus--focus-ring--focus-glow--secondary--secondary-foreground--danger--danger-muted--danger-subtle
派生规则放在纯辅助函数中,以便独立测试。确切的颜色混合公式是实现细节,但该辅助函数必须满足两个约束:
- 保持接近导入主题意图的可读对比度
- 对相同导入载荷生成稳定输出
v1 中忽略的 token
这些 tweakcn token 在第一个版本中有意忽略:
chart-*sidebar-*font-serifshadow-*tracking-*letter-spacingspacing
这会把范围限定在当前 Control UI 实际需要的 token 上。
字体
如果存在字体栈字符串,则导入它们,但 OpenClaw 在 v1 中不会加载远程字体资产。如果导入的栈引用浏览器中不可用的字体,则适用正常回退行为。
故障行为
错误导入必须以关闭方式失败。
- URL 格式无效:显示内联校验错误,不发起获取。
- 不支持的主机或路径形态:显示内联校验错误,不发起获取。
- 网络失败、非 OK 响应或 JSON 格式错误:显示内联错误,保持当前存储载荷不变。
- Schema 失败或缺少 light/dark 块:显示内联错误,保持当前存储载荷不变。
- 清除操作:
- 移除存储的自定义载荷
- 移除受管理的自定义样式标签内容
- 如果
custom当前处于活动状态,则将主题系列切回claw
- 首次加载时存储的自定义载荷无效:
- 忽略存储的载荷
- 不输出自定义 CSS
- 如果持久化的主题系列为
custom,则回退到claw
失败导入在任何时候都不应让活动文档应用了部分自定义 CSS 变量。
实现中预计会变更的文件
主要文件:
ui/src/ui/theme.tsui/src/ui/storage.tsui/src/ui/app-settings.tsui/src/ui/views/config.tsui/src/ui/views/config-quick.tsui/src/styles/base.css
可能新增的辅助函数:
ui/src/ui/custom-theme.ts
测试:
ui/src/ui/app-settings.test.tsui/src/ui/storage.node.test.tsui/src/ui/views/config.browser.test.ts- 针对 URL 解析和载荷规范化的新聚焦测试
测试
最低实现覆盖范围:
- 将分享链接 URL 解析为 tweakcn 主题 ID
- 将
/themes/{id}和/r/themes/{id}规范化为获取 URL - 拒绝不支持的主机和格式错误的 ID
- 校验 tweakcn 载荷形态
- 将有效 tweakcn 载荷映射为规范化的 OpenClaw 浅色和深色 token 映射
- 在浏览器本地设置中加载并保存自定义载荷
- 为
light、dark和system解析custom - 当不存在载荷时禁用
Custom选择 - 当
custom已经处于活动状态时立即应用导入的主题 - 当活动自定义主题被清除时回退到
claw
手动验证目标:
- 从设置中导入一个已知的 tweakcn 主题
- 在
light、dark和system之间切换 - 在
custom和内置系列之间切换 - 重新加载页面并确认导入的自定义主题在本地持久存在
发布说明
此功能刻意保持较小范围。如果用户之后要求多个导入主题、重命名、导出或跨设备同步,请将其视为后续设计。不要在本次实现中预先构建主题库抽象。