diff --git a/docs/architecture/theme-token-optimization.md b/docs/architecture/theme-token-optimization.md index e5f21cdc6..729363dba 100644 --- a/docs/architecture/theme-token-optimization.md +++ b/docs/architecture/theme-token-optimization.md @@ -108,6 +108,10 @@ registry。 | compatibility alias family 直接使用次数 | 0 | generated widget frame 同时暴露 canonical 与 legacy family,内部 shell 读取 canonical family | | stale compatibility alias family contracts | 0 | 防止动态 family 或 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 回流 | +| active surface token rename occurrences | 0 | 防止旧 key 在 SCSS、CSS 或 TSX inline style 中被重新定义或读取 | +| surface token rename missing canonical | 0 | 防止 rename registry 指向不存在的 canonical key | | generated widget payload key | 326 | widget iframe 对外主题变量 allowlist,作为外部边界单独预算,不计入内部 alias 读取 | | generated widget payload compatibility alias | 64 | payload 仍暴露已登记 legacy alias,防止已生成或第三方内容读取失败 | | generated widget payload compatibility family key | 17 | payload 仍暴露 `--radius-*`、`--spacing-*` 具体 key,同时暴露 canonical family | @@ -130,7 +134,7 @@ registry。 | Mermaid | 139 | 95 | Mermaid 专用渲染域 | | Theme runtime | 54 | 45 | `ThemeService.ts` 运行时注入 | | Language identity | 52 | 50 | 语言身份色,已集中到 identity registry | -| Terminal | 38 | 30 | terminal/ANSI 专用域 | +| Terminal | 37 | 29 | terminal/ANSI 专用域;工具命令空状态已复用 `--tool-command-empty-rgb`,不再保留独立 raw 色 | | Boundary fallback | 22 | 22 | iframe/miniapp/截图兜底值,不作为普通 app token | | Visual effects | 0 | 0 | StreamText/TextStroke raw literal 已迁出普通组件层 | | UI exception registry | 38 | 34 | 已归档的 UI 例外色,包含 review team、agent capability、template context、insights 和 inspector 等固定身份色 | @@ -161,7 +165,7 @@ fallback 收敛决策表: | `--char-index` | 上移到组件根 | StreamText 根提供 `0`,每字符 inline style 仍可覆盖 | 移除 3 处 keyframe fallback | | `--gallery-grid-min` | 上移到根 token 默认值 | `tokens.scss` 提供 `320px`,祖先变量和 props inline style 仍可覆盖 | 移除 grid sizing fallback | | `--gallery-skeleton-height` | 上移到根 token 默认值 | `tokens.scss` 提供 `140px`,祖先变量和 props inline style 仍可覆盖 | 移除 skeleton height fallback | -| `--primary-color` | 改为 Markdown 组件 token | `--primary-color` 是 tool card 局部兼容入口,不提升为全局 app token;BaseToolCard 映射到 `--markdown-primary-color` | Markdown 内部不再读取旧 key fallback | +| `--primary-color` | 改为明确的 tool-card accent token | `--primary-color` 是历史 tool card 局部入口,不提升为全局 app token;BaseToolCard 现在通过 `--base-tool-card-accent-color` 映射到 `--markdown-primary-color` | 产品代码不再定义或读取旧 key,回流由 `surfaceTokenRenames` 拦截 | | `--scene-viewport-border-width` | 上移默认值 | 静态 token 提供 `1px`,ThemeService 继续按主题 layout 覆盖为 `1px` 或 `0` | 移除 viewport border fallback | 阶段状态: @@ -172,9 +176,9 @@ fallback 收敛决策表: | Phase 1:canonical token 契约 | 已完成调用方迁移 | compatibility alias registry 已记录 64 个显式 alias 和 2 个 alias family;内部 `var()` 读取已清零;widget payload 的 64 个显式 alias 和 17 个 family key 作为外部兼容面单独预算,并补齐 `--size-radius-2xl` / `--size-radius-full` canonical 导出 | | Phase 2:精确重复合并 | 已完成主体 | token-equivalent app literal 已清零;截图兜底、language identity 和 review/agent/insights 固定色已迁入显式 registry;`tokens.scss` 中同域同语义的 deep-review consent 精确重复已改为 alias | | Phase 3:legacy fallback 迁移 | 已完成主体 | 组件级 `var(--token, fallback)` 已清零,baseline 降到 0;新增 fallback 会被审计报告和 baseline 拦截 | -| Phase 4:组件 token 抽取 | 已完成主体 | CodeEditor、StreamText、ChatInputPixelPet、ReferencesPanel、AgentCompanion、tool-card、editor 组件装饰色已抽为组件私有 RGB channel 或复用 contract token | +| Phase 4:组件 token 抽取 | 已完成主体 | CodeEditor、StreamText、ChatInputPixelPet、ReferencesPanel、AgentCompanion、tool-card、editor、generative-widget 组件装饰色已抽为组件 token 或复用 contract token;Flow Chat 局部动态 key 已改为 surface namespace | | Phase 5:近似色合并 | 已完成主体 | 普通组件 near pair 已清零;极近似视觉色只在不相邻或不承担状态差异时合并,Monaco/terminal/Mermaid/syntax 专用 palette 不强行合并 | -| Phase 6:防回退约束 | 已强化 | baseline 已同步到 component/non-token=0、appUi=0、token-equivalent=0、nearPair=0、compatibility alias 读取=0、fallback=0,并保留 generated widget payload、domain contract 防回退指标 | +| Phase 6:防回退约束 | 已强化 | baseline 已同步到 component/non-token=0、appUi=0、token-equivalent=0、nearPair=0、compatibility alias 读取=0、fallback=0、surface rename debt=0,并保留 generated widget payload、domain contract 防回退指标 | Phase 5 决策记录: @@ -211,6 +215,9 @@ Phase 6 防回退约束: | `compatibilityAliases.staleRegisteredUnique` | 0 | 0 | 防止兼容 alias registry 保留没有定义或 canonical 目标缺失的 key | | `compatibilityAliases.staleRegisteredFamilyUnique` | 0 | 0 | 防止 `--radius-*`、`--spacing-*` 这类动态 family 与 canonical family 失配 | | `compatibilityAliases.missingCanonicalUnique` | 0 | 0 | 防止 family alias 具体 key 缺失对应 canonical key | +| `surfaceTokenRenames.activeUnique` | 0 | 0 | 防止已迁移的 surface-local 旧 key 重新出现 | +| `surfaceTokenRenames.activeOccurrences` | 0 | 0 | 防止旧 key 在定义和读取两侧回流 | +| `surfaceTokenRenames.missingCanonicalUnique` | 0 | 0 | 防止 surface rename contract 指向不存在的 canonical key | | `generatedWidgetPayload.varUnique` | 326 | 326 | 控制 widget 对外主题 payload allowlist 不继续膨胀 | | `generatedWidgetPayload.compatibilityAliasUnique` | 64 | 64 | 控制 payload 中显式 legacy alias 数量,后续只能降低或经复审调整 | | `generatedWidgetPayload.compatibilityAliasFamilyUnique` | 17 | 17 | 控制 payload 中 legacy size family 具体 key 数量 | @@ -237,7 +244,7 @@ editor、syntax、terminal、language identity、boundary fallback 等专用域 | tool-cards-review | tool card、review panel、expanded/collapsed/status | danger alias 保留 destructive 语义,不能和 error 无证据合并 | | code-editor-diff | Monaco、diff、selection、added/deleted/conflict | editor/diff 色表达相邻状态,不能按数值相似直接合并 | | terminal | ANSI normal/bright、selection、error | ANSI 语义独立于 app semantic color | -| markdown-mermaid | Markdown、Prism、Mermaid、diagram/error | Markdown accent 通过 `--markdown-primary-color` 表达;tool-card 的旧 `--primary-color` 只作为局部兼容输入映射,Mermaid 角色不等于 app status | +| markdown-mermaid | Markdown、Prism、Mermaid、diagram/error | Markdown accent 通过 `--markdown-primary-color` 表达;tool-card 历史 `--primary-color` 已退场,Mermaid 角色不等于 app status | | generated-widget | iframe fallback、host payload、loading/error | widget payload 兼容是保留多组旧 alias 的主要原因 | | theme-settings | theme switcher、system/custom theme preview | custom theme preview 可能比普通组件更早暴露 runtime alias 缺失 | | mobile-web-shell | mobile-web、mobile/narrow、loading/error/navigation | mobile web 是独立构建目标,不能只依赖 desktop WebView 验证 | @@ -840,7 +847,7 @@ alpha 差异经常承担 elevation 和交互状态,不应全部压成一个值 建议按证据和 surface 拆分,避免一次性大迁移: -当前 PR 已覆盖前 5 项,并把 widget payload 外部兼容面纳入审计预算: +已完成的治理批次覆盖前 9 项,并把 widget payload 外部兼容面纳入审计预算: 1. 审计工具和 baseline report。 2. canonical token map 与 compatibility alias。 diff --git a/scripts/audit-theme-colors.mjs b/scripts/audit-theme-colors.mjs index 8876be3b6..b9faadbef 100644 --- a/scripts/audit-theme-colors.mjs +++ b/scripts/audit-theme-colors.mjs @@ -18,6 +18,7 @@ import { REGISTERED_DYNAMIC_VAR_PREFIXES, RUNTIME_CONTRACT_VAR_DEFINITION_PATH_PARTS, STATIC_CONTRACT_VAR_DEFINITION_PATH_PARTS, + SURFACE_TOKEN_RENAME_CONTRACTS, TOKEN_COMPATIBILITY_ALIAS_CONTRACTS, TOKEN_COMPATIBILITY_ALIAS_FAMILY_CONTRACTS, TOKEN_ALIAS_SOURCE_PATH_PARTS, @@ -1115,6 +1116,35 @@ function audit(options) { )) .map(([key, scope]) => ({ key, count: scope.occurrences })) .sort((a, b) => a.key.localeCompare(b.key)); + const surfaceTokenRenameEntries = SURFACE_TOKEN_RENAME_CONTRACTS + .map(contract => { + const usageCount = varUsageCounts.get(contract.key) ?? 0; + const definitionCount = varDefinitionCounts.get(contract.key) ?? 0; + const files = new Set([ + ...Array.from(varUsageFiles.get(contract.key) ?? []), + ...Array.from(varDefinitionFiles.get(contract.key) ?? []), + ]); + return { + key: contract.key, + canonical: contract.canonical, + usageCount, + definitionCount, + count: usageCount + definitionCount, + files: Array.from(files).sort().slice(0, 5), + }; + }) + .filter(entry => entry.count > 0) + .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key)); + const missingSurfaceTokenRenameCanonicalEntries = checksFullThemeSourceRoot + ? SURFACE_TOKEN_RENAME_CONTRACTS + .map(contract => ({ + key: contract.key, + canonical: contract.canonical, + canonicalDefinitionKind: getDefinitionKind(contract.canonical), + })) + .filter(entry => !entry.canonicalDefinitionKind) + .sort((a, b) => a.key.localeCompare(b.key)) + : []; return { root: normalizePath(path.relative(cwd, root)) || '.', @@ -1213,6 +1243,14 @@ function audit(options) { missingColorDomainContracts: missingColorDomainContractEntries, staleColorDomainContracts: staleColorDomainContractEntries, activeUncontractedColorDomains: activeUncontractedColorDomainEntries, + surfaceTokenRenames: { + registeredUnique: SURFACE_TOKEN_RENAME_CONTRACTS.length, + activeUnique: surfaceTokenRenameEntries.length, + activeOccurrences: surfaceTokenRenameEntries.reduce((total, entry) => total + entry.count, 0), + missingCanonicalUnique: missingSurfaceTokenRenameCanonicalEntries.length, + active: surfaceTokenRenameEntries.slice(0, REPORT_ROW_LIMIT), + missingCanonicals: missingSurfaceTokenRenameCanonicalEntries.slice(0, REPORT_ROW_LIMIT), + }, tokenAliasLiterals: { occurrences: sumMapValues(tokenAliasLiteralCounts), uniqueColors: tokenAliasLiteralCounts.size, @@ -1301,6 +1339,12 @@ function printText(report) { `stale=${report.colorDomainContracts.staleRegisteredUnique}, ` + `activeUncontracted=${report.colorDomainContracts.activeUncontractedUnique}` ); + console.log( + `Surface token renames: registered=${report.surfaceTokenRenames.registeredUnique}, ` + + `active=${report.surfaceTokenRenames.activeUnique}, ` + + `occurrences=${report.surfaceTokenRenames.activeOccurrences}, ` + + `missingCanonicals=${report.surfaceTokenRenames.missingCanonicalUnique}` + ); console.log('\nTop colors:'); console.log(printRows(report.topColors)); @@ -1446,6 +1490,28 @@ function printText(report) { ]; console.log(printRows(colorDomainGapRows.slice(0, 10))); + console.log('\nSurface token rename debt:'); + if (report.surfaceTokenRenames.active.length === 0) { + console.log(' none'); + } else { + for (const row of report.surfaceTokenRenames.active.slice(0, 10)) { + console.log( + ` ${row.key} -> ${row.canonical} ` + + `count=${row.count} definitions=${row.definitionCount} usages=${row.usageCount} ` + + `files=${row.files.join(', ')}` + ); + } + } + + console.log('\nSurface token rename missing canonicals:'); + if (report.surfaceTokenRenames.missingCanonicals.length === 0) { + console.log(' none'); + } else { + for (const row of report.surfaceTokenRenames.missingCanonicals.slice(0, 10)) { + console.log(` ${row.key} -> ${row.canonical}`); + } + } + console.log('\nTop token-equivalent app literals:'); if (report.tokenAliasLiterals.top.length === 0) { console.log(' none'); diff --git a/scripts/audit-theme-colors.test.mjs b/scripts/audit-theme-colors.test.mjs index 3ae1821ba..b279f49c2 100644 --- a/scripts/audit-theme-colors.test.mjs +++ b/scripts/audit-theme-colors.test.mjs @@ -11,6 +11,7 @@ import { COLOR_DOMAIN_RULES, DYNAMIC_VAR_FAMILY_CONTRACTS, FALLBACK_VAR_CONTRACTS, + SURFACE_TOKEN_RENAME_CONTRACTS, TOKEN_COMPATIBILITY_ALIAS_CONTRACTS, TOKEN_COMPATIBILITY_ALIAS_FAMILY_CONTRACTS, } from './theme-css-var-contract.mjs'; @@ -122,6 +123,20 @@ test('theme CSS var contract registry is explicit and non-overlapping', () => { assert.ok(contract.reason.trim().length >= 30, `${contract.key} must explain why fallback is intentional`); assert.ok(contract.boundary.trim().length >= 10, `${contract.key} must classify the fallback boundary`); } + + const surfaceRenameKeys = new Set(SURFACE_TOKEN_RENAME_CONTRACTS.map(contract => contract.key)); + assert.equal( + surfaceRenameKeys.size, + SURFACE_TOKEN_RENAME_CONTRACTS.length, + 'surface token rename contracts must be unique', + ); + for (const contract of SURFACE_TOKEN_RENAME_CONTRACTS) { + assert.match(contract.key, /^--[a-z0-9-]+$/); + assert.match(contract.canonical, /^--[a-z0-9-]+$/); + assert.notEqual(contract.key, contract.canonical, `${contract.key} must point to a different canonical token`); + assert.ok(contract.owner.includes('src/web-ui/src/'), `${contract.key} must name a source owner`); + assert.ok(contract.reason.trim().length >= 30, `${contract.key} must explain the rename boundary`); + } }); test('repository dynamic CSS var families match the registered contract', () => { @@ -146,6 +161,9 @@ test('repository dynamic CSS var families match the registered contract', () => assert.equal(report.colorDomainContracts.missingRegisteredUnique, 0); assert.equal(report.colorDomainContracts.staleRegisteredUnique, 0); assert.equal(report.colorDomainContracts.activeUncontractedUnique, 0); + assert.equal(report.surfaceTokenRenames.activeUnique, 0); + assert.equal(report.surfaceTokenRenames.activeOccurrences, 0); + assert.equal(report.surfaceTokenRenames.missingCanonicalUnique, 0); }); test('theme color audit reports alias family usages whose exact canonical key is missing', (t) => { @@ -219,6 +237,52 @@ test('theme color audit emits scoped machine-readable reports', (t) => { assert.equal(report.summary.baseline.enforced, false); }); +test('theme color audit reports deprecated surface-local token names', (t) => { + const { dir, sourceRoot } = createFixture({ + 'component-library/styles/tokens.scss': [ + ':root {', + ' --base-tool-card-accent-color: #60a5fa;', + ' --snapshot-card-operation-color: #60a5fa;', + '}', + '', + ].join('\n'), + 'component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.scss': [ + '.base-tool-card {', + ' --primary-color: var(--base-tool-card-accent-color);', + ' color: var(--primary-color);', + '}', + '', + ].join('\n'), + 'component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx': [ + "export const style = { '--operation-color': 'var(--snapshot-card-operation-color)' };", + '', + ].join('\n'), + 'tools/editor/meditor/components/TiptapEditor.scss': [ + '.m-editor-tiptap {', + ' --m-editor-highlight-rgb: var(--markdown-editor-highlight-rgb);', + ' background: rgba(var(--m-editor-highlight-rgb), 0.15);', + '}', + '', + ].join('\n'), + }); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + + const result = runAudit(['--root', sourceRoot, '--json', '--no-baseline']); + assert.equal(result.status, 0, result.stderr || result.stdout); + + const report = JSON.parse(result.stdout); + assert.equal(report.surfaceTokenRenames.activeUnique, 3); + assert.equal(report.surfaceTokenRenames.activeOccurrences, 5); + assert.deepEqual( + report.surfaceTokenRenames.active.map(row => [row.key, row.canonical, row.definitionCount, row.usageCount]), + [ + ['--m-editor-highlight-rgb', '--markdown-editor-highlight-rgb', 1, 1], + ['--primary-color', '--base-tool-card-accent-color', 1, 1], + ['--operation-color', '--snapshot-card-operation-color', 1, 0], + ], + ); +}); + test('theme color audit reports compatibility alias usage without treating it as raw color debt', (t) => { const { dir, sourceRoot } = createFixture({ 'component-library/styles/tokens.scss': [ diff --git a/scripts/theme-color-governance-baseline.json b/scripts/theme-color-governance-baseline.json index d76c26095..68438ffbd 100644 --- a/scripts/theme-color-governance-baseline.json +++ b/scripts/theme-color-governance-baseline.json @@ -41,6 +41,18 @@ "compatibilityAliases.missingCanonicalUnique": { "max": 0 }, + "surfaceTokenRenames.registeredUnique": { + "max": 9 + }, + "surfaceTokenRenames.activeUnique": { + "max": 0 + }, + "surfaceTokenRenames.activeOccurrences": { + "max": 0 + }, + "surfaceTokenRenames.missingCanonicalUnique": { + "max": 0 + }, "generatedWidgetPayload.varUnique": { "max": 326 }, @@ -90,7 +102,7 @@ "max": 697 }, "colorScopes.exception.uniqueColors": { - "max": 274 + "max": 273 }, "cssVarDefinitions.unresolvedRequiredUnique": { "max": 0 @@ -159,7 +171,7 @@ "max": 18 }, "colorDomainScopes.terminal.occurrences": { - "max": 38 + "max": 37 }, "colorDomainScopes.debugOverlay.occurrences": { "max": 0 diff --git a/scripts/theme-css-var-contract.mjs b/scripts/theme-css-var-contract.mjs index bbec90712..3503ca20a 100644 --- a/scripts/theme-css-var-contract.mjs +++ b/scripts/theme-css-var-contract.mjs @@ -676,6 +676,63 @@ export const TOKEN_COMPATIBILITY_ALIAS_FAMILY_CONTRACTS = [ export const FALLBACK_VAR_CONTRACTS = []; +export const SURFACE_TOKEN_RENAME_CONTRACTS = [ + { + key: '--primary-color', + canonical: '--base-tool-card-accent-color', + owner: 'src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard', + reason: 'BaseToolCard used a generic local primary color key; the explicit component key prevents accidental global primary-token coupling.', + }, + { + key: '--operation-color', + canonical: '--snapshot-card-operation-color', + owner: 'src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard', + reason: 'Snapshot operation color is a card-local role and should not look like a reusable operation namespace for other surfaces.', + }, + { + key: '--delay', + canonical: '--flowchat-scroll-anchor-delay', + owner: 'src/web-ui/src/flow_chat/components/modern/ScrollAnchor', + reason: 'ScrollAnchor animation delay is a Flow Chat runtime input; the generic key is too easy to collide with unrelated animation code.', + }, + { + key: '--um-failed-fs', + canonical: '--user-message-failed-font-size', + owner: 'src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss', + reason: 'UserMessage failed-state sizing should use readable Flow Chat surface names instead of an abbreviated local key family.', + }, + { + key: '--um-failed-lh', + canonical: '--user-message-failed-line-height', + owner: 'src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss', + reason: 'UserMessage failed-state line-height should use readable Flow Chat surface names instead of an abbreviated local key family.', + }, + { + key: '--um-failed-line-box', + canonical: '--user-message-failed-line-box', + owner: 'src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss', + reason: 'UserMessage failed-state line box should use readable Flow Chat surface names instead of an abbreviated local key family.', + }, + { + key: '--tool-command-preview-empty-rgb', + canonical: '--tool-command-empty-rgb', + owner: 'src/web-ui/src/flow_chat/tool-cards/ToolCommandPreview.scss; src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.scss', + reason: 'Empty command color is shared by command preview and terminal command rendering, so it needs one tool-command token.', + }, + { + key: '--m-editor-highlight-rgb', + canonical: '--markdown-editor-highlight-rgb', + owner: 'src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss', + reason: 'Markdown editor highlight color should use the shared markdown-editor namespace instead of the abbreviated meditor local key.', + }, + { + key: '--m-editor-highlight-border-rgb', + canonical: '--markdown-editor-highlight-border-rgb', + owner: 'src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss', + reason: 'Markdown editor highlight border color should use the shared markdown-editor namespace instead of the abbreviated meditor local key.', + }, +]; + export const DYNAMIC_VAR_FAMILY_CONTRACTS = [ { prefix: '--blur-', diff --git a/scripts/theme-visual-governance-contract.json b/scripts/theme-visual-governance-contract.json index 44ef53cb7..8c3e51e06 100644 --- a/scripts/theme-visual-governance-contract.json +++ b/scripts/theme-visual-governance-contract.json @@ -129,7 +129,7 @@ "formFactors": ["desktop", "narrow"], "themes": ["dark", "light", "system"], "states": ["default", "code", "table", "link", "diagram", "error"], - "tokenFamilies": ["--markdown-*", "--primary-color", "Mermaid palette", "Prism palette"], + "tokenFamilies": ["--markdown-*", "--markdown-primary-color", "Mermaid palette", "Prism palette"], "evidence": [ { "type": "theme-color-audit", @@ -142,7 +142,7 @@ } ], "risks": [ - "--primary-color is an embedded-content override and must not be removed as a normal fallback.", + "--markdown-primary-color is the embedded-content accent override and must remain distinct from generic app primary aliases.", "Mermaid graph roles do not map directly to app status colors." ] }, diff --git a/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.scss b/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.scss index 59665753b..a6601db8c 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.scss +++ b/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.scss @@ -5,8 +5,8 @@ @use '../shared-styles' as shared; .base-tool-card { - --primary-color: #{tokens.$color-accent-600}; - --markdown-primary-color: var(--primary-color); + --base-tool-card-accent-color: #{tokens.$color-accent-600}; + --markdown-primary-color: var(--base-tool-card-accent-color); @include shared.liquid-card-base; @include shared.card-light-flow; diff --git a/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.tsx b/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.tsx index 453aecf6b..c61e7f1be 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.tsx +++ b/src/web-ui/src/component-library/components/FlowChatCards/BaseToolCard/BaseToolCard.tsx @@ -101,7 +101,7 @@ export const BaseToolCard: React.FC = ({ return (
{typeof icon === 'string' ? ( @@ -120,7 +120,7 @@ export const BaseToolCard: React.FC = ({ return (
diff --git a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.scss b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.scss index 686b373f5..50419a2b5 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.scss +++ b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.scss @@ -6,7 +6,7 @@ .snapshot-card { - --operation-color: #{tokens.$color-accent-500}; + --snapshot-card-operation-color: #{tokens.$color-accent-500}; @include shared.liquid-card-base; font-family: tokens.$font-family-mono; @@ -83,7 +83,7 @@ .snapshot-card__icon { flex-shrink: 0; - color: var(--operation-color); + color: var(--snapshot-card-operation-color); transition: all tokens.$motion-base; } diff --git a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx index ed497a1f1..dcc0fd54f 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx +++ b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx @@ -80,7 +80,7 @@ export const SnapshotCard: React.FC = ({ return (
diff --git a/src/web-ui/src/component-library/styles/tokens.scss b/src/web-ui/src/component-library/styles/tokens.scss index bc2c3ca73..0d48125ea 100644 --- a/src/web-ui/src/component-library/styles/tokens.scss +++ b/src/web-ui/src/component-library/styles/tokens.scss @@ -1017,6 +1017,10 @@ $badge-info-text: $color-info; --flowchat-inline-tag-red: var(--color-error-soft); --tool-card-file-search-color: var(--color-cyan-500); --tool-card-web-search-color: #0ea5e9; + --generative-widget-card-icon-rgb: 125, 207, 255; + --generative-widget-card-error-rgb: 253, 164, 175; + --generative-widget-card-error-soft-rgb: 252, 165, 165; + --tool-command-empty-rgb: 224, 108, 117; --flowchat-copy-success-color: var(--color-success); --markdown-copy-success-color: var(--color-success); --git-graph-lane-blue: #{$color-accent-500}; @@ -1217,6 +1221,8 @@ $badge-info-text: $color-info; --gallery-skeleton-height: 140px; --inline-context-tag-color: var(--color-accent-500); --deep-review-action-bar-surface: var(--color-bg-secondary); + --markdown-editor-highlight-rgb: 255, 213, 79; + --markdown-editor-highlight-border-rgb: 255, 193, 7; --markdown-editor-list-indent: 1.75rem; --markdown-editor-list-marker-width: 1.25rem; --markdown-editor-list-gap: 0.25rem; diff --git a/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.scss b/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.scss index 28dec0a2c..f56b5b831 100644 --- a/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.scss +++ b/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.scss @@ -3,7 +3,7 @@ */ .scroll-anchor { - --delay: 0s; + --flowchat-scroll-anchor-delay: 0s; position: absolute; right: 0; @@ -56,7 +56,7 @@ transition: all 0.15s ease; animation: anchorFade 0.3s ease backwards; - animation-delay: var(--delay); + animation-delay: var(--flowchat-scroll-anchor-delay); &::before { content: ''; diff --git a/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.tsx b/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.tsx index c7d2df4cc..7eaa014ce 100644 --- a/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.tsx +++ b/src/web-ui/src/flow_chat/components/modern/ScrollAnchor.tsx @@ -167,7 +167,7 @@ export const ScrollAnchor: React.FC = ({ className={`scroll-anchor__point ${hoveredAnchor?.id === anchor.id ? 'active' : ''}`} style={{ top: `${anchor.position}%`, - '--delay': `${idx * 0.03}s` + '--flowchat-scroll-anchor-delay': `${idx * 0.03}s` } as React.CSSProperties} onClick={(e) => { e.stopPropagation(); diff --git a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss index 22c0c1cd1..c8b652ba1 100644 --- a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss +++ b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss @@ -123,9 +123,11 @@ // Failed state: no bubble. Icons/actions align to the first text line (not flex baseline: // `-webkit-box` line-clamp does not expose a reliable baseline for `align-items: baseline`). .user-message-item--failed { - --um-failed-fs: calc(var(--flowchat-font-size-base) * 0.86); - --um-failed-lh: 1.55; - --um-failed-line-box: calc(var(--um-failed-fs) * var(--um-failed-lh)); + --user-message-failed-font-size: calc(var(--flowchat-font-size-base) * 0.86); + --user-message-failed-line-height: 1.55; + --user-message-failed-line-box: calc( + var(--user-message-failed-font-size) * var(--user-message-failed-line-height) + ); background: transparent; border: none; @@ -164,8 +166,8 @@ align-items: center; justify-content: center; box-sizing: border-box; - height: var(--um-failed-line-box); - min-height: var(--um-failed-line-box); + height: var(--user-message-failed-line-box); + min-height: var(--user-message-failed-line-box); color: var(--color-text-muted); opacity: 0.9; @@ -181,10 +183,10 @@ width: max-content; max-width: 100%; color: var(--color-text-muted); - font-size: var(--um-failed-fs); + font-size: var(--user-message-failed-font-size); font-weight: 400; font-style: italic; - line-height: var(--um-failed-lh); + line-height: var(--user-message-failed-line-height); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--color-text-muted) 72%, transparent); } @@ -193,7 +195,7 @@ flex: 0 0 auto; align-items: center; /* Match `.user-message-item__copy-btn` height (28px) to first-line box. */ - margin-top: calc((var(--um-failed-line-box) - 28px) / 2); + margin-top: calc((var(--user-message-failed-line-box) - 28px) / 2); } .user-message-item__failed-body .user-message-item__steering-tag { diff --git a/src/web-ui/src/flow_chat/tool-cards/GenerativeWidgetToolCard.scss b/src/web-ui/src/flow_chat/tool-cards/GenerativeWidgetToolCard.scss index 1cba4872a..5a5d44c7b 100644 --- a/src/web-ui/src/flow_chat/tool-cards/GenerativeWidgetToolCard.scss +++ b/src/web-ui/src/flow_chat/tool-cards/GenerativeWidgetToolCard.scss @@ -1,8 +1,4 @@ .generative-widget-card { - --generative-widget-card-icon-rgb: 125, 207, 255; - --generative-widget-card-error-rgb: 253, 164, 175; - --generative-widget-card-error-soft-rgb: 252, 165, 165; - overflow: hidden; } diff --git a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.scss b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.scss index 790e6dfdb..4782da30c 100644 --- a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.scss +++ b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.scss @@ -142,7 +142,7 @@ } .command-empty { - color: #e06c75; + color: rgb(var(--tool-command-empty-rgb)); font-style: italic; } } diff --git a/src/web-ui/src/flow_chat/tool-cards/ToolCommandPreview.scss b/src/web-ui/src/flow_chat/tool-cards/ToolCommandPreview.scss index 5e664873b..d70262f54 100644 --- a/src/web-ui/src/flow_chat/tool-cards/ToolCommandPreview.scss +++ b/src/web-ui/src/flow_chat/tool-cards/ToolCommandPreview.scss @@ -15,9 +15,7 @@ } .tool-command-preview__empty { - --tool-command-preview-empty-rgb: 224, 108, 117; - - color: rgb(var(--tool-command-preview-empty-rgb)); + color: rgb(var(--tool-command-empty-rgb)); font-style: italic; } diff --git a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss index 4200437b8..2a74e98de 100644 --- a/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss +++ b/src/web-ui/src/tools/editor/meditor/components/TiptapEditor.scss @@ -1,9 +1,6 @@ @use '../../../../component-library/styles/tokens.scss' as *; .m-editor-tiptap { - --m-editor-highlight-rgb: 255, 213, 79; - --m-editor-highlight-border-rgb: 255, 193, 7; - position: relative; width: 100%; height: 100%; @@ -38,7 +35,7 @@ } &.m-editor-tiptap-block-highlighted { - background: rgba(var(--m-editor-highlight-rgb), 0.15); + background: rgba(var(--markdown-editor-highlight-rgb), 0.15); } } @@ -213,7 +210,7 @@ } &[data-block-id].m-editor-tiptap-block-highlighted { - border-top-color: rgba(var(--m-editor-highlight-border-rgb), 0.55); + border-top-color: rgba(var(--markdown-editor-highlight-border-rgb), 0.55); } }