Skip to content

建立统一的 Base16 外观主题系统 #584

Description

@RiriAgent

目标

建立一个同时覆盖 OpenAlice 前端界面、图表和 Workspace 终端的统一外观主题系统。该系统以 Base16 为最低兼容契约,支持 Base24 的终端扩展色,允许直接导入现有 scheme,并可选调用用户 PATH 中的 Matugen 或 Hellwal 从图片生成可预览、可保存、可复现的明暗主题。

本 issue 的完成态不是“终端能换 16 个颜色”,而是同一个主题来源经过明确的语义投影后驱动应用界面、第三方图表和 xterm.js;生成器差异止于输入适配层,不扩散成三套渲染逻辑。

上下文

  • Repo: TraderAlice/OpenAlice
  • 目标分支: dev
  • Baseline: TraderAlice/OpenAlice@65fd2ec0
  • 当前相关表面:
    • ui/src/index.css:内置浅色/深色 CSS token,同时仍存在固定颜色和组件级 dark: 分支;
    • ui/src/theme/:只持有 light | dark | auto,没有主题 family 身份;
    • ui/src/components/workspace/terminalThemeProfile.ts:终端使用两套硬编码 profile;
    • ui/src/components/market/KlinePanel.tsx:图表文字、网格、K 线和成交量颜色直接写在 JS 中;
    • src/workspaces/spawn-env.ts:PTY 对外声明 TERM=xterm-256colorCOLORTERM=truecolor
    • src/core/preferences.tssrc/webui/routes/preferences.ts:文件偏好和 HTTP 边界;
    • src/migrations/OPENALICE_HOME 持久化数据的迁移机制。

本设计来自以下明确需求:

“一个兼容Base16的主题系统,允许直接导入color scheme,并且程序可选检测PATH中是否存在Matugen和hellwal,如果任意一个存在则可以调用输入图片后生成主题,要可以选择浅色还是深色,如果是Matugen额外还可以方案选择,hellwal要支持offset,matugen要把md color映射到base16而不是使用wal后端,hellwal本身生成base16” — 本次设计请求

“前端的主题呢?” — 本次设计请求

“多的十六进制颜色,以及写死的颜色,到底能不能使用base16或者base16差值” — 本次设计请求

“K线颜色是否映射可以给个开关,或者重要的颜色” — 本次设计请求

“需要先有内置主题管理,要能接受常见的主题文件导入,并且能在设置选择,而这也是如何测试base16引入后正常渲染的重点” — 本次设计补充

问题

当前应用主题、终端主题和图表颜色彼此分离。应用只在 CSS 中维护两套内置配色;终端另有两套硬编码 profile;Canvas/SVG/第三方图表和若干状态组件继续直接使用十六进制、rgba()、固定 Tailwind 色阶和 dark: 分支。用户不能导入通用 scheme,也不能从同一图片生成前端和终端一致的主题。

直接把每个固定颜色替换为某个 baseXX 也不能解决问题。Base16 是基础色板,不是完整 UI token 集;背景层级、交互状态、透明强调、图表网格和安全状态必须经过确定且可测试的语义投影。K 线涨跌、盈亏、真实交易确认和破坏性操作还需要独立的辨识度策略,不能因一张图片的主色而失去含义。

预期结果

  1. 用户可以先在 Settings 管理内置和已导入主题;除 Base16/Base24 scheme 外,常见 ANSI16 终端主题文件也能导入并保留原色表。
  2. OpenAlice 检测其有效 PATH 中的 Matugen 和 Hellwal,展示绝对路径、版本和已验证能力;缺失任一工具不影响另一工具和普通导入。
  3. 用户选择图片、浅色/深色和生成器后可以预览结果;Matugen 额外选择 Material scheme,Hellwal 额外设置 dark/bright offset。
  4. Matugen 只消费 Material Dynamic Colors/tonal palettes,明确忽略其 Wal backend 产生的 base16 子树。
  5. Hellwal 的 ANSI 16 色 JSON 被完整保留用于终端,同时按本文规则投影出 Base16 语义色板;不得把 pywal 形状的 color0..color15 冒充原生 Base16。
  6. 同一个保存主题驱动应用 CSS token、运行时图表和 xterm.js。终端默认跟随应用 family,但允许独立覆盖。
  7. 系统/浅色/深色模式与主题 family 正交。只有同时具备 light/dark variant 的 family 才能启用“跟随系统”;单 variant 不静默借用或反转另一 variant。
  8. 市场方向色和普通状态色各有“高辨识度保护色/跟随主题”策略;涨跌习惯独立支持绿涨红跌和红涨绿跌。
  9. 已保存主题是文件状态,具备来源、生成参数和版本化映射信息;重启、开发服务器和打包 Electron 的首帧均不闪回内置配色。

主题契约

Scheme 与 family

一个主题 variant 至少包含:

  • 稳定 ID、显示名称和 light | dark variant;
  • 完整 base00..base0F
  • 可选 base10..base17
  • 可选精确 ANSI 0–15 override;
  • 来源、工具绝对路径、工具版本、输入图片 SHA-256、生成参数和映射版本;
  • 创建时间,但不把临时图片路径作为身份。

一个主题 family 包含明确的 light variant、dark variant 或两者。导入单个 scheme 时使用文件中的 variant;旧式 flat Base16 文件没有 variant 时,导入 UI 必须要求用户明确选择,禁止通过亮度猜测。生成页允许只生成当前 variant,也允许从同一图片和同一参数生成完整 light/dark family。

应用偏好至少表达:

  • active family;
  • system | light | dark 外观模式;
  • 终端是跟随应用还是使用独立 family/variant;
  • 市场颜色来源、涨跌习惯、普通状态颜色来源。

导入边界

必须接受并严格区分:

  1. 当前 Tinted Theming YAML:systemnameauthorvariantpalette
  2. 旧式 flat Base16 YAML:schemeauthorbase00..base0F
  3. Base24 YAML:system: base24base00..base17
  4. 上述 Base16/Base24 的等价 JSON。
  5. iTerm2 .itermcolors plist。
  6. Windows Terminal scheme JSON。
  7. Alacritty YAML/TOML theme。
  8. Kitty/Ghostty theme text。
  9. Xresources background/foreground/cursor/color0..color15 色表。

Base16/Base24 输入保留原始语义槽位。iTerm2、Windows Terminal、Alacritty、Kitty/Ghostty 和 Xresources 作为 ANSI16 输入,必须完整保留 foreground/background/cursor/selection 和 ANSI0–15 override,并复用本文 Hellwal ANSI16→Base16 投影;不同文件 adapter 不得各自发明映射。格式检测以内容 schema 为权威,扩展名只选择候选 parser;多个 parser 同时匹配时必须作为 ambiguous input 失败。VS Code/JetBrains 全量 UI theme、任意 CSS theme、脚本模板和 include 不在本 issue 范围内。

颜色在输入边界解析为规范的不透明 RGB 值。缺少必需槽位、重复/未知 system、非法 variant、非法颜色、错误数量或与声明 system 不符时,导入失败并指向具体字段;不得补默认值、吞掉未知结构或从文件名推断语义。导入先进入预览,用户确认后才持久化。

Base16/Base24 到终端

Base16 的 ANSI 0–15 映射固定为:

ANSI 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Base16 00 08 0B 0A 0D 0E 0C 05 03 08 0B 0A 0D 0E 0C 07

xterm.js extendedAnsi 的前六项固定为:ANSI 16=base09、17=base0F、18=base01、19=base02、20=base04、21=base06

Base24 提供独立 bright ANSI:9=base12、10=base14、11=base13、12=base16、13=base17、14=base15base10/base11 保留为额外背景层级。若 variant 携带精确 ANSI 0–15 override,则终端渲染优先使用该 override,Base16 仍用于前端、语义消费和导出。

终端前景、背景、光标、光标反色、选择背景和选择前景全部来自当前 variant;主题变化后现有终端实例立即更新,不能要求重建 Workspace 或丢失 PTY/scrollback。

Base16 到前端

基础投影固定为:

前端语义 Base 色
页面背景 base00
二级表面/侧栏 base01
卡片/悬浮表面 base02
边框/分隔线 base03
次要文字 base04
正文 base05
强调正文 base06
最高对比文字 base07
装饰性 danger base08
橙色提示 base09
warning base0A
装饰性 success base0B
info base0C
主 accent/focus base0D
次 accent base0E
特殊/弃用提示 base0F

hover、active、selection、focus ring、subtle surface、透明状态背景、图表网格和 overlay 由这些基础色确定性生成。颜色混合统一在 OKLab/OKLCH 中完成;已解析主题保存实际颜色值,不把浏览器相关的 color-mix() 字符串当持久化数据。中性背景和文字优先使用现成的 base00..base07 梯度,只有连续交互状态才做差值。

前景/背景、普通文本、交互控件和非文本图形必须满足适用的 WCAG 对比度要求。on-accent 从候选前景中选择满足要求的一项;没有候选能满足时主题不能保存为可用主题,错误必须指出失败的 token 对。

所有仍在运行路径中的固定 Tailwind 色阶、十六进制、rgba() 和组件级 dark: 分支都要分类迁移为:基础 token、派生 token、受保护语义色,或有明确理由的允许项。允许项仅包括内置主题源数据、测试 fixture/期望值、媒体内容、透明/物理阴影基元以及本文定义的受保护颜色;不能用 allowlist 隐藏未迁移组件。

图表与市场颜色

Canvas、SVG 和第三方图表必须消费同一 resolved theme,并在主题切换时更新现有实例。图表背景=base00,网格来自 base01/base02/base03,轴文字=base04,轴边框=base03,交互强调=base0D

市场颜色设置包含两个正交字段:

  • 来源:protected | theme
  • 习惯:green-up-red-down | red-up-green-down

protected 使用内置、分别适配 light/dark 且满足图形对比要求的方向色;theme 使用 base0Bbase08,再按涨跌习惯决定 positive/negative 绑定。K 线实体、边框、影线、成交量、盈亏数字和市场方向提示必须共用该绑定,不得各自写颜色。背景、网格和坐标无论选择哪种来源都跟随主题。

普通运行状态另有 protected | theme 策略,控制 success/warning/danger/info。不可逆删除、权限拒绝、真实交易确认、broker 写入失败和风控阻止始终使用受保护的危险语义;主题可以调整其表面亮度和透明度,但不能改变危险含义。任何关键状态都不能只靠颜色表达。

图片生成契约

能力检测

检测 OpenAlice 实际有效 PATH 中的 matugenhellwal,解析为绝对可执行路径并报告版本和能力。检测结果不是单一布尔值。设置页可手动刷新;工具不存在、版本无法识别或缺少所需 flag 时显示明确状态,不影响普通主题导入和另一生成器。

执行时使用已解析的绝对路径和 argv,不经 shell,不执行用户模板,不写 wallpaper/终端配置。图片只进入受控的本地工作目录;完成、失败或取消后清理。持久化只记录 digest 和生成参数,除非用户明确选择保留原图。

Matugen

支持 light | dark,并展示已安装版本实际支持的 scheme。当前目标集合为:

  • scheme-content
  • scheme-expressive
  • scheme-fidelity
  • scheme-fruit-salad
  • scheme-monochrome
  • scheme-neutral
  • scheme-rainbow
  • scheme-tonal-spot
  • scheme-vibrant
  • scheme-smart

调用必须使用 dry-run JSON 输出。解析 colors/palettes,忽略 Matugen JSON 的 base16 子树,不使用 --base16-backend,不消费 Wal 提取结果。

Material tonal palette 到 Base16 的 v1 映射固定如下:

Base Light Dark
00 neutral 98 neutral 10
01 neutral 95 neutral 15
02 neutral 90 neutral 20
03 neutral_variant 60 neutral_variant 60
04 neutral_variant 40 neutral_variant 70
05 neutral 20 neutral 90
06 neutral 10 neutral 95
07 neutral 0 neutral 100

强调色定义为 Light strong=40, alternate=30,Dark strong=80, alternate=70

Base Material tonal palette
08 error strong
09 tertiary alternate
0A secondary alternate
0B primary alternate
0C secondary strong
0D primary strong
0E tertiary strong
0F error alternate

该映射带版本号。未来修改映射必须产生新版本,不能让已保存主题在升级后静默变色。

Hellwal

支持 --dark/--light--dark-offset--bright-offset。两个 offset 都按 Hellwal 契约严格接受 0..1;越界、NaN 或非数字输入在启动进程前失败。

调用 JSON 模式时同时使用 --no-cache--skip-term-colors,确保只从 stdout 消费结果且不修改当前终端。严格解析 special.background/foreground/cursorcolors.color0..color15

ANSI 0–15 原值完整保存为 terminal override。Base16 投影固定为:

  • base00=background/color0base03=color8base05=foreground/color7base07=color15
  • base01base02base00→base03 间按 OKLab 三等分;
  • base04base03→base05 中点,base06base05→base07 中点;
  • base08=color1base09=color9base0A=color3base0B=color2base0C=color6base0D=color4base0E=color5base0F=color13

该投影明确是 ANSI 语义到 Base16 语义的有损归一化;终端本身仍使用完整原表,因此不能因归一化丢失 bright 色。

UI 与生命周期

第一条用户可见交付必须是内置/导入主题管理,而不是图片生成器。Settings 先能列出内置和已导入 family、导入上述常见文件、选择 family/mode、应用、取消、删除并跨重启保持;该路径必须驱动真实基础 app token 和真实 xterm ANSI 测试面,作为 Base16 引入后正常渲染的主要回归入口。Matugen/Hellwal 之后接入同一个 manager,不另建生成主题仓库或选择器。

设置页的外观区域至少提供:

  • family 选择、system/light/dark、删除和来源详情;
  • 导入 Base16/Base24 文件;
  • 选择图片和可用生成器;
  • 公共 light/dark 选择;
  • Matugen scheme;
  • Hellwal dark offset/bright offset;
  • 应用组件、文本层级、状态、K 线和 ANSI 色表的同屏预览;
  • 市场颜色来源、涨跌习惯、普通状态颜色来源;
  • 保存、应用和取消。

预览是隔离状态,取消不得污染 active theme。删除 active family 必须先切换到另一个 family;不允许在后台静默回落。生成进程可取消,失败时保留用户输入和诊断,不保存半成品。

主题保存在 OPENALICE_HOME 的文件状态中;active selection 走现有 preferences 和 migration 机制。内置 OpenAlice Light/Dark 作为不可删除的完整 family 参与同一投影,不再拥有旁路渲染逻辑。

首帧主题必须在 React 渲染前可用。开发、demo、浏览器和打包 Electron 都不能先绘制内置主题再异步闪到 active family;文件状态是权威,任何首帧缓存都必须带版本/身份并在状态变化时同步失效。

约束

  • 不改变 UTA、broker、账户和交易写入边界;主题是 Alice/UI 文件状态。
  • 不执行 shell 字符串,不信任 scheme 文件、图片路径或生成器 stdout。
  • 不允许宽松解析、未知字段猜测、缺色补默认值、单 variant 自动反色或生成失败后静默回退。
  • 不允许 Matugen/Hellwal 模板、副作用后端或用户配置写入。
  • 不把完整主题仅存在 localStorage;持久化 shape 变化必须使用 migration framework。
  • 保持 lite/read-only 模式可用;没有任何生成器时仍可使用内置主题和导入。
  • 终端继续声明 xterm-256color/truecolor;主题系统不伪装成 shell 全局主题管理器。
  • 保护用户现有主题文件;升级迁移幂等,删除必须是显式用户动作。
  • README 产品定位不在本 issue 范围内;若实现造成重大产品表述变化,另行确认文案。

不应残留

  • 前端和终端各自维护互不关联的 active theme 身份;
  • Matugen base16/Wal backend 被当作 OpenAlice 主题输入;
  • Hellwal color0..color15 被标称为原生 Base16;
  • 活跃组件中的主题相关固定 hex/rgba、固定 Tailwind 色阶和组件级 dark: 分支;
  • K 线、成交量、盈亏和买卖方向各自维护颜色常量;
  • 生成器通过 shell、模板或用户配置完成输出;
  • 删除/加载失败后无提示地换成默认主题;
  • 只在 mock/unit test 中证明工具集成,未运行真实 Matugen/Hellwal;
  • 只有 scheme parser/API,没有 Settings 内置/导入管理和真实 app/xterm 渲染证明;
  • 只验证浏览器开发模式,未验证打包 Electron 的 PATH 和首帧行为。

验收标准

# Dimension Check Command Env Expect
1 function 类型、解析、映射、派生色、offset、对比度、迁移和失败路径 npx tsc --noEmit && pnpm test && (cd ui && npx tsc -b) local 全部 exit 0;测试覆盖 Base16 common/legacy、Base24、Matugen fixture、Hellwal fixture、明暗 family、非法输入和幂等迁移
2 integration 主题文件、preferences、API、UI、图表和终端共同消费一个 active family pnpm test:e2e local exit 0;导入/生成/预览/保存/切换/重启/删除约束均通过,图表与现有 xterm 实例无需重建即可更新
3 environment demo API 和完整外观页不依赖真实 broker/UTA pnpm -F open-alice-ui dev:demo browser demo /settings 可完成内置/导入主题、明暗切换、市场策略和前端/图表/终端预览;无缺失 handler
4 environment Hellwal 真机能力检测和图片生成 hellwal --version && pnpm dev local,PATH 中安装 Hellwal 1.0.7 或兼容版本 UI 显示绝对路径/版本/能力;light/dark 和两种 offset 影响结果;保存后前端与终端一致;进程不写终端配置
5 environment Matugen 真机能力检测和 Material→Base16 matugen --version && pnpm dev local,PATH 中安装支持 dry-run JSON 的 Matugen UI 只展示实际支持 scheme;相同图片在 light/dark、至少两种 scheme 下产生不同且可复现结果;结果与 JSON palettes 映射一致且不读取 base16 子树
6 environment GUI/打包环境 PATH、持久化和首帧 CSC_IDENTITY_AUTO_DISCOVERY=false pnpm electron:smoke:workspace local packaged Electron exit 0;GUI 能检测 Homebrew/Cargo 等有效 PATH 中的工具;重启保持主题;启动无默认主题闪烁;smoke 自有包被正常清理
7 integration Base24/ANSI/extended ANSI pnpm test:e2e real xterm browser surface ANSI 0–15、bright 9–14 和 extended 16–21 的测试图与契约逐项一致;Hellwal 保留原始 ANSI16
8 integration 市场颜色策略和地区习惯 pnpm test:e2e browser protected/theme 与绿涨红跌/红涨绿跌四种组合同时更新 K 线、成交量、盈亏和方向提示;背景/网格始终跟随主题;关键状态不只靠颜色表达
9 function 活跃运行路径不再绕过主题 token `rg -n --glob '*.{ts,tsx,css}' '#[0-9a-fA-F]{3,8} rgba?( (?:text
10 function 完整仓库回归 pnpm build && pnpm test local exit 0;内置 Light/Dark 与现有默认外观等价,lite/read-only 启动不要求安装生成器
11 integration 内置/常见导入主题管理是首个 Base16 渲染基准 pnpm test:e2e Settings + real app/xterm browser surface 从 Settings 导入并选择 Base16/Base24、iTerm2、Windows Terminal、Alacritty、Kitty/Ghostty、Xresources fixtures;基础 app computed token 与 xterm ANSI/bright/extended 槽位逐项符合输入,reload 后保持且无首帧闪烁

依赖关系

  • Depends on: 无。
  • Blocks: 后续任何第三方 scheme catalog、主题分享/导出、壁纸自动同步和系统级主题监听能力。

本 issue 是完整的产品与验证契约。若实现需要拆成多个 PR,应先建立子 issue 并让每个 PR 只对应一个子 issue;子 issue 必须继承本文的映射、失败语义、安全颜色和父级验收义务。PR 合入非 default branch 时,Closes 只形成 cross-reference,必须在核对 PR merged/base 后手动关闭对应 issue;父 issue 仅在所有子 issue 完成且以上整体验收全部通过后关闭。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions