Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions docs/architecture/theme-token-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,18 @@ Mermaid、Monaco/editor、Prism syntax、terminal ANSI、language identity 和 U
registry。Mobile Web 和 Installer 已纳入同一审计口径,但各自使用独立 baseline,
避免移动端或安装器的独立 token 被误算为 web-ui 游离 key。

`395` 个 web-ui 唯一颜色是前端生产文件的全域审计数,不是普通 app UI 的色值预算。
`356` 个 web-ui 唯一颜色是前端生产文件的全域审计数,不是普通 app UI 的色值预算。
其中包含主题 preset、token contract、Mermaid、Monaco/editor、terminal、syntax、
language identity 和 UI exception 等专用 palette。真正需要继续压缩的是这些专用域
language identity 和 UI exception 等专用 palette。language identity 已收敛为 8 个大类身份色,
不再按每种语言或文件类型保留独立色值。真正需要继续压缩的是这些专用域
内部能被证明等价的近似色,而不是把它们直接并入普通 app semantic token。前后端职责边界
已经收敛为:web-ui/TS 侧维护完整主题源;Rust/desktop 侧只读取 TS 生成的首屏和
prompt snapshot 投影;mobile-web 和 installer 颜色在各自 TS root 中维护;CLI/TUI
颜色作为独立终端产品 surface 单独治理。

language/file identity 色只允许作为类别辅助 accent。消费方展示语言或文件身份时必须同时渲染
label、icon、扩展名或文件名之一,不允许构建只靠颜色区分语言或文件类型的 UI。

补充看 resolved theme 输出而不是只看源码字面量:相邻状态、主题个性和 elevation
强度不能只按数值近似强行合并。复审后暗色主题 `effects.shadow` 保留各 preset 的
原有 ramp;本轮只压缩非相邻、非语义的 surface/token 微差。
Expand All @@ -170,12 +174,12 @@ prompt snapshot 投影;mobile-web 和 installer 颜色在各自 TS root 中维

| 指标 | 当前基线 |
| --- | ---: |
| 扫描的生产前端文件数 | 1540 |
| 扫描的生产前端文件数 | 1542 |
| 忽略的测试文件数 | 224 |
| 忽略的构建生成文件数 | 1 |
| 包含颜色字面量的文件数 | 25 |
| 颜色字面量出现次数 | 599 |
| 唯一颜色字面量数量 | 395 |
| 颜色字面量出现次数 | 553 |
| 唯一颜色字面量数量 | 356 |
| 组件或非 token 文件中的颜色出现次数 | 0 |
| 组件或非 token 唯一颜色数量 | 0 |
| App UI 颜色出现次数 | 0 |
Expand All @@ -193,7 +197,7 @@ prompt snapshot 投影;mobile-web 和 installer 颜色在各自 TS root 中维

| root | 颜色出现次数 | 唯一颜色 | App UI raw | fallback var | unresolved / non-contract key | dynamic family | 说明 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| `src/web-ui/src` | 599 | 395 | 0 | 0 | 0 | 13 | 主应用完整主题、专用 palette、widget payload 和 editor/terminal/Mermaid 域;UI exception 与 boundary fallback 已收敛到小型语义 palette,未读取的 git 同义 runtime/static key 已退役。 |
| `src/web-ui/src` | 553 | 356 | 0 | 0 | 0 | 13 | 主应用完整主题、专用 palette、widget payload 和 editor/terminal/Mermaid 域;UI exception、syntax、language identity 与 boundary fallback 已收敛到小型语义 palette,未读取的 git 同义 runtime/static key 已退役。 |
| `src/mobile-web/src` | 35 | 30 | 0 | 0 | 0 | 3 | mobile-web 图片缩略图关闭按钮已读取 `--color-static-white`,普通 app UI raw 归零。 |
| `BitFun-Installer/src` | 81 | 66 | 0 | 0 | 0 | 1 | 安装器主题数据保留主题卡可见的 primary/secondary background、accent、text 和状态反馈;runtime 只导出实际消费的 accent family、基础 text/border/element/status key,不复制主应用 purple/info/tooltip 等完整主题面。 |

Expand Down Expand Up @@ -277,14 +281,14 @@ PR 通过而放宽 `appUi`、fallback、unresolved、non-contract 或 dynamic fa
| Editor | 52 | 48 | Monaco/editor 专用域,不能直接泛化到 app token;被动 selection/word highlight 的重复弱高亮已收敛 |
| Mermaid | 91 | 73 | Mermaid 专用渲染域 |
| Theme runtime | 30 | 29 | `ThemeService.ts` 运行时注入;黑白 overlay alpha 与静态 token、payload shell 保持相同 stop,避免 early render 与 runtime 状态层级漂移 |
| Language identity | 52 | 50 | 语言身份色,已集中到 identity registry |
| Language identity | 8 | 8 | 语言/文件身份色,已收敛到 8 个大类色;具体识别继续由 key、label、icon 和扩展名承担 |
| Terminal | 37 | 29 | terminal/ANSI 专用域;工具命令空状态已复用 `--tool-command-empty-rgb`,不再保留独立 raw 色 |
| Boundary fallback | 18 | 18 | iframe/miniapp/截图兜底值,不作为普通 app token;generated widget 初始 CSS 需要覆盖 retired alias 的 canonical 目标 |
| Visual effects | 0 | 0 | StreamText/TextStroke raw literal 已迁出普通组件层 |
| UI exception registry | 17 | 17 | 已归档的 UI 例外色,包含 review team、agent capability、template context、insights 和 inspector 等固定身份色;非相邻同语义私有色已归并到小型 exception palette |
| Generated widget | 0 | 0 | 颜色默认值已迁到 boundary fallback registry |
| App UI | 0 | 0 | 普通 app/component raw color 已清零;后续新增必须先进入 token/exception 决策 |
| Syntax | 18 | 17 | Prism syntax palette,保留为专用渲染域 |
| Syntax | 16 | 16 | Prism syntax palette,已按 foreground、muted/comment、keyword、literal、function、markup 等角色收敛;light punctuation 与 dark tag/property 保留相邻代码可读性差异 |

专用域 near color pair 已单独进入证据队列,避免把 editor、terminal、Mermaid、
theme preset 或 boundary fallback 误算成普通 app UI 债务。当前队列不是自动合并指令,
Expand All @@ -306,12 +310,14 @@ theme preset 或 boundary fallback 误算成普通 app UI 债务。当前队列
| --- | ---: | --- |
| `src/web-ui/src/component-library/styles/tokens.scss` | 100 | 根 token 契约;优先处理同语义 alias,避免把状态/层级 ramp 按数值强合并 |
| `src/web-ui/src/tools/mermaid-editor/theme/mermaidThemeFallbacks.ts` | 64 | Mermaid 专用渲染兜底;需以节点、边、文本、错误态截图为依据 |
| `src/web-ui/src/shared/theme/languageIdentityAccents.ts` | 52 | 内置 language/file identity registry;调用方复用常量 |
| `src/web-ui/src/tools/editor/themes/bitfun-dark.theme.ts` | 46 | Monaco theme palette;不拆散到普通 app token |
| `src/web-ui/src/infrastructure/theme/core/ThemeService.ts` | 30 | 运行时注入;需保持 early render、system theme 和 payload 导出兼容 |
| `src/web-ui/src/shared/theme/uiExceptionAccents.ts` | 17 | 固定 UI 身份/角色色 registry;新增必须说明 owner/role |
| `src/web-ui/src/tools/terminal/utils/xtermTheme.ts` | 36 | terminal ANSI palette;不与 app semantic color 合并 |
| `src/web-ui/src/tools/mermaid-editor/theme/_tokens.scss` | 27 | Mermaid SCSS token 兜底;只在 Mermaid owner 内治理 |
| `src/web-ui/src/infrastructure/theme/presets/midnight-theme.ts` | 23 | theme preset palette;保留主题个性、状态色和 alpha ramp 边界 |
| `src/web-ui/src/infrastructure/theme/presets/slate-theme.ts` | 22 | theme preset palette;保留主题个性、状态色和 alpha ramp 边界 |
| `src/web-ui/src/shared/theme/themeBoundaryFallbacks.ts` | 18 | iframe/miniapp/截图兜底 palette;只作为边界 fallback 治理 |
| `src/web-ui/src/shared/theme/uiExceptionAccents.ts` | 17 | 固定 UI 身份/角色色 registry;新增必须说明 owner/role |

组件级 `var(--token, fallback)` 已收敛到 0;原先的 7 个 fallback token 不再需要
fallback contract registry 保留。
Expand Down Expand Up @@ -351,12 +357,13 @@ Phase 5 决策记录:
| `#ffdd00` -> `#ffd700` | merge | `StreamText.scss` fire yellow;editor/reference yellow | RGB distance = 6,均为亮黄强调色,调用点不相邻,不承担不同业务状态 |
| `#7dd3fc` -> `#7DCFFF` | merge | `GenerativeWidgetToolCard.scss`;`bitfun-dark.theme.ts` editor link | RGB distance = 5,均为非状态 sky/cyan 强调,调用点跨 surface 且不相邻 |
| `#00b4d8` -> `#00add8` | merge | `StreamText.scss` ocean mid;Go language identity | RGB distance = 7,同为 cyan/blue identity/visual-effect 色,非错误/警告/状态强度 |
| language identity palette | category-compress | `languageIdentityAccents.ts` 内置 language/file identity registry;`LanguageRegistry.ts` 和 code snippet metadata 读取 | 原 52 个出现、50 个唯一色改为 8 个大类色;保持所有 language/file key、label、icon、extension 和 helper API 不变,颜色不再承担唯一识别职责;消费方必须同时显示 label/icon/extension/file name 之一 |
| syntax palette roles | role-compress | `syntaxHighlightAccents.ts` Prism/Markdown/CodePreview palette | 浅色 `string/number` 共享 literal blue;light `punctuation` 独立于 comment,dark `tag/property` 保持区分,避免 12px code preview 和 JSX/HTML 相邻 token 可读性下降;syntax 18/17 -> 16/16 |
| Mermaid `#dfe2e8` -> `#e0e2e8` | merge | light `nodeFillHover`;dark `nodeText` | RGB distance = 1,跨 light/dark fallback 角色,不在同一主题视口中承担相邻状态区分;合并后保留 `nodeFillHover` 和 `nodeText` 语义 key |
| Mermaid `#5a5e68` -> `#5a5e6a` | merge | dark `edgeLabelBorderHover`;dark `nodeStroke`/`edgeStroke` | RGB distance = 2,均为深色 Mermaid neutral stroke/border;edge label hover border 不表达独立状态严重程度,合并后仍通过 key 区分角色 |
| Mermaid `#6a6e78` -> `#6a6e7a` | merge | dark `textMuted`;dark `nodeStrokeHover` | RGB distance = 2,均为深色 Mermaid subdued neutral;不是 success/warning/error 或数据类别色,合并后保留 muted text 与 hover stroke 语义 |
| Cyber `#141414` -> `#151515` | merge | `bitfun-cyber` scene background;secondary background;Monaco line highlight | RGB distance = 1.73,三者都在 Cyber 暗色 neutral surface 内,不表达不同状态、严重程度或数据类别;保留 `bgScene`/`secondary`/`lineHighlight` 语义 key,实际色值统一 |
| dark card white alpha literal | merge to canonical overlay stops | `ThemeService.ts` dark `--card-bg-*` runtime injection | `0.015/0.025/0.035/0.09/0.13` 仅是 dark card fallback 的微弱层级色,合并到现有 canonical overlay stop;复审后保留 default/elevated/subtle/hover/active 的相邻状态差异 |
| `#141414` vs `#121214` | preserve | `LanguageRegistry.ts` reStructuredText identity;Flow Chat capture/editor fallback | RGB distance = 2.83,但 `#141414` 是已存在的 language identity,迁移到 registry 时保持原值;`#121214` 仅作为截图/边界兜底 |
| `#f3f3f5` vs `#f4f4f5` | preserve | light theme primary background;dark theme status text | RGB distance = 1.41,但跨 light/dark theme 且角色不同;不通过数值相近抹平主题个性或状态文本对比 |
| `#b8c6ff` -> `#b8c4ff` | merge | Slate theme purple alpha ramp;Slate purple solid stop | RGB distance = 2,同一 Slate purple ramp 内肉眼不可区分;alpha ramp 继续保留独立语义 key,实际 RGB channel 收敛到 solid 500 stop |
| `#fafafa` -> `#ffffff` | merge | Dark theme primary button hover/active text | RGB distance = 8.66,均为同一 dark primary button 交互前景,不承担相邻区域或状态类别区分;默认态 `#f4f4f5` 保留以维持按钮交互层级 |
Expand Down Expand Up @@ -416,7 +423,9 @@ Phase 6 防回退约束:
| `colorDomainNearPairs.nearTotal` | 26 | 26 | 控制 theme preset/runtime/token/editor/Mermaid 等专用域 near 队列规模 |
| `colorScopes.appUi.uniqueColors` | 0 | 0 | 阻止普通组件 raw color 唯一色回涨 |
| `colorScopes.appUi.occurrences` | 0 | 0 | 阻止普通组件 raw color 出现次数回涨 |
| `colorScopes.exception.uniqueColors` | 229 | 229 | 控制专用域/例外域总体规模;UI exception 已收敛,但 Mermaid/editor/terminal/language identity 仍按各自 owner 单独治理 |
| `colorScopes.exception.uniqueColors` | 190 | 190 | 控制专用域/例外域总体规模;UI exception、syntax 和 language identity 已收敛,但 Mermaid/editor/terminal 仍按各自 owner 单独治理 |
| `colorDomainScopes.syntax.occurrences` | 16 | 16 | 阻止 Prism syntax palette 回到一 token class 一色的不可扩展模式,同时保留相邻 token 可读性边界 |
| `colorDomainScopes.languageIdentity.uniqueColors` | 8 | 8 | 阻止 language/file identity 回到一语言一色或一扩展一色的不可扩展模式 |
| `colorDomainScopes.boundaryFallback.occurrences` | 18 | 18 | 防止 iframe/mini app/截图兜底色重新散写;导出 key 可保留语义,实际字面值必须回到 boundary fallback palette |
| `tokenAliasLiterals.occurrences` | 0 | 0 | 阻止重新出现可映射到 token 的 app literal |
| `colorDomainScopes.appUi.occurrences` | 0 | 0 | 阻止未归类 app UI 色值回涨 |
Expand Down
8 changes: 4 additions & 4 deletions scripts/theme-color-governance-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"max": 205
},
"colorScopes.exception.uniqueColors": {
"max": 229
"max": 190
},
"cssVarDefinitions.unresolvedRequiredUnique": {
"max": 0
Expand Down Expand Up @@ -267,7 +267,7 @@
"max": 52
},
"colorDomainScopes.syntax.occurrences": {
"max": 18
"max": 16
},
"colorDomainScopes.terminal.occurrences": {
"max": 37
Expand All @@ -282,10 +282,10 @@
"max": 17
},
"colorDomainScopes.languageIdentity.occurrences": {
"max": 52
"max": 8
},
"colorDomainScopes.languageIdentity.uniqueColors": {
"max": 50
"max": 8
},
"colorDomainScopes.visualEffect.occurrences": {
"max": 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ describe('CodeSnippetContextImpl language metadata', () => {

it('keeps code snippet language accents centralized', () => {
expect(getLanguageColor('typescript')).toBe('#3178c6');
expect(getLanguageColor('rust')).toBe('var(--color-bg-primary)');
expect(getLanguageColor('java')).toBe('#007396');
expect(getLanguageColor('unknown-lang')).toBe('#858585');
expect(getLanguageColor()).toBe('#858585');
expect(getLanguageColor('rust')).toBe('#e38c00');
expect(getLanguageColor('java')).toBe('#e38c00');
expect(getLanguageColor('unknown-lang')).toBe('#64748b');
expect(getLanguageColor()).toBe('#64748b');
});

it('keeps mermaid diagram context color in the UI exception registry', () => {
Expand Down
117 changes: 64 additions & 53 deletions src/web-ui/src/shared/theme/languageIdentityAccents.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,79 @@
// Language identity colors are data semantics, not app theme surface colors.
// Keep this registry narrow and only add values that are consumed outside the
// full language registry.
const LANGUAGE_IDENTITY_PALETTE = {
blue: '#3178c6',
cyan: '#00add8',
yellow: '#f7df1e',
orange: '#e38c00',
red: '#ef4444',
green: '#22c55e',
purple: '#8b5cf6',
slate: '#64748b',
} as const;

export const BUILTIN_LANGUAGE_ACCENTS = {
typescript: '#3178c6',
typescriptReact: '#61dafb',
javascript: '#f7df1e',
javascriptReact: '#61dafb',
python: '#3776ab',
rust: '#ce422b',
go: '#00add8',
java: '#b07219',
kotlin: '#a97bff',
cpp: '#f34b7d',
c: '#555555',
csharp: '#178600',
swift: '#f05138',
php: '#4f5d95',
ruby: '#cc342d',
scala: '#c22d40',
dart: '#00b4ab',
lua: '#000080',
r: '#198ce7',
html: '#e34c26',
xml: '#0060ac',
vue: '#41b883',
svelte: '#ff3e00',
css: '#563d7c',
scss: '#c6538c',
sass: '#c6538c',
less: '#1d365d',
json: '#cbcb41',
yaml: '#cb171e',
toml: '#9c4121',
sql: '#e38c00',
graphql: '#e10098',
dockerfile: '#2496ed',
makefile: '#427819',
ini: '#d1dbe0',
env: '#ecd53f',
shell: '#89e051',
powershell: '#012456',
batch: '#c1f12e',
markdown: '#083fa1',
restructuredtext: '#141414',
image: '#a855f7',
audio: '#22c55e',
video: '#ef4444',
font: '#f59e0b',
archive: '#8b5cf6',
binary: '#64748b',
plaintext: '#6e7781',
typescript: LANGUAGE_IDENTITY_PALETTE.blue,
typescriptReact: LANGUAGE_IDENTITY_PALETTE.cyan,
javascript: LANGUAGE_IDENTITY_PALETTE.yellow,
javascriptReact: LANGUAGE_IDENTITY_PALETTE.cyan,
python: LANGUAGE_IDENTITY_PALETTE.blue,
rust: LANGUAGE_IDENTITY_PALETTE.orange,
go: LANGUAGE_IDENTITY_PALETTE.cyan,
java: LANGUAGE_IDENTITY_PALETTE.orange,
kotlin: LANGUAGE_IDENTITY_PALETTE.purple,
cpp: LANGUAGE_IDENTITY_PALETTE.red,
c: LANGUAGE_IDENTITY_PALETTE.slate,
csharp: LANGUAGE_IDENTITY_PALETTE.green,
swift: LANGUAGE_IDENTITY_PALETTE.red,
php: LANGUAGE_IDENTITY_PALETTE.purple,
ruby: LANGUAGE_IDENTITY_PALETTE.red,
scala: LANGUAGE_IDENTITY_PALETTE.red,
dart: LANGUAGE_IDENTITY_PALETTE.cyan,
lua: LANGUAGE_IDENTITY_PALETTE.blue,
r: LANGUAGE_IDENTITY_PALETTE.blue,
html: LANGUAGE_IDENTITY_PALETTE.red,
xml: LANGUAGE_IDENTITY_PALETTE.blue,
vue: LANGUAGE_IDENTITY_PALETTE.green,
svelte: LANGUAGE_IDENTITY_PALETTE.orange,
css: LANGUAGE_IDENTITY_PALETTE.blue,
scss: LANGUAGE_IDENTITY_PALETTE.purple,
sass: LANGUAGE_IDENTITY_PALETTE.purple,
less: LANGUAGE_IDENTITY_PALETTE.blue,
json: LANGUAGE_IDENTITY_PALETTE.yellow,
yaml: LANGUAGE_IDENTITY_PALETTE.red,
toml: LANGUAGE_IDENTITY_PALETTE.orange,
sql: LANGUAGE_IDENTITY_PALETTE.orange,
graphql: LANGUAGE_IDENTITY_PALETTE.purple,
dockerfile: LANGUAGE_IDENTITY_PALETTE.blue,
makefile: LANGUAGE_IDENTITY_PALETTE.green,
ini: LANGUAGE_IDENTITY_PALETTE.slate,
env: LANGUAGE_IDENTITY_PALETTE.yellow,
shell: LANGUAGE_IDENTITY_PALETTE.green,
powershell: LANGUAGE_IDENTITY_PALETTE.blue,
batch: LANGUAGE_IDENTITY_PALETTE.green,
markdown: LANGUAGE_IDENTITY_PALETTE.blue,
restructuredtext: LANGUAGE_IDENTITY_PALETTE.slate,
image: LANGUAGE_IDENTITY_PALETTE.purple,
audio: LANGUAGE_IDENTITY_PALETTE.green,
video: LANGUAGE_IDENTITY_PALETTE.red,
font: LANGUAGE_IDENTITY_PALETTE.orange,
archive: LANGUAGE_IDENTITY_PALETTE.purple,
binary: LANGUAGE_IDENTITY_PALETTE.slate,
plaintext: LANGUAGE_IDENTITY_PALETTE.slate,
} as const;

export const CODE_SNIPPET_LANGUAGE_ACCENTS = {
javascript: BUILTIN_LANGUAGE_ACCENTS.javascript,
typescript: BUILTIN_LANGUAGE_ACCENTS.typescript,
python: BUILTIN_LANGUAGE_ACCENTS.python,
rust: 'var(--color-bg-primary)',
rust: BUILTIN_LANGUAGE_ACCENTS.rust,
go: BUILTIN_LANGUAGE_ACCENTS.go,
java: '#007396',
java: BUILTIN_LANGUAGE_ACCENTS.java,
html: BUILTIN_LANGUAGE_ACCENTS.html,
css: '#1572b6',
scss: '#cc6699',
fallback: '#858585',
css: BUILTIN_LANGUAGE_ACCENTS.css,
scss: BUILTIN_LANGUAGE_ACCENTS.scss,
fallback: LANGUAGE_IDENTITY_PALETTE.slate,
} as const;

export function getCodeSnippetLanguageAccent(language?: string): string {
Expand Down
Loading
Loading