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
29 changes: 18 additions & 11 deletions docs/architecture/theme-token-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,14 @@ prompt snapshot 投影;CLI/TUI 颜色作为独立终端产品 surface 单独

| 治理契约指标 | 当前值 | 说明 |
| --- | ---: | --- |
| compatibility alias contracts | 0 | 显式 root/runtime 兼容别名已退役;历史 generated widget 内容通过 iframe fallback 兼容,尺寸旧 family family contract 单独治理 |
| compatibility alias contracts | 0 | 显式 root/runtime 兼容别名已退役;历史 generated widget 内容通过 iframe fallback 兼容,尺寸旧 family 仅由 iframe fallback 和 family contract 识别 |
| compatibility alias 直接使用 key | 0 | 产品代码不再通过 `var(--legacy-alias)` 读取旧 key;新增直接读取会被 baseline 拦截 |
| compatibility alias 直接使用次数 | 0 | 显式旧 key 不再在 root/runtime 定义,内部样式读取统一使用 canonical token |
| stale compatibility alias contracts | 0 | 防止 registry 重新保留没有必要的静态/runtime 兼容定义 |
| compatibility alias family contracts | 2 | `--radius-* -> --size-radius-*`、`--spacing-* -> --size-gap-*` |
| compatibility alias family 直接使用 key | 0 | `--radius-*`、`--spacing-*` 旧 family 不再被内部 `var()` 读取 |
| compatibility alias family 直接使用次数 | 0 | generated widget payload 只暴露 canonical family,iframe shell 通过 alias fallback 兼容 legacy family |
| stale compatibility alias family contracts | 0 | 防止动态 family canonical family 失配 |
| stale compatibility alias family contracts | 0 | 防止 family contract 指向缺失的 canonical family |
| missing compatibility alias family canonicals | 0 | 防止新增 `--radius-x` / `--spacing-x` 但缺少对应 canonical key |
| surface token rename contracts | 9 | 显式记录已迁移的 surface-local 旧 key、canonical 目标、owner 和命名边界 |
| active surface token rename key | 0 | 防止 `--primary-color`、`--operation-color`、`--delay`、`--um-*` 等旧局部 key 回流 |
Expand All @@ -188,10 +188,10 @@ prompt snapshot 投影;CLI/TUI 颜色作为独立终端产品 surface 单独

| 区域 | 当前出现次数 | 当前唯一色数 | 说明 |
| --- | ---: | ---: | --- |
| Theme presets | 190 | 162 | 主题个性与 palette 映射,不作为普通 app literal 直接合并;仅在同一主题内角色可证明等价时由 preset helper 复用 |
| Token contracts | 128 | 114 | `tokens.scss` 等静态契约根 |
| Theme presets | 189 | 161 | 主题个性与 palette 映射,不作为普通 app literal 直接合并;仅在同一主题内角色可证明等价时由 preset helper 复用 |
| Token contracts | 127 | 113 | `tokens.scss` 等静态契约根 |
| Editor | 55 | 48 | Monaco/editor 专用域,不能直接泛化到 app token;组件装饰色已迁出 raw literal |
| Mermaid | 93 | 74 | Mermaid 专用渲染域 |
| Mermaid | 91 | 73 | Mermaid 专用渲染域 |
| Theme runtime | 35 | 34 | `ThemeService.ts` 运行时注入;暗色 card alpha fallback 和 scrollbar fallback 已收敛到现有 overlay stop |
| Language identity | 52 | 50 | 语言身份色,已集中到 identity registry |
| Terminal | 37 | 29 | terminal/ANSI 专用域;工具命令空状态已复用 `--tool-command-empty-rgb`,不再保留独立 raw 色 |
Expand Down Expand Up @@ -220,14 +220,14 @@ theme preset 或 boundary fallback 误算成普通 app UI 债务。当前队列

| 文件 | 颜色出现次数 | 后续处理策略 |
| --- | ---: | --- |
| `src/web-ui/src/component-library/styles/tokens.scss` | 115 | 根 token 契约;优先处理同语义 alias,避免把状态/层级 ramp 按数值强合并 |
| `src/web-ui/src/component-library/styles/tokens.scss` | 114 | 根 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` | 35 | 运行时注入;需保持 early render、system theme 和 payload 导出兼容 |
| `src/web-ui/src/shared/theme/uiExceptionAccents.ts` | 38 | 固定 UI 身份/角色色 registry;新增必须说明 owner/role |
| `src/web-ui/src/tools/terminal/utils/xtermTheme.ts` | 36 | terminal ANSI palette;不与 app semantic color 合并 |
| `src/web-ui/src/infrastructure/theme/presets/slate-theme.ts` | 30 | theme preset palette;保留主题个性和 alpha ramp 边界 |
| `src/web-ui/src/infrastructure/theme/presets/slate-theme.ts` | 25 | theme preset palette;保留主题个性和 alpha ramp 边界 |

组件级 `var(--token, fallback)` 已收敛到 0;原先的 7 个 fallback token 不再需要
fallback contract registry 保留。
Expand Down Expand Up @@ -308,7 +308,7 @@ Phase 6 防回退约束:
| `compatibilityAliases.familyUsedUnique` | 0 | 0 | 阻止 `--radius-*`、`--spacing-*` 旧 family 重新成为内部读取面 |
| `compatibilityAliases.familyOccurrences` | 0 | 0 | 阻止旧 family 读取次数回涨 |
| `compatibilityAliases.staleRegisteredUnique` | 0 | 0 | 防止兼容 alias registry 保留没有定义或 canonical 目标缺失的 key |
| `compatibilityAliases.staleRegisteredFamilyUnique` | 0 | 0 | 防止 `--radius-*`、`--spacing-*` 这类动态 family canonical family 失配 |
| `compatibilityAliases.staleRegisteredFamilyUnique` | 0 | 0 | 防止 `--radius-*`、`--spacing-*` family contract 指向缺失的 canonical family |
| `compatibilityAliases.missingCanonicalUnique` | 0 | 0 | 防止 family alias 具体 key 缺失对应 canonical key |
| `surfaceTokenRenames.activeUnique` | 0 | 0 | 防止已迁移的 surface-local 旧 key 重新出现 |
| `surfaceTokenRenames.activeOccurrences` | 0 | 0 | 防止旧 key 在定义和读取两侧回流 |
Expand Down Expand Up @@ -541,8 +541,8 @@ semantic token 描述产品级语义,应作为共享 UI 的默认使用层。
| `--color-border-subtle` | `--border-subtle` | root/runtime alias 已退役;历史 generated widget 内容仅通过 iframe fallback 映射。 |
| `--color-danger` | `--color-error` | root/runtime alias 已退役;历史 generated widget 内容仅通过 iframe fallback 映射到 error palette。 |
| `--color-bg-hover` | `--element-bg-hover` | root/runtime alias 已退役;泛化 hover 收敛到 element interaction layer。 |
| `--radius-*` | `--size-radius-*` | canonical family 为 `--size-radius-*`旧 family 只作 legacy/widget iframe fallback 兼容。 |
| `--spacing-*` | `--size-gap-*` | canonical family 为 `--size-gap-*`旧 family 只作 legacy/widget iframe fallback 兼容。 |
| `--radius-*` | `--size-radius-*` | canonical family 为 `--size-radius-*`旧 family 已从 root/runtime 退役,仅作为 generated widget iframe fallback 兼容和内部回读拦截规则。 |
| `--spacing-*` | `--size-gap-*` | canonical family 为 `--size-gap-*`旧 family 已从 root/runtime 退役,仅作为 generated widget iframe fallback 兼容和内部回读拦截规则。 |

## 近似色合并规则

Expand Down Expand Up @@ -986,8 +986,15 @@ alpha 差异经常承担 elevation 和交互状态,不应全部压成一个值
stacking context,只通过 canonical color、spacing、shape、button 和 tool-card key 获取必要主题信息。
- 根 CSS var export 只保留被 `var()`、运行时主题注入或外部兼容边界实际消费的 key;组件内部仍可继续使用
SCSS token/mixin,避免把未消费的 badge、legacy effect、z-index、git/status 和局部布局 key 扩散为运行时 contract。
- `colors.purple` 是次级强调色契约,不再等同完整 `AccentColors`。运行时只导出
`50/100/200/400/500/600/800`,`300/700` 因无内部读取和 payload 消费已退役。
- ThemeService 的 runtime dynamic family 采用显式白名单导出,包括 accent、purple、shadow、
blur、radius、spacing、motion、easing、font weight、font size 和 line-height;custom theme
中的额外字段不得自动外溢为 root key。FontPreference 也不再生成 `5xl`。
- `effects.shadow` 运行时主题 scale 到 `xl` 为止;`$shadow-2xl` 仅作为 SCSS 编译期局部变量保留,
不再进入 runtime theme contract。
- 尺寸 canonical family 是 `--size-radius-*` / `--size-gap-*`;`--radius-*` /
`--spacing-*` 只作为 legacy source generated widget iframe alias fallback 保留。
`--spacing-*` 已从 root/runtime 和内部 source 退役,只作为 generated widget iframe alias fallback 保留。
- 迁移期 CI 采用严格 baseline:普通 app raw color、内部 compatibility alias 读取、fallback、
未定义 CSS var、payload 未定义 key、payload 缺失 canonical 和 payload 未导出 canonical 均为 0;
payload 兼容 alias 数量必须保持为 0,不能无依据增长。
Expand Down
2 changes: 1 addition & 1 deletion scripts/audit-theme-colors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ function audit(options) {
: [];
const staleCompatibilityAliasFamilyEntries = checksFullThemeSourceRoot
? compatibilityAliasFamilyEntries
.filter(entry => !entry.defined || !entry.canonicalDefined)
.filter(entry => !entry.canonicalDefined)
.map(entry => ({ key: entry.key, canonical: entry.canonical }))
: [];
const uncontractedFallbackVars = fallbackVars
Expand Down
4 changes: 2 additions & 2 deletions scripts/audit-theme-colors.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -891,8 +891,8 @@ test('theme color audit requires dynamic CSS var families to be registered', (t)
test('theme color audit accepts registered dynamic CSS var families', (t) => {
const { dir, sourceRoot } = createFixture({
'infrastructure/theme/core/ThemeService.ts': [
"for (const [key, value] of Object.entries(theme.effects.spacing)) {",
" document.documentElement.style.setProperty(`--spacing-${key}`, value);",
"for (const [key, value] of Object.entries(theme.motion.duration)) {",
" document.documentElement.style.setProperty(`--motion-${key}`, value);",
'}',
'',
].join('\n'),
Expand Down
14 changes: 7 additions & 7 deletions scripts/theme-color-governance-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@
"max": 0
},
"colorScopes.token.uniqueColors": {
"max": 257
"max": 255
},
"colorScopes.exception.uniqueColors": {
"max": 246
"max": 245
},
"cssVarDefinitions.unresolvedRequiredUnique": {
"max": 0
Expand Down Expand Up @@ -237,16 +237,16 @@
"max": 0
},
"colorDomainScopes.themePreset.occurrences": {
"max": 190
"max": 189
},
"colorDomainScopes.themePreset.uniqueColors": {
"max": 162
"max": 161
},
"colorDomainScopes.themeRuntime.occurrences": {
"max": 35
},
"colorDomainScopes.tokenContract.occurrences": {
"max": 128
"max": 127
},
"colorDomainScopes.generatedWidget.occurrences": {
"max": 0
Expand Down Expand Up @@ -294,10 +294,10 @@
"max": 0
},
"colorDomainScopes.mermaid.occurrences": {
"max": 93
"max": 91
},
"colorDomainScopes.mermaid.uniqueColors": {
"max": 74
"max": 73
}
}
}
24 changes: 6 additions & 18 deletions scripts/theme-css-var-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,16 @@ export const TOKEN_COMPATIBILITY_ALIAS_FAMILY_CONTRACTS = [
{
prefix: '--radius-',
canonicalPrefix: '--size-radius-',
owner: 'src/web-ui/src/component-library/styles/tokens.scss; src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
reason: 'Radius aliases keep older selectors and generated widget iframe fallback working while size-radius is the canonical shape scale.',
removal: 'Retire after all source and generated widget iframe consumers migrate to --size-radius-*.',
owner: 'src/web-ui/src/tools/generative-widget/themePayloadCompatibility.ts',
reason: 'Radius aliases are retired from root/runtime but remain recognized so old generated widget iframe content maps to the canonical shape scale.',
removal: 'Retire after generated widget iframe compatibility no longer needs --radius-* fallbacks.',
},
{
prefix: '--spacing-',
canonicalPrefix: '--size-gap-',
owner: 'src/web-ui/src/component-library/styles/tokens.scss; src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
reason: 'Spacing aliases keep older selectors and generated widget iframe fallback working while size-gap is the canonical spacing scale.',
removal: 'Retire after all source and generated widget iframe consumers migrate to --size-gap-*.',
owner: 'src/web-ui/src/tools/generative-widget/themePayloadCompatibility.ts',
reason: 'Spacing aliases are retired from root/runtime but remain recognized so old generated widget iframe content maps to the canonical spacing scale.',
removal: 'Retire after generated widget iframe compatibility no longer needs --spacing-* fallbacks.',
},
];

Expand Down Expand Up @@ -335,12 +335,6 @@ export const DYNAMIC_VAR_FAMILY_CONTRACTS = [
owner: 'src/web-ui/src/infrastructure/font-preference/core/FontPreferenceService.ts',
reason: 'Font preference runtime exports navigation font-size aliases from the adjusted typography scale.',
},
{
prefix: '--radius-',
owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
canonicalPrefix: '--size-radius-',
reason: 'Theme runtime exports configurable radius entries.',
},
{
prefix: '--shadow-',
owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
Expand All @@ -356,12 +350,6 @@ export const DYNAMIC_VAR_FAMILY_CONTRACTS = [
owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
reason: 'Size radius aliases are derived from theme radius entries.',
},
{
prefix: '--spacing-',
owner: 'src/web-ui/src/infrastructure/theme/core/ThemeService.ts',
canonicalPrefix: '--size-gap-',
reason: 'Theme runtime exports configurable spacing entries.',
},
];

export const REGISTERED_DYNAMIC_VAR_PREFIXES = new Set(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
--font-size-2xl: var(--nav-font-size-2xl);
--font-size-3xl: var(--nav-font-size-3xl);
--font-size-4xl: var(--nav-font-size-4xl);
--font-size-5xl: var(--nav-font-size-5xl);
}
33 changes: 0 additions & 33 deletions src/web-ui/src/component-library/styles/tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ $color-purple-300: $color-purple-500-a25;
$color-purple-400: $color-purple-500-a40;
$color-purple-500: #8b5cf6;
$color-purple-600: #7c3aed;
$color-purple-700: rgba(124, 58, 237, 0.8);
$color-purple-800: rgba(124, 58, 237, 0.9);

// ==================== Semantic colors ====================
Expand Down Expand Up @@ -175,9 +174,6 @@ $border-accent-subtle: $color-blue-600-a15;
$border-accent: $color-blue-600-a40;
$border-accent-strong: rgba(59, 130, 246, 0.6);

$border-purple-subtle: $color-purple-500-a15;
$border-purple: $color-purple-500-a40;

// ==================== Shadow system ====================
$shadow-xs: 0 1px 2px $overlay-black-90;
$shadow-sm: 0 2px 4px $overlay-black-80;
Expand Down Expand Up @@ -360,7 +356,6 @@ $font-size-xl: 16px;
$font-size-2xl: 18px;
$font-size-3xl: 22px;
$font-size-4xl: 26px;
$font-size-5xl: 32px;

$line-height-tight: 1.2;
$line-height-base: 1.5;
Expand All @@ -384,7 +379,6 @@ $sidebar-width: 240px;
$opacity-disabled: 0.6;
$opacity-hover: 0.8;
$opacity-focus: 0.9;
$opacity-overlay: 0.4;

// ==================== Component-specific tokens ====================

Expand Down Expand Up @@ -865,11 +859,9 @@ $badge-info-text: $color-info;
--color-purple-50: #{$color-purple-50};
--color-purple-100: #{$color-purple-100};
--color-purple-200: #{$color-purple-200};
--color-purple-300: #{$color-purple-300};
--color-purple-400: #{$color-purple-400};
--color-purple-500: #{$color-purple-500};
--color-purple-600: #{$color-purple-600};
--color-purple-700: #{$color-purple-700};
--color-purple-800: #{$color-purple-800};
--color-purple-soft: #a78bfa;
--color-indigo-500: #6366f1;
Expand Down Expand Up @@ -913,8 +905,6 @@ $badge-info-text: $color-info;
--border-accent-subtle: #{$border-accent-subtle};
--border-accent: #{$border-accent};
--border-accent-strong: #{$border-accent-strong};
--border-purple-subtle: #{$border-purple-subtle};
--border-purple: #{$border-purple};

--z-base: #{$z-base};
--z-decoration: #{$z-decoration};
Expand Down Expand Up @@ -1201,9 +1191,6 @@ $badge-info-text: $color-info;

--blur-subtle: #{$blur-subtle};
--blur-base: #{$blur-base};
--blur-medium: #{$blur-medium};
--blur-strong: #{$blur-strong};
--blur-intense: #{$blur-intense};

--size-radius-sm: #{$size-radius-sm};
--size-radius-base: #{$size-radius-base};
Expand All @@ -1212,13 +1199,6 @@ $badge-info-text: $color-info;
--size-radius-xl: #{$size-radius-xl};
--size-radius-2xl: #{$size-radius-2xl};
--size-radius-full: #{$size-radius-full};
--radius-sm: var(--size-radius-sm);
--radius-base: var(--size-radius-base);
--radius-md: var(--size-radius-base);
--radius-lg: var(--size-radius-lg);
--radius-xl: var(--size-radius-xl);
--radius-2xl: var(--size-radius-2xl);
--radius-full: var(--size-radius-full);

--size-gap-1: #{$size-gap-1};
--size-gap-2: #{$size-gap-2};
Expand All @@ -1230,16 +1210,6 @@ $badge-info-text: $color-info;
--size-gap-10: #{$size-gap-10};
--size-gap-12: #{$size-gap-12};
--size-gap-16: #{$size-gap-16};
--spacing-1: var(--size-gap-1);
--spacing-2: var(--size-gap-2);
--spacing-3: var(--size-gap-3);
--spacing-4: var(--size-gap-4);
--spacing-5: var(--size-gap-5);
--spacing-6: var(--size-gap-6);
--spacing-8: var(--size-gap-8);
--spacing-10: var(--size-gap-10);
--spacing-12: var(--size-gap-12);
--spacing-16: var(--size-gap-16);

--motion-instant: #{$motion-instant};
--motion-fast: #{$motion-fast};
Expand Down Expand Up @@ -1278,7 +1248,6 @@ $badge-info-text: $color-info;
--font-size-2xl: #{$font-size-2xl};
--font-size-3xl: #{$font-size-3xl};
--font-size-4xl: #{$font-size-4xl};
--font-size-5xl: #{$font-size-5xl};

/* Flow chat: mirrors UI scale until font preference overwrites (independent mode). */
--flowchat-font-size-xxs: var(--font-size-xxs);
Expand Down Expand Up @@ -1356,7 +1325,6 @@ $badge-info-text: $color-info;
--nav-font-size-2xl: var(--font-size-2xl);
--nav-font-size-3xl: var(--font-size-3xl);
--nav-font-size-4xl: var(--font-size-4xl);
--nav-font-size-5xl: var(--font-size-5xl);
--bitfun-nav-row-action-size: 20px;
--bitfun-nav-row-action-icon-size: 13px;
--bitfun-nav-row-action-offset: 4px;
Expand All @@ -1372,7 +1340,6 @@ $badge-info-text: $color-info;
--opacity-disabled: #{$opacity-disabled};
--opacity-hover: #{$opacity-hover};
--opacity-focus: #{$opacity-focus};
--opacity-overlay: #{$opacity-overlay};

--badge-font-size: #{$badge-font-size};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('deriveFontSizeTokens', () => {
expect(tokens.lg).toBe('15px');
expect(tokens.xl).toBe('16px');
expect(tokens['2xl']).toBe('18px');
expect('5xl' in tokens).toBe(false);
});

it('clamps below minimum (12px)', () => {
Expand Down
2 changes: 0 additions & 2 deletions src/web-ui/src/infrastructure/font-preference/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export interface FontSizeTokens {
'2xl': string;
'3xl': string;
'4xl': string;
'5xl': string;
}

export type FontSizeLevelPresets = Record<Exclude<FontSizeLevel, 'custom'>, FontSizeTokens>;
Expand Down Expand Up @@ -65,7 +64,6 @@ export function deriveFontSizeTokens(basePx: number): FontSizeTokens {
'2xl': `${b + 4}px`,
'3xl': `${b + 8}px`,
'4xl': `${b + 12}px`,
'5xl': `${b + 18}px`,
};
}

Expand Down
Loading
Loading