Skip to content

Commit cc00e4d

Browse files
committed
refactor(theme): collapse git derived color tokens
1 parent 44b3f51 commit cc00e4d

21 files changed

Lines changed: 220 additions & 173 deletions

File tree

docs/architecture/theme-token-optimization.md

Lines changed: 21 additions & 21 deletions
Large diffs are not rendered by default.

scripts/theme-color-governance-baseline.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,10 @@
105105
"max": 0
106106
},
107107
"colorScopes.token.occurrences": {
108-
"max": 291
108+
"max": 284
109109
},
110110
"colorScopes.token.uniqueColors": {
111-
"max": 181
111+
"max": 177
112112
},
113113
"colorScopes.exception.uniqueColors": {
114114
"max": 162
@@ -144,16 +144,16 @@
144144
"max": 0
145145
},
146146
"cssVarDefinitions.staticContractDefinedUnique": {
147-
"max": 250
147+
"max": 242
148148
},
149149
"cssVarDefinitions.staticContractExternalUsageUnique": {
150-
"max": 250
150+
"max": 242
151151
},
152152
"cssVarDefinitions.staticContractInternalOnlyUnique": {
153153
"max": 0
154154
},
155155
"cssVarDefinitions.staticContractLowExternalUsageUnique": {
156-
"max": 51
156+
"max": 44
157157
},
158158
"tokenAliasLiterals.occurrences": {
159159
"max": 0
@@ -264,19 +264,19 @@
264264
"max": 0
265265
},
266266
"colorDomainScopes.themePreset.occurrences": {
267-
"max": 168
267+
"max": 167
268268
},
269269
"colorDomainScopes.themePreset.uniqueColors": {
270-
"max": 115
270+
"max": 114
271271
},
272272
"colorDomainScopes.themeRuntime.occurrences": {
273273
"max": 27
274274
},
275275
"colorDomainScopes.tokenContract.occurrences": {
276-
"max": 90
276+
"max": 84
277277
},
278278
"colorDomainScopes.tokenContract.uniqueColors": {
279-
"max": 80
279+
"max": 76
280280
},
281281
"colorDomainScopes.generatedWidget.occurrences": {
282282
"max": 0

src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,10 @@
201201

202202
.snapshot-card__action-btn--accept {
203203
color: var(--color-success);
204-
border: 1px solid var(--git-color-staged-border);
204+
border: 1px solid color-mix(in srgb, var(--git-color-staged) 30%, transparent);
205205

206206
&:hover:not(:disabled) {
207-
background: var(--git-color-staged-bg-hover);
207+
background: color-mix(in srgb, var(--git-color-staged) 15%, transparent);
208208
border-color: color-mix(in srgb, var(--git-color-staged) 50%, transparent);
209209
}
210210
}
@@ -214,7 +214,7 @@
214214
border: 1px solid var(--color-error-border);
215215

216216
&:hover:not(:disabled) {
217-
background: var(--git-color-deleted-bg-hover);
217+
background: color-mix(in srgb, var(--git-color-deleted) 15%, transparent);
218218
border-color: color-mix(in srgb, var(--git-color-deleted) 50%, transparent);
219219
}
220220
}

src/web-ui/src/component-library/styles/tokens.scss

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,12 @@ $git-color-branch-bg: $overlay-white-08;
117117
$git-color-branch-bg-hover: $overlay-white-12;
118118

119119
$git-color-changes: $color-warning;
120-
$git-color-changes-bg: rgba(245, 158, 11, 0.1);
121-
$git-color-changes-bg-hover: rgba(245, 158, 11, 0.15);
122120

123121
$git-color-staged: rgb(34, 197, 94);
124-
$git-color-staged-bg: rgba(34, 197, 94, 0.1);
125-
$git-color-staged-bg-hover: rgba(34, 197, 94, 0.15);
126-
$git-color-staged-border: rgba(34, 197, 94, 0.3);
127122

128123
$git-color-added: $git-color-staged;
129-
$git-color-added-bg: $git-color-staged-bg;
130-
$git-color-added-bg-hover: $git-color-staged-bg-hover;
131124

132125
$git-color-deleted: $color-error;
133-
$git-color-deleted-bg: $color-error-bg;
134-
$git-color-deleted-bg-hover: rgba(239, 68, 68, 0.15);
135126

136127
// ==================== Border system ====================
137128
$border-subtle: $overlay-white-12;
@@ -428,17 +419,9 @@ $badge-info-text: $color-info;
428419
--git-color-branch-bg: #{$git-color-branch-bg};
429420
--git-color-branch-bg-hover: #{$git-color-branch-bg-hover};
430421
--git-color-changes: #{$git-color-changes};
431-
--git-color-changes-bg: #{$git-color-changes-bg};
432422
--git-color-staged: #{$git-color-staged};
433-
--git-color-staged-bg: #{$git-color-staged-bg};
434-
--git-color-staged-bg-hover: #{$git-color-staged-bg-hover};
435-
--git-color-staged-border: #{$git-color-staged-border};
436423
--git-color-added: #{$git-color-added};
437-
--git-color-added-bg: #{$git-color-added-bg};
438-
--git-color-added-bg-hover: #{$git-color-added-bg-hover};
439424
--git-color-deleted: #{$git-color-deleted};
440-
--git-color-deleted-bg: #{$git-color-deleted-bg};
441-
--git-color-deleted-bg-hover: #{$git-color-deleted-bg-hover};
442425

443426
--border-subtle: #{$border-subtle};
444427
--border-base: #{$border-base};

src/web-ui/src/flow_chat/components/InlineDiffPreview.scss

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,23 @@
8181
}
8282

8383
&--added {
84-
background: var(--git-color-staged-bg);
84+
background: color-mix(in srgb, var(--git-color-added) 10%, transparent);
8585

8686
.diff-line__gutter {
87-
background: var(--git-color-staged-bg-hover);
87+
background: color-mix(in srgb, var(--git-color-added) 15%, transparent);
8888
}
8989

9090
.diff-line__prefix {
91-
color: var(--git-color-staged);
91+
color: var(--git-color-added);
9292
font-weight: 500;
9393
}
9494
}
9595

9696
&--removed {
97-
background: var(--git-color-deleted-bg);
97+
background: color-mix(in srgb, var(--git-color-deleted) 10%, transparent);
9898

9999
.diff-line__gutter {
100-
background: var(--git-color-deleted-bg-hover);
100+
background: color-mix(in srgb, var(--git-color-deleted) 15%, transparent);
101101
}
102102

103103
.diff-line__prefix {
@@ -225,19 +225,19 @@
225225
.light .inline-diff-preview {
226226
.diff-line {
227227
&--added {
228-
background: var(--git-color-staged-bg);
228+
background: color-mix(in srgb, var(--git-color-added) 10%, transparent);
229229

230230
.diff-line__gutter {
231-
background: color-mix(in srgb, var(--git-color-staged) 18%, transparent);
231+
background: color-mix(in srgb, var(--git-color-added) 18%, transparent);
232232
}
233233

234234
.diff-line__prefix {
235-
color: var(--git-color-staged);
235+
color: var(--git-color-added);
236236
}
237237
}
238238

239239
&--removed {
240-
background: var(--git-color-deleted-bg);
240+
background: color-mix(in srgb, var(--git-color-deleted) 10%, transparent);
241241

242242
.diff-line__gutter {
243243
background: color-mix(in srgb, var(--git-color-deleted) 18%, transparent);

src/web-ui/src/flow_chat/tool-cards/GetFileDiffDisplay.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@
7979
word-break: break-all;
8080

8181
.diff-add {
82-
background: var(--git-color-added-bg-hover);
83-
color: var(--color-success);
82+
background: color-mix(in srgb, var(--git-color-added) 15%, transparent);
83+
color: var(--git-color-added);
8484
}
8585

8686
.diff-del {
87-
background: var(--git-color-deleted-bg-hover);
88-
color: var(--color-error);
87+
background: color-mix(in srgb, var(--git-color-deleted) 15%, transparent);
88+
color: var(--git-color-deleted);
8989
}
9090
}
9191
}

src/web-ui/src/flow_chat/tool-cards/GitToolDisplay.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@
337337
color: color-mix(in srgb, var(--color-success) 80%, transparent);
338338

339339
&:hover:not(:disabled) {
340-
background: var(--git-color-staged-bg-hover);
340+
background: color-mix(in srgb, var(--git-color-staged) 15%, transparent);
341341
color: var(--git-color-staged);
342342
}
343343

@@ -350,8 +350,8 @@
350350
color: color-mix(in srgb, var(--color-error) 80%, transparent);
351351

352352
&:hover:not(:disabled) {
353-
background: var(--git-color-deleted-bg-hover);
354-
color: var(--color-error);
353+
background: color-mix(in srgb, var(--git-color-deleted) 15%, transparent);
354+
color: var(--git-color-deleted);
355355
}
356356

357357
&:active:not(:disabled) {

src/web-ui/src/infrastructure/theme/core/ThemeService.test.ts

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,15 @@ describe('ThemeService runtime theme tokens', () => {
137137
expect(rootStyle.getPropertyValue('--git-color-branch')).toBe('#a1a1aa');
138138
expect(rootStyle.getPropertyValue('--git-color-branch-bg')).toBe('rgba(255, 255, 255, 0.06)');
139139
expect(rootStyle.getPropertyValue('--git-color-branch-bg-hover')).toBe('rgba(255, 255, 255, 0.12)');
140+
expect(rootStyle.getPropertyValue('--git-color-changes')).toBe('rgb(245, 158, 11)');
140141
expect(rootStyle.getPropertyValue('--git-color-added')).toBe('rgb(34, 197, 94)');
141-
expect(rootStyle.getPropertyValue('--git-color-added-bg')).toBe('rgba(34, 197, 94, 0.1)');
142-
expect(rootStyle.getPropertyValue('--git-color-added-bg-hover')).toBe('rgba(34, 197, 94, 0.15)');
143-
expect(rootStyle.getPropertyValue('--git-color-changes-bg')).toBe('rgba(245, 158, 11, 0.1)');
144-
expect(rootStyle.getPropertyValue('--git-color-deleted-bg-hover')).toBe('rgba(239, 68, 68, 0.15)');
145-
expect(rootStyle.getPropertyValue('--git-color-staged-bg-hover')).toBe('rgba(34, 197, 94, 0.15)');
146-
expect(rootStyle.getPropertyValue('--git-color-staged-border')).toBe('rgba(34, 197, 94, 0.3)');
142+
expect(rootStyle.getPropertyValue('--git-color-deleted')).toBe('rgb(239, 68, 68)');
143+
expect(rootStyle.getPropertyValue('--git-color-staged')).toBe('rgb(34, 197, 94)');
144+
expect(rootStyle.getPropertyValue('--git-color-changes-bg')).toBe('');
145+
expect(rootStyle.getPropertyValue('--git-color-added-bg')).toBe('');
146+
expect(rootStyle.getPropertyValue('--git-color-deleted-bg')).toBe('');
147+
expect(rootStyle.getPropertyValue('--git-color-staged-bg')).toBe('');
148+
expect(rootStyle.getPropertyValue('--git-color-staged-border')).toBe('');
147149
expect(rootStyle.getPropertyValue('--git-color-pull')).toBe('');
148150
expect(rootStyle.getPropertyValue('--git-color-push')).toBe('');
149151
});
@@ -388,7 +390,7 @@ describe('ThemeService runtime theme tokens', () => {
388390
expect(configAPI.setConfig).not.toHaveBeenCalledWith('themes.custom', expect.anything());
389391
});
390392

391-
it('does not export non-contract dynamic keys from custom themes', () => {
393+
it('does not inject non-contract dynamic keys from custom themes', () => {
392394
const service = new ThemeService();
393395
const customTheme = {
394396
...bitfunLightTheme,
@@ -616,6 +618,115 @@ describe('ThemeService runtime theme tokens', () => {
616618
).rejects.toThrow(/reserved for a built-in theme/);
617619
});
618620

621+
it('strips non-contract git color keys from registered custom themes', async () => {
622+
const nonContractGitColorKeys = [
623+
'changesBg',
624+
'addedBg',
625+
'deletedBg',
626+
'stagedBg',
627+
'addedBgHover',
628+
'stagedBorder',
629+
'pull',
630+
] as const;
631+
const expectNoNonContractGitColorKeys = (gitColors: ThemeConfig['colors']['git']) => {
632+
const gitRecord = gitColors as unknown as Record<string, unknown>;
633+
nonContractGitColorKeys.forEach(key => {
634+
expect(gitRecord).not.toHaveProperty(key);
635+
});
636+
};
637+
const service = new ThemeService();
638+
const legacyTheme = {
639+
...bitfunDarkTheme,
640+
id: 'custom-legacy-git-bg',
641+
name: 'Legacy Git Backgrounds',
642+
colors: {
643+
...bitfunDarkTheme.colors,
644+
git: {
645+
...bitfunDarkTheme.colors.git,
646+
changesBg: 'rgba(245, 158, 11, 0.1)',
647+
addedBg: 'rgba(34, 197, 94, 0.1)',
648+
deletedBg: 'rgba(239, 68, 68, 0.1)',
649+
stagedBg: 'rgba(16, 185, 129, 0.1)',
650+
addedBgHover: 'rgba(34, 197, 94, 0.2)',
651+
stagedBorder: 'rgba(16, 185, 129, 0.4)',
652+
pull: '#60a5fa',
653+
},
654+
},
655+
} as unknown as ThemeConfig;
656+
657+
await service.registerTheme(legacyTheme);
658+
659+
const normalized = service.getTheme('custom-legacy-git-bg');
660+
expect(normalized).toBeDefined();
661+
if (!normalized) {
662+
throw new Error('Expected custom legacy git theme to be registered');
663+
}
664+
expect(normalized.colors.git.added).toBe(bitfunDarkTheme.colors.git.added);
665+
expectNoNonContractGitColorKeys(normalized.colors.git);
666+
667+
const persistedThemes = vi.mocked(configAPI.setConfig).mock.calls.find(([key]) => key === 'themes.custom')?.[1] as
668+
| ThemeConfig[]
669+
| undefined;
670+
const persistedTheme = persistedThemes?.find(theme => theme.id === 'custom-legacy-git-bg');
671+
expect(persistedTheme).toBeDefined();
672+
if (!persistedTheme) {
673+
throw new Error('Expected custom legacy git theme to be persisted');
674+
}
675+
expectNoNonContractGitColorKeys(persistedTheme.colors.git);
676+
677+
const exported = service.exportTheme('custom-legacy-git-bg');
678+
expect(exported).not.toBeNull();
679+
if (!exported) {
680+
throw new Error('Expected custom legacy git theme to be exported');
681+
}
682+
expectNoNonContractGitColorKeys(exported.theme.colors.git);
683+
});
684+
685+
it('migrates persisted custom themes with non-contract git color keys on load', async () => {
686+
const legacyTheme = {
687+
...bitfunDarkTheme,
688+
id: 'custom-loaded-legacy-git',
689+
name: 'Loaded Legacy Git',
690+
colors: {
691+
...bitfunDarkTheme.colors,
692+
git: {
693+
...bitfunDarkTheme.colors.git,
694+
changesBg: 'rgba(245, 158, 11, 0.1)',
695+
addedBgHover: 'rgba(34, 197, 94, 0.2)',
696+
stagedBorder: 'rgba(16, 185, 129, 0.4)',
697+
},
698+
},
699+
} as unknown as ThemeConfig;
700+
vi.mocked(configAPI.getConfig).mockResolvedValue({ custom: [legacyTheme] });
701+
const service = new ThemeService();
702+
703+
await service.ensureUserThemesLoaded();
704+
705+
const normalized = service.getTheme('custom-loaded-legacy-git');
706+
expect(normalized).toBeDefined();
707+
if (!normalized) {
708+
throw new Error('Expected legacy custom theme to load');
709+
}
710+
expect(normalized.colors.git.added).toBe(bitfunDarkTheme.colors.git.added);
711+
expect(normalized.colors.git.staged).toBe(bitfunDarkTheme.colors.git.staged);
712+
expect(normalized.colors.git as unknown as Record<string, unknown>).not.toHaveProperty('changesBg');
713+
expect(normalized.colors.git as unknown as Record<string, unknown>).not.toHaveProperty('addedBgHover');
714+
expect(normalized.colors.git as unknown as Record<string, unknown>).not.toHaveProperty('stagedBorder');
715+
716+
const migratedThemes = vi.mocked(configAPI.setConfig).mock.calls.find(([key]) => key === 'themes.custom')?.[1] as
717+
| ThemeConfig[]
718+
| undefined;
719+
expect(migratedThemes).toHaveLength(1);
720+
const migratedGitColors = migratedThemes?.[0]?.colors.git as unknown as Record<string, unknown> | undefined;
721+
expect(migratedGitColors).toBeDefined();
722+
if (!migratedGitColors) {
723+
throw new Error('Expected migrated theme to keep git colors');
724+
}
725+
expect(migratedGitColors).not.toHaveProperty('changesBg');
726+
expect(migratedGitColors).not.toHaveProperty('addedBgHover');
727+
expect(migratedGitColors).not.toHaveProperty('stagedBorder');
728+
});
729+
619730
it('projects normalized custom themes through the compact plugin color boundary', async () => {
620731
const service = new ThemeService();
621732
const partialCustomTheme = {

0 commit comments

Comments
 (0)