diff --git a/BitFun-Installer/src/styles/variables.css b/BitFun-Installer/src/styles/variables.css
index a6bac9df4d..cfd9e8f718 100644
--- a/BitFun-Installer/src/styles/variables.css
+++ b/BitFun-Installer/src/styles/variables.css
@@ -1,17 +1,10 @@
:root {
--color-bg-primary: #121214;
--color-bg-secondary: #1a1c1e;
- --color-bg-tertiary: var(--color-bg-primary);
- --color-bg-quaternary: var(--color-bg-secondary);
- --color-bg-elevated: var(--color-bg-secondary);
- --color-bg-workbench: var(--color-bg-primary);
- --color-bg-flowchat: var(--color-bg-primary);
- --color-bg-tooltip: rgba(30, 30, 32, 0.92);
--color-text-primary: #e8e8e8;
--color-text-secondary: #b0b0b0;
--color-text-muted: #858585;
- --color-text-disabled: #555555;
--color-accent-50: rgba(96, 165, 250, 0.04);
--color-accent-100: rgba(96, 165, 250, 0.08);
@@ -21,37 +14,20 @@
--color-accent-500: #60a5fa;
--color-accent-600: #3b82f6;
- --color-purple-50: var(--color-accent-50);
- --color-purple-100: var(--color-accent-100);
- --color-purple-200: var(--color-accent-200);
- --color-purple-400: var(--color-accent-400);
- --color-purple-500: var(--color-accent-500);
- --color-purple-600: var(--color-accent-600);
- --color-purple-800: var(--color-accent-600);
-
--color-success: #34d399;
--color-warning: #f59e0b;
--color-error: #ef4444;
- --color-info: var(--color-accent-500);
- --color-highlight: var(--color-warning);
- --color-highlight-bg: rgba(245, 158, 11, 0.15);
--element-bg-subtle: rgba(255, 255, 255, 0.06);
--element-bg-soft: rgba(255, 255, 255, 0.12);
- --element-bg-base: var(--element-bg-soft);
--element-bg-medium: rgba(255, 255, 255, 0.18);
--element-bg-strong: rgba(255, 255, 255, 0.24);
- --element-bg-elevated: var(--element-bg-strong);
--border-subtle: rgba(255, 255, 255, 0.12);
--border-base: rgba(255, 255, 255, 0.18);
--border-medium: rgba(255, 255, 255, 0.24);
- --border-strong: rgba(255, 255, 255, 0.32);
- --border-prominent: rgba(255, 255, 255, 0.45);
--size-radius-sm: 6px;
- --size-radius-base: 8px;
- --size-radius-lg: 12px;
--font-sans: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei',
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro Display',
diff --git a/BitFun-Installer/src/theme/installerThemeRuntime.ts b/BitFun-Installer/src/theme/installerThemeRuntime.ts
index 538954a5fa..c0f4e29def 100644
--- a/BitFun-Installer/src/theme/installerThemeRuntime.ts
+++ b/BitFun-Installer/src/theme/installerThemeRuntime.ts
@@ -4,7 +4,6 @@ import type { InstallerTheme } from './installerThemesData';
import { findInstallerThemeById } from './installerThemesData';
const ACCENT_STOPS = ['50', '100', '200', '300', '400', '500', '600'] as const;
-const SECONDARY_ACCENT_STOPS = ['50', '100', '200', '400', '500', '600', '800'] as const;
/** Same rule as main app `getSystemPreferredDefaultThemeId`: dark -> bitfun-dark, else bitfun-light. */
export function getSystemPreferredBuiltinThemeId(): ThemeId {
@@ -20,42 +19,24 @@ export function applyInstallerThemeToDocument(theme: InstallerTheme): void {
root.style.setProperty('--color-bg-primary', colors.background.primary);
root.style.setProperty('--color-bg-secondary', colors.background.secondary);
- root.style.setProperty('--color-bg-tertiary', colors.background.tertiary);
- root.style.setProperty('--color-bg-quaternary', colors.background.quaternary);
- root.style.setProperty('--color-bg-elevated', colors.background.elevated);
- root.style.setProperty('--color-bg-workbench', colors.background.workbench);
- root.style.setProperty('--color-bg-flowchat', colors.background.flowchat);
- root.style.setProperty('--color-bg-tooltip', colors.background.tooltip ?? colors.background.elevated);
root.style.setProperty('--color-text-primary', colors.text.primary);
root.style.setProperty('--color-text-secondary', colors.text.secondary);
root.style.setProperty('--color-text-muted', colors.text.muted);
- root.style.setProperty('--color-text-disabled', colors.text.disabled);
root.style.setProperty('--element-bg-subtle', colors.element.subtle);
root.style.setProperty('--element-bg-soft', colors.element.soft);
- root.style.setProperty('--element-bg-base', colors.element.base);
root.style.setProperty('--element-bg-medium', colors.element.medium);
root.style.setProperty('--element-bg-strong', colors.element.strong);
- root.style.setProperty('--element-bg-elevated', colors.element.elevated);
root.style.setProperty('--border-subtle', colors.border.subtle);
root.style.setProperty('--border-base', colors.border.base);
root.style.setProperty('--border-medium', colors.border.medium);
- root.style.setProperty('--border-strong', colors.border.strong);
- root.style.setProperty('--border-prominent', colors.border.prominent);
root.style.setProperty('--color-success', colors.semantic.success);
root.style.setProperty('--color-warning', colors.semantic.warning);
root.style.setProperty('--color-error', colors.semantic.error);
- root.style.setProperty('--color-info', colors.semantic.info);
- root.style.setProperty('--color-highlight', colors.semantic.highlight);
- root.style.setProperty('--color-highlight-bg', colors.semantic.highlightBg);
ACCENT_STOPS.forEach((key) => {
root.style.setProperty(`--color-accent-${key}`, colors.accent[key]);
});
- SECONDARY_ACCENT_STOPS.forEach((key) => {
- root.style.setProperty(`--color-purple-${key}`, colors.purple[key]);
- });
-
root.setAttribute('data-theme', theme.id);
root.setAttribute('data-theme-type', theme.type);
}
diff --git a/BitFun-Installer/src/theme/installerThemesData.ts b/BitFun-Installer/src/theme/installerThemesData.ts
index 4f85b85424..3773c78043 100644
--- a/BitFun-Installer/src/theme/installerThemesData.ts
+++ b/BitFun-Installer/src/theme/installerThemesData.ts
@@ -1,9 +1,7 @@
import type { ThemeId } from '../types/installer';
type AccentStop = '50' | '100' | '200' | '300' | '400' | '500' | '600';
-type SecondaryAccentStop = Exclude
| '800';
type AccentRamp = Record;
-type SecondaryAccentRamp = Record;
type RampAlphas = readonly [string, string, string, string, string];
type BackgroundColors = InstallerTheme['colors']['background'];
type TextColors = InstallerTheme['colors']['text'];
@@ -19,43 +17,28 @@ export type InstallerTheme = {
background: {
primary: string;
secondary: string;
- tertiary: string;
- quaternary: string;
- elevated: string;
- workbench: string;
- flowchat: string;
- tooltip: string;
};
text: {
primary: string;
secondary: string;
muted: string;
- disabled: string;
};
accent: AccentRamp;
- purple: SecondaryAccentRamp;
semantic: {
success: string;
warning: string;
error: string;
- info: string;
- highlight: string;
- highlightBg: string;
};
border: {
subtle: string;
base: string;
medium: string;
- strong: string;
- prominent: string;
};
element: {
subtle: string;
soft: string;
- base: string;
medium: string;
strong: string;
- elevated: string;
};
};
};
@@ -86,21 +69,9 @@ function createAccentRamp(
};
}
-function createSecondaryRampFromAccent(ramp: AccentRamp): SecondaryAccentRamp {
- return {
- '50': ramp['50'],
- '100': ramp['100'],
- '200': ramp['200'],
- '400': ramp['400'],
- '500': ramp['500'],
- '600': ramp['600'],
- '800': ramp['600'],
- };
-}
-
type TonePreset = {
text: TextColors;
- semantic: Omit;
+ semantic: SemanticColors;
border: BorderColors;
element: ElementColors;
};
@@ -112,8 +83,6 @@ type ThemeSeed = {
background: {
primary: string;
secondary?: string;
- tooltipRgb: string;
- tooltipAlpha?: string;
};
accentRgb: string;
accent500: string;
@@ -126,58 +95,46 @@ function createBackground(seed: ThemeSeed['background']): BackgroundColors {
return {
primary: seed.primary,
secondary,
- tertiary: seed.primary,
- quaternary: secondary,
- elevated: secondary,
- workbench: seed.primary,
- flowchat: seed.primary,
- tooltip: alpha(seed.tooltipRgb, seed.tooltipAlpha ?? '0.95'),
};
}
-function createBorderRamp(rgb: string, alphas: readonly [string, string, string, string, string]): BorderColors {
+function createBorderRamp(rgb: string, alphas: readonly [string, string, string]): BorderColors {
return {
subtle: alpha(rgb, alphas[0]),
base: alpha(rgb, alphas[1]),
medium: alpha(rgb, alphas[2]),
- strong: alpha(rgb, alphas[3]),
- prominent: alpha(rgb, alphas[4]),
};
}
-function createElementRamp(rgb: string, elevated: string): ElementColors {
+function createElementRamp(rgb: string): ElementColors {
return {
subtle: alpha(rgb, '0.06'),
soft: alpha(rgb, '0.12'),
- base: alpha(rgb, '0.12'),
medium: alpha(rgb, '0.18'),
strong: alpha(rgb, '0.24'),
- elevated,
};
}
const DARK_TONE: TonePreset = {
- text: { primary: '#e8e8e8', secondary: '#b0b0b0', muted: '#858585', disabled: '#555555' },
+ text: { primary: '#e8e8e8', secondary: '#b0b0b0', muted: '#858585' },
semantic: {
success: '#34d399',
warning: '#f59e0b',
error: '#ef4444',
- highlightBg: alpha('245, 158, 11', '0.15'),
},
- border: createBorderRamp('255, 255, 255', ['0.12', '0.18', '0.24', '0.32', '0.45']),
- element: createElementRamp('255, 255, 255', alpha('255, 255, 255', '0.24')),
+ border: createBorderRamp('255, 255, 255', ['0.12', '0.18', '0.24']),
+ element: createElementRamp('255, 255, 255'),
};
const LIGHT_TONE: TonePreset = {
- text: { primary: '#1e293b', secondary: '#3d4f66', muted: '#64748b', disabled: '#94a3b8' },
+ text: { primary: '#1e293b', secondary: '#3d4f66', muted: '#64748b' },
semantic: {
success: '#5b9a6f',
warning: '#c08c42',
error: '#c26565',
- highlightBg: alpha('192, 140, 66', '0.12'),
},
- border: createBorderRamp('100, 116, 139', ['0.15', '0.22', '0.32', '0.42', '0.52']),
- element: createElementRamp('71, 102, 143', alpha('255, 255, 255', '0.92')),
+ border: createBorderRamp('100, 116, 139', ['0.15', '0.22', '0.32']),
+ element: createElementRamp('71, 102, 143'),
};
function createInstallerTheme(seed: ThemeSeed): InstallerTheme {
@@ -192,14 +149,10 @@ function createInstallerTheme(seed: ThemeSeed): InstallerTheme {
background: createBackground(seed.background),
text: { ...tone.text },
accent,
- purple: createSecondaryRampFromAccent(accent),
semantic: {
success: tone.semantic.success,
warning: tone.semantic.warning,
error: tone.semantic.error,
- info: accent['500'],
- highlight: tone.semantic.warning,
- highlightBg: tone.semantic.highlightBg,
...seed.semantic,
},
border: { ...tone.border },
@@ -216,8 +169,6 @@ export const THEMES: InstallerTheme[] = [
background: {
primary: '#121214',
secondary: '#1a1c1e',
- tooltipRgb: '30, 30, 32',
- tooltipAlpha: '0.92',
},
accentRgb: DEFAULT_BLUE_RGB,
accent500: DEFAULT_BLUE_500,
@@ -227,7 +178,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-light',
name: 'Light',
type: 'light',
- background: { primary: '#f7f8fa', secondary: '#ffffff', tooltipRgb: '255, 255, 255', tooltipAlpha: '0.98' },
+ background: { primary: '#f7f8fa', secondary: '#ffffff' },
accentRgb: '71, 102, 143',
accent500: '#5a7bb2',
accent600: '#4a6694',
@@ -236,7 +187,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-midnight',
name: 'Midnight',
type: 'dark',
- background: { primary: '#2b2d30', secondary: '#1e1f22', tooltipRgb: '43, 45, 48', tooltipAlpha: '0.94' },
+ background: { primary: '#2b2d30', secondary: '#1e1f22' },
accentRgb: DEFAULT_BLUE_RGB,
accent500: DEFAULT_BLUE_500,
accent600: DEFAULT_BLUE_600,
@@ -250,7 +201,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-china-style',
name: 'Ink Charm',
type: 'light',
- background: { primary: '#faf8f0', secondary: '#f5f3e8', tooltipRgb: '250, 248, 240', tooltipAlpha: '0.96' },
+ background: { primary: '#faf8f0', secondary: '#f5f3e8' },
accentRgb: '46, 94, 138',
accent500: '#2e5e8a',
accent600: '#234a6d',
@@ -264,7 +215,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-china-night',
name: 'Ink Night',
type: 'dark',
- background: { primary: '#1a1814', secondary: '#212019', tooltipRgb: '26, 24, 20' },
+ background: { primary: '#1a1814', secondary: '#212019' },
accentRgb: '115, 165, 204',
accent500: '#73a5cc',
accent600: '#5a8bb3',
@@ -278,7 +229,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-cyber',
name: 'Cyber',
type: 'dark',
- background: { primary: '#0e0e10', secondary: '#151515', tooltipRgb: '14, 14, 16' },
+ background: { primary: '#0e0e10', secondary: '#151515' },
accentRgb: '0, 230, 255',
accent500: '#00e6ff',
accent600: '#00ccff',
@@ -292,7 +243,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-tokyo-night',
name: 'Tokyo Night',
type: 'dark',
- background: { primary: '#1a1b26', secondary: '#16161e', tooltipRgb: '22, 22, 30', tooltipAlpha: '0.94' },
+ background: { primary: '#1a1b26', secondary: '#16161e' },
accentRgb: '122, 162, 247',
accent500: '#7aa2f7',
accent600: '#6183bb',
@@ -306,7 +257,7 @@ export const THEMES: InstallerTheme[] = [
id: 'bitfun-slate',
name: 'Slate',
type: 'dark',
- background: { primary: '#1a1c1e', tooltipRgb: '42, 45, 48', tooltipAlpha: '0.96' },
+ background: { primary: '#1a1c1e' },
accentRgb: '122, 176, 238',
accent500: '#7ab0ee',
accent600: '#689ad8',
diff --git a/docs/architecture/theme-token-optimization.md b/docs/architecture/theme-token-optimization.md
index 11ca0a987d..75027e1940 100644
--- a/docs/architecture/theme-token-optimization.md
+++ b/docs/architecture/theme-token-optimization.md
@@ -174,7 +174,7 @@ prompt snapshot 投影;mobile-web 和 installer 颜色在各自 TS root 中维
| 忽略的测试文件数 | 224 |
| 忽略的构建生成文件数 | 1 |
| 包含颜色字面量的文件数 | 25 |
-| 颜色字面量出现次数 | 601 |
+| 颜色字面量出现次数 | 599 |
| 唯一颜色字面量数量 | 395 |
| 组件或非 token 文件中的颜色出现次数 | 0 |
| 组件或非 token 唯一颜色数量 | 0 |
@@ -193,9 +193,9 @@ prompt snapshot 投影;mobile-web 和 installer 颜色在各自 TS root 中维
| root | 颜色出现次数 | 唯一颜色 | App UI raw | fallback var | unresolved / non-contract key | dynamic family | 说明 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
-| `src/web-ui/src` | 601 | 395 | 0 | 0 | 0 | 13 | 主应用完整主题、专用 palette、widget payload 和 editor/terminal/Mermaid 域;UI exception 与 boundary fallback 已收敛到小型语义 palette。 |
+| `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/mobile-web/src` | 35 | 30 | 0 | 0 | 0 | 3 | mobile-web 图片缩略图关闭按钮已读取 `--color-static-white`,普通 app UI raw 归零。 |
-| `BitFun-Installer/src` | 88 | 72 | 0 | 0 | 0 | 2 | 安装器主题数据保留命名深色主题的可识别背景 seed,仅压缩低风险的重复 accent/tone 派生;runtime 保留实际消费的 accent/purple 色阶和语义状态反馈。 |
+| `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 等完整主题面。 |
CLI/TUI 使用独立审计,不参与 CSS var root 计数:
@@ -273,7 +273,7 @@ PR 通过而放宽 `appUi`、fallback、unresolved、non-contract 或 dynamic fa
| 区域 | 当前出现次数 | 当前唯一色数 | 说明 |
| --- | ---: | ---: | --- |
| Theme presets | 171 | 120 | 主题个性与 palette 映射;跨主题深色 neutral、弱文本、非状态浅色背景和同概念 success 色已收敛;相邻 surface、主题识别主背景、状态色和 editor lineHighlight 继续保留 |
-| Token contracts | 115 | 101 | `tokens.scss` 等静态契约根;黑白 overlay alpha stop 继续保留相邻状态层级,不按数值相近强行合并 |
+| Token contracts | 113 | 101 | `tokens.scss` 等静态契约根;黑白 overlay alpha stop 继续保留相邻状态层级,不按数值相近强行合并 |
| 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 状态层级漂移 |
@@ -304,7 +304,7 @@ theme preset 或 boundary fallback 误算成普通 app UI 债务。当前队列
| 文件 | 颜色出现次数 | 后续处理策略 |
| --- | ---: | --- |
-| `src/web-ui/src/component-library/styles/tokens.scss` | 102 | 根 token 契约;优先处理同语义 alias,避免把状态/层级 ramp 按数值强合并 |
+| `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 |
@@ -395,7 +395,7 @@ Phase 5 决策记录:
| installer preset seed compression | derive within installer namespace | `BitFun-Installer/src/theme/installerThemesData.ts`、`BitFun-Installer/src/styles/variables.css` | 安装器是简单首启/安装 surface,不应维护完整 app 级主题色板;8 套 installer 主题压缩为少量 background/accent/status seed 加 dark/light tone,theme preset 色值 160 -> 63、preset 唯一色 121 -> 60,静态变量同步用 alias 派生而不是复制色值;保留每个主题的 success/warning/error seed,避免安装进度、完成和错误反馈与最终主题状态色脱节 |
| generated widget payload shell compression | move host-internal keys to iframe shell | `src/web-ui/src/tools/generative-widget/themePayload.ts`、`GenerativeWidgetFrame.tsx` | host payload allowlist 153 -> 98;只保留外部 widget 真正需要随主题变化的 canonical 子集和 `components.button` 投影,tool-card/layout/旧 overlay 与 legacy alias 由 iframe fallback/static shell 派生,避免把宿主内部 key 变成插件或主题扩展 API;accent/status/border/radius/spacing/button 等主题敏感 key 必须继续由宿主 payload 覆盖,不能固化在 iframe shell |
| slate success and CLI added diff near merge | merge non-adjacent equivalent concepts | `src/web-ui/src/infrastructure/theme/presets/slate-theme.ts`、`src/apps/cli/themes/presets/bitfun-ink-night.json`、`bitfun-tokyo-night.json` | Slate success 复用已有 success green;CLI ink/tokyo added diff body 复用同一深绿非相邻 surface。保留 Cyber element/border、diff body/gutter 等相邻区分,web theme preset near 11 -> 10,CLI preset near 5 -> 4 |
-| installer named dark background seed | preserve adjacent preview identity | `BitFun-Installer/src/theme/installerThemesData.ts` | 复审后保留 Ink Night、Cyber、Tokyo Night、Slate 的 primary/secondary/tooltip seed;这些色值虽然接近,但在 ThemeSetup 相邻预览中承担主题识别,不作为本轮可合并收益。Installer 仅保留低风险 accent/tone 派生压缩,preset 唯一色 73 -> 72,near pair 保持 11 |
+| installer named dark background seed | preserve adjacent preview identity | `BitFun-Installer/src/theme/installerThemesData.ts` | 复审后保留 Ink Night、Cyber、Tokyo Night、Slate 的 primary/secondary seed;这些色值虽然接近,但在 ThemeSetup 相邻预览中承担主题识别,不作为本轮可合并收益。Installer 不再保留未读取的 tooltip seed,near pair 保持 11 |
| web overlay alpha ladder | preserve adjacent state stops | `tokens.scss`、`ThemeService.ts`、`themePayload.ts` | 复审后保留 `0.04/0.06/0.08/0.10/0.12/0.15/0.20` 等黑白 overlay stop;这些 key 被 card、markdown、shadow 等相邻状态消费,不能为了清零 near pair 折叠 hover/active/elevated 区分。生成式 widget host payload 仍不转发静态 overlay,由 iframe fallback/static shell 派生 |
| generated widget static overlay payload | move to iframe shell | `src/web-ui/src/tools/generative-widget/themePayload.ts`、`themePayload.test.ts` | 静态黑白 overlay 不再由 host payload 转发,iframe fallback/static shell 直接派生;payload allowlist 98 -> 94,历史低阶 overlay alias 继续通过 iframe fallback 映射,不影响已生成 widget |
| mobile static overlay foreground | tokenized static color | `src/mobile-web/src/theme/presets/shared.ts`、`chat-input.scss`、`light.ts` | 图片缩略图关闭按钮必须在黑色 overlay 上保持白色前景;新增 mobile `--color-static-white` 后组件 raw `#fff` 归零,light secondary 改读该静态 token,mobile 总颜色出现次数不增加且唯一色 31 -> 30 |
@@ -403,6 +403,8 @@ Phase 5 决策记录:
| remaining near pairs | none in ordinary components | 无 | 审计口径下普通组件 near pair 已清零;后续只在专用 palette 自身重设计时处理 Monaco/terminal/Mermaid/syntax 内部近似色 |
| Monaco theme palette | classify as exception | `tools/editor/themes/bitfun-dark.theme.ts` | 该文件是 Monaco theme 完整色板,不是普通 app UI;归入 editor/exception 后不再被误计为 component raw color |
| Flow Chat capture fallback | boundary fallback | `ExportImageButton.tsx`、`captureElementToDownloadsPng.tsx` -> `themeBoundaryFallbacks.ts` | `#121214` 只在 root theme 变量不可用时兜底截图背景,集中 owner 后避免截图工具重复携带 raw fallback |
+| git runtime alias surface | retire unused keys | `tokens.scss`、`ThemeService.ts` | `--git-color-pull*`、`--git-color-push*`、`--git-color-branch-border`、`--git-color-added-border`、`--git-color-changes-border`、`--git-color-changes-bg-hover` 和 `--git-color-deleted-border` 没有产品读取,且分别只是 branch/staged/status 派生同义 key;删除后实际渲染仍读取 canonical branch、staged、added、changes、deleted key,web-ui 定义 key 956 -> 945 |
+| installer minimal runtime projection | retire unused installer keys | `BitFun-Installer/src/theme/installerThemesData.ts`、`installerThemeRuntime.ts`、`variables.css`、`ThemeSetup.tsx` | 安装器是简单首启/安装 surface,不消费完整 app 主题 schema;删除未读取的 background tertiary/quaternary/elevated/workbench/flowchat/tooltip、text disabled、purple family、info/highlight、border strong/prominent、element base/elevated 和额外 radius key。ThemeSetup 预览仍保留 primary/secondary background、element soft、muted text 和 accent;安装器定义 key 54 -> 30,唯一色 72 -> 66 |
Phase 6 防回退约束:
@@ -440,7 +442,7 @@ Phase 6 防回退约束:
| `mobile-web` app UI raw color | 0 | 0 | mobile-web 组件不再允许 raw app color;图片缩略图黑色 overlay 上的关闭按钮前景读取 `--color-static-white` |
| `mobile-web` dynamic families | 3 | 3 | `--color-accent-*`、`--color-purple-*`、`--color-pink-*` 由 mobile preset 拥有 |
| `installer` app UI raw color | 0 | 0 | 安装器组件不得携带 raw app color |
-| `installer` dynamic families | 2 | 2 | 安装器只导出实际消费的 accent/purple family,不保留未使用高阶 key |
+| `installer` dynamic families | 1 | 1 | 安装器只导出实际消费的 accent family;secondary purple family 不再作为安装器扩展面 |
| `cli` total unique colors | 152 | 152 | 控制 CLI/TUI preset 和 Rust fallback palette 不继续膨胀 |
| `cli` runtime unique colors | 109 | 109 | 只统计 BitFun CLI renderer 当前消费的 preset key 加 Rust fallback palette,避免 OpenCode 兼容声明误导实际运行时预算 |
| `cli` runtime preset unique colors | 79 | 79 | 控制 CLI 当前消费的 preset key,不包含 markdown/syntax/diff line-number background 等未消费兼容字段 |
diff --git a/scripts/theme-color-governance-baseline.installer.json b/scripts/theme-color-governance-baseline.installer.json
index 4113ee319f..4df99a0a7b 100644
--- a/scripts/theme-color-governance-baseline.installer.json
+++ b/scripts/theme-color-governance-baseline.installer.json
@@ -96,16 +96,16 @@
"max": 0
},
"colorDomainScopes.themePreset.occurrences": {
- "max": 61
+ "max": 59
},
"colorDomainScopes.themePreset.uniqueColors": {
- "max": 59
+ "max": 57
},
"colorDomainScopes.tokenContract.occurrences": {
- "max": 27
+ "max": 22
},
"colorDomainScopes.tokenContract.uniqueColors": {
- "max": 24
+ "max": 19
},
"colorDomainScopes.appUi.occurrences": {
"max": 0
diff --git a/scripts/theme-color-governance-baseline.json b/scripts/theme-color-governance-baseline.json
index bbaf78f1fa..0f7ad977a2 100644
--- a/scripts/theme-color-governance-baseline.json
+++ b/scripts/theme-color-governance-baseline.json
@@ -249,7 +249,7 @@
"max": 30
},
"colorDomainScopes.tokenContract.occurrences": {
- "max": 115
+ "max": 113
},
"colorDomainScopes.generatedWidget.occurrences": {
"max": 0
diff --git a/scripts/theme-css-var-contract.mjs b/scripts/theme-css-var-contract.mjs
index dd21c76c73..497017c87d 100644
--- a/scripts/theme-css-var-contract.mjs
+++ b/scripts/theme-css-var-contract.mjs
@@ -304,8 +304,8 @@ export const DYNAMIC_VAR_FAMILY_CONTRACTS = [
},
{
prefix: '--color-purple-',
- owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts; src/mobile-web/src/theme/presets; BitFun-Installer/src/theme',
- reason: 'Theme runtime, mobile presets, and installer theme data export the secondary purple palette scale by numeric stop.',
+ owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts; src/mobile-web/src/theme/presets',
+ reason: 'Theme runtime and mobile presets export the secondary purple palette scale by numeric stop; installer keeps only the accent family it renders.',
},
{
prefix: '--color-pink-',
diff --git a/scripts/theme-visual-governance-contract.json b/scripts/theme-visual-governance-contract.json
index 8c3e51e067..cf507550d2 100644
--- a/scripts/theme-visual-governance-contract.json
+++ b/scripts/theme-visual-governance-contract.json
@@ -213,6 +213,30 @@
"Mobile web is a separate build target and may not exercise desktop-only theme runtime paths.",
"Narrow form factor can surface contrast and spacing regressions that desktop screenshots miss."
]
+ },
+ {
+ "key": "installer-shell",
+ "owner": "BitFun-Installer/src/theme; BitFun-Installer/src/pages; BitFun-Installer/src/styles",
+ "platforms": ["installer"],
+ "formFactors": ["desktop", "narrow"],
+ "themes": ["dark", "light", "system"],
+ "states": ["theme-setup", "language", "options", "loading", "progress", "error", "completed", "hover", "focus"],
+ "tokenFamilies": ["--color-bg-*", "--color-text-*", "--element-bg-*", "--border-*", "--color-accent-*", "--color-success", "--color-warning", "--color-error"],
+ "evidence": [
+ {
+ "type": "theme-color-audit",
+ "command": "pnpm run theme:color-audit:installer",
+ "requirement": "Installer theme changes must keep app UI raw colors, unresolved vars, fallback vars, and unregistered dynamic families at zero."
+ },
+ {
+ "type": "focused-visual-review",
+ "requirement": "Review ThemeSetup cards plus language, options, progress, completed, and error states across light, dark, system, and named themes when installer tokens change."
+ }
+ ],
+ "risks": [
+ "Installer is a separate React/Tauri surface with its own TS theme root; Rust must not copy or extend the installer palette.",
+ "Theme cards use adjacent primary and secondary backgrounds for identity, so near colors cannot be merged without focused visual evidence."
+ ]
}
]
}
diff --git a/scripts/validate-theme-visual-contract.mjs b/scripts/validate-theme-visual-contract.mjs
index aeffa0c9df..1b28fe14c4 100644
--- a/scripts/validate-theme-visual-contract.mjs
+++ b/scripts/validate-theme-visual-contract.mjs
@@ -11,8 +11,9 @@ const REQUIRED_SURFACE_KEYS = [
'generated-widget',
'theme-settings',
'mobile-web-shell',
+ 'installer-shell',
];
-const ALLOWED_PLATFORMS = new Set(['desktop-webview', 'web', 'mobile-web', 'generated-widget']);
+const ALLOWED_PLATFORMS = new Set(['desktop-webview', 'web', 'mobile-web', 'generated-widget', 'installer']);
const ALLOWED_FORM_FACTORS = new Set(['desktop', 'narrow', 'mobile', 'iframe']);
const ALLOWED_THEMES = new Set(['dark', 'light', 'system']);
const ALLOWED_EVIDENCE_TYPES = new Set([
diff --git a/src/web-ui/src/component-library/styles/tokens.scss b/src/web-ui/src/component-library/styles/tokens.scss
index 9e0fdb9be6..652ebdbd43 100644
--- a/src/web-ui/src/component-library/styles/tokens.scss
+++ b/src/web-ui/src/component-library/styles/tokens.scss
@@ -120,12 +120,10 @@ $color-info-border: $overlay-white-24;
$git-color-branch: #a1a1aa;
$git-color-branch-bg: $overlay-white-06;
$git-color-branch-bg-hover: $overlay-white-12;
-$git-color-branch-border: $overlay-white-20;
$git-color-changes: $color-warning;
$git-color-changes-bg: rgba(245, 158, 11, 0.1);
$git-color-changes-bg-hover: rgba(245, 158, 11, 0.15);
-$git-color-changes-border: rgba(245, 158, 11, 0.3);
$git-color-staged: rgb(34, 197, 94);
$git-color-staged-bg: rgba(34, 197, 94, 0.1);
@@ -135,20 +133,10 @@ $git-color-staged-border: rgba(34, 197, 94, 0.3);
$git-color-added: $git-color-staged;
$git-color-added-bg: $git-color-staged-bg;
$git-color-added-bg-hover: $git-color-staged-bg-hover;
-$git-color-added-border: $git-color-staged-border;
$git-color-deleted: $color-error;
$git-color-deleted-bg: $color-error-bg;
$git-color-deleted-bg-hover: rgba(239, 68, 68, 0.15);
-$git-color-deleted-border: rgba(239, 68, 68, 0.3);
-
-$git-color-pull: $git-color-branch;
-$git-color-pull-bg: $git-color-branch-bg;
-$git-color-pull-bg-hover: $git-color-branch-bg-hover;
-
-$git-color-push: $git-color-staged;
-$git-color-push-bg: $git-color-staged-bg;
-$git-color-push-bg-hover: $git-color-staged-bg-hover;
// ==================== Border system ====================
$border-subtle: $overlay-white-12;
@@ -775,11 +763,8 @@ $badge-info-text: $color-info;
--git-color-branch: #{$git-color-branch};
--git-color-branch-bg: #{$git-color-branch-bg};
--git-color-branch-bg-hover: #{$git-color-branch-bg-hover};
- --git-color-branch-border: #{$git-color-branch-border};
--git-color-changes: #{$git-color-changes};
--git-color-changes-bg: #{$git-color-changes-bg};
- --git-color-changes-bg-hover: #{$git-color-changes-bg-hover};
- --git-color-changes-border: #{$git-color-changes-border};
--git-color-staged: #{$git-color-staged};
--git-color-staged-bg: #{$git-color-staged-bg};
--git-color-staged-bg-hover: #{$git-color-staged-bg-hover};
@@ -787,17 +772,9 @@ $badge-info-text: $color-info;
--git-color-added: #{$git-color-added};
--git-color-added-bg: #{$git-color-added-bg};
--git-color-added-bg-hover: #{$git-color-added-bg-hover};
- --git-color-added-border: #{$git-color-added-border};
--git-color-deleted: #{$git-color-deleted};
--git-color-deleted-bg: #{$git-color-deleted-bg};
--git-color-deleted-bg-hover: #{$git-color-deleted-bg-hover};
- --git-color-deleted-border: #{$git-color-deleted-border};
- --git-color-pull: #{$git-color-pull};
- --git-color-pull-bg: #{$git-color-pull-bg};
- --git-color-pull-bg-hover: #{$git-color-pull-bg-hover};
- --git-color-push: #{$git-color-push};
- --git-color-push-bg: #{$git-color-push-bg};
- --git-color-push-bg-hover: #{$git-color-push-bg-hover};
--border-subtle: #{$border-subtle};
--border-base: #{$border-base};
diff --git a/src/web-ui/src/infrastructure/theme/core/ThemeService.test.ts b/src/web-ui/src/infrastructure/theme/core/ThemeService.test.ts
index 8daf528890..44f53e13b3 100644
--- a/src/web-ui/src/infrastructure/theme/core/ThemeService.test.ts
+++ b/src/web-ui/src/infrastructure/theme/core/ThemeService.test.ts
@@ -124,7 +124,7 @@ describe('ThemeService runtime theme tokens', () => {
expect(document.documentElement.style.getPropertyValue('--color-info-border')).toBe('rgba(255, 255, 255, 0.24)');
});
- it('exports the complete git runtime token family from the resolved theme', async () => {
+ it('exports the consumed git runtime token family from the resolved theme', async () => {
const service = new ThemeService();
await service.applyTheme('bitfun-dark');
@@ -133,24 +133,15 @@ describe('ThemeService runtime theme tokens', () => {
expect(rootStyle.getPropertyValue('--git-color-branch')).toBe('#a1a1aa');
expect(rootStyle.getPropertyValue('--git-color-branch-bg')).toBe('rgba(255, 255, 255, 0.06)');
expect(rootStyle.getPropertyValue('--git-color-branch-bg-hover')).toBe('rgba(255, 255, 255, 0.12)');
- expect(rootStyle.getPropertyValue('--git-color-branch-border')).toBe('rgba(255, 255, 255, 0.18)');
expect(rootStyle.getPropertyValue('--git-color-added')).toBe('rgb(34, 197, 94)');
expect(rootStyle.getPropertyValue('--git-color-added-bg')).toBe('rgba(34, 197, 94, 0.1)');
expect(rootStyle.getPropertyValue('--git-color-added-bg-hover')).toBe('rgba(34, 197, 94, 0.15)');
- expect(rootStyle.getPropertyValue('--git-color-added-border')).toBe('rgba(34, 197, 94, 0.3)');
expect(rootStyle.getPropertyValue('--git-color-changes-bg')).toBe('rgba(245, 158, 11, 0.1)');
- expect(rootStyle.getPropertyValue('--git-color-changes-bg-hover')).toBe('rgba(245, 158, 11, 0.15)');
- expect(rootStyle.getPropertyValue('--git-color-changes-border')).toBe('rgba(245, 158, 11, 0.3)');
expect(rootStyle.getPropertyValue('--git-color-deleted-bg-hover')).toBe('rgba(239, 68, 68, 0.15)');
- expect(rootStyle.getPropertyValue('--git-color-deleted-border')).toBe('rgba(239, 68, 68, 0.3)');
expect(rootStyle.getPropertyValue('--git-color-staged-bg-hover')).toBe('rgba(34, 197, 94, 0.15)');
expect(rootStyle.getPropertyValue('--git-color-staged-border')).toBe('rgba(34, 197, 94, 0.3)');
- expect(rootStyle.getPropertyValue('--git-color-pull')).toBe('#a1a1aa');
- expect(rootStyle.getPropertyValue('--git-color-pull-bg')).toBe('rgba(255, 255, 255, 0.06)');
- expect(rootStyle.getPropertyValue('--git-color-pull-bg-hover')).toBe('rgba(255, 255, 255, 0.12)');
- expect(rootStyle.getPropertyValue('--git-color-push')).toBe('rgb(34, 197, 94)');
- expect(rootStyle.getPropertyValue('--git-color-push-bg')).toBe('rgba(34, 197, 94, 0.1)');
- expect(rootStyle.getPropertyValue('--git-color-push-bg-hover')).toBe('rgba(34, 197, 94, 0.15)');
+ expect(rootStyle.getPropertyValue('--git-color-pull')).toBe('');
+ expect(rootStyle.getPropertyValue('--git-color-push')).toBe('');
});
it('uses canonical dark overlay stops when a theme omits scrollbar values', () => {
diff --git a/src/web-ui/src/infrastructure/theme/core/ThemeService.ts b/src/web-ui/src/infrastructure/theme/core/ThemeService.ts
index 592ea59c09..a22764d9d0 100644
--- a/src/web-ui/src/infrastructure/theme/core/ThemeService.ts
+++ b/src/web-ui/src/infrastructure/theme/core/ThemeService.ts
@@ -638,29 +638,18 @@ export class ThemeService {
root.style.setProperty('--git-color-branch', colors.git.branch);
root.style.setProperty('--git-color-branch-bg', colors.git.branchBg);
root.style.setProperty('--git-color-branch-bg-hover', colors.element.medium);
- root.style.setProperty('--git-color-branch-border', colors.border.base);
root.style.setProperty('--git-color-changes', colors.git.changes);
root.style.setProperty('--git-color-changes-bg', colors.git.changesBg);
- root.style.setProperty('--git-color-changes-bg-hover', colorWithAlpha(colors.git.changes, 0.15));
- root.style.setProperty('--git-color-changes-border', colorWithAlpha(colors.git.changes, 0.3));
root.style.setProperty('--git-color-added', colors.git.added);
root.style.setProperty('--git-color-added-bg', colors.git.addedBg);
root.style.setProperty('--git-color-added-bg-hover', colorWithAlpha(colors.git.added, 0.15));
- root.style.setProperty('--git-color-added-border', colorWithAlpha(colors.git.added, 0.3));
root.style.setProperty('--git-color-deleted', colors.git.deleted);
root.style.setProperty('--git-color-deleted-bg', colors.git.deletedBg);
root.style.setProperty('--git-color-deleted-bg-hover', colorWithAlpha(colors.git.deleted, 0.15));
- root.style.setProperty('--git-color-deleted-border', colorWithAlpha(colors.git.deleted, 0.3));
root.style.setProperty('--git-color-staged', colors.git.staged);
root.style.setProperty('--git-color-staged-bg', colors.git.stagedBg);
root.style.setProperty('--git-color-staged-bg-hover', colorWithAlpha(colors.git.staged, 0.15));
root.style.setProperty('--git-color-staged-border', colorWithAlpha(colors.git.staged, 0.3));
- root.style.setProperty('--git-color-pull', colors.git.branch);
- root.style.setProperty('--git-color-pull-bg', colors.git.branchBg);
- root.style.setProperty('--git-color-pull-bg-hover', colors.element.medium);
- root.style.setProperty('--git-color-push', colors.git.staged);
- root.style.setProperty('--git-color-push-bg', colors.git.stagedBg);
- root.style.setProperty('--git-color-push-bg-hover', colorWithAlpha(colors.git.staged, 0.15));