From 26b60279dc6c63e8a693a8bacfd1fdd03bef8f1e Mon Sep 17 00:00:00 2001 From: limityan Date: Fri, 3 Jul 2026 10:50:11 +0800 Subject: [PATCH] refactor(theme): trim unused installer and git token surface --- BitFun-Installer/src/pages/ThemeSetup.tsx | 2 +- BitFun-Installer/src/styles/variables.css | 24 ------ .../src/theme/installerThemeRuntime.ts | 19 ----- .../src/theme/installerThemesData.ts | 81 ++++--------------- docs/architecture/theme-token-optimization.md | 16 ++-- ...e-color-governance-baseline.installer.json | 8 +- scripts/theme-color-governance-baseline.json | 2 +- scripts/theme-css-var-contract.mjs | 4 +- scripts/theme-visual-governance-contract.json | 24 ++++++ scripts/validate-theme-visual-contract.mjs | 3 +- .../src/component-library/styles/tokens.scss | 23 ------ .../theme/core/ThemeService.test.ts | 15 +--- .../infrastructure/theme/core/ThemeService.ts | 11 --- 13 files changed, 62 insertions(+), 170 deletions(-) diff --git a/BitFun-Installer/src/pages/ThemeSetup.tsx b/BitFun-Installer/src/pages/ThemeSetup.tsx index af3ef32666..92ecae9a52 100644 --- a/BitFun-Installer/src/pages/ThemeSetup.tsx +++ b/BitFun-Installer/src/pages/ThemeSetup.tsx @@ -106,7 +106,7 @@ export function ThemeSetup({ options, setOptions, onLaunch, onClose }: ThemeSetu
-
+
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));