authorgravatar for 97427849+flamboh@users.noreply.github.comoliver <97427849+flamboh@users.noreply.github.com> 2026-09-30 17:49:15-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-30 17:49:15-07:00
log1905846e03226582f64fec16e1500ddffa151fdf
tree8df3ec7100f8b2d26b5868184735269e10f15279
parent9da066dbe906310671b86290b09df0b56a88ffa5
signature Signed by PGP key B5690EEEBB952194

fix(web): Dark+ and Light+ themes import instead of colliding with built-in ids (#14499)


2 files changed, 40 insertions(+), 3 deletions(-)

apps/web/src/vscodeThemeImport.test.ts+30
......@@ -112,6 +112,36 @@ describe("VS Code theme import", () => {
112112 );
113113 });
114114
115 it("moves reserved VS Code names to a non-reserved id", () => {
116 const darkPlus = parseVsCodeThemeFile({
117 name: "Dark+",
118 type: "dark",
119 colors: { "editor.background": "#1e1e1e" },
120 });
121 expect(darkPlus.label).toBe("Dark+");
122 expect(darkPlus.id).not.toBe("dark");
123 expect(darkPlus.id).toBe("dark-vscode");
124 });
125
126 it("keeps Dark+ and Light+ as separate themes instead of pairing them as +", () => {
127 const themes = pairVsCodeThemes([
128 parseVsCodeThemeFile({
129 name: "Light+",
130 type: "light",
131 colors: { "editor.background": "#ffffff" },
132 }),
133 parseVsCodeThemeFile({
134 name: "Dark+",
135 type: "dark",
136 colors: { "editor.background": "#1e1e1e" },
137 }),
138 ]);
139 expect(themes.map((theme) => [theme.id, theme.label])).toEqual([
140 ["light-vscode", "Light+"],
141 ["dark-vscode", "Dark+"],
142 ]);
143 });
144
115145 it("fills every role the file omits with a readable derived value", () => {
116146 const theme = parseVsCodeThemeFile(VSCODE_DARK);
117147 const colors = getThemeColorsForMode(theme, "dark")!;
apps/web/src/vscodeThemeImport.ts+10-3
......@@ -1,8 +1,10 @@
11import {
22 createVividThemeColors,
33 getThemeModes,
4 isReservedThemeId,
45 parseThemeFile,
56 themeColorToHex,
7 themeIdFromName,
68 THEME_FILE_VERSION,
79 type ThemeAppearance,
810 type ThemeColorRole,
......@@ -321,9 +323,12 @@ export function parseVsCodeThemeFile(value: unknown): ThemeDefinition {
321323
322324 // Reuse the theme-file parser so ids, names, and color values go through the
323325 // same validation as a hand-written file.
326 const name = resolveName(value);
327 const generatedId = themeIdFromName(name);
324328 return parseThemeFile({
325329 version: THEME_FILE_VERSION,
326 name: resolveName(value),
330 ...(isReservedThemeId(generatedId) ? { id: `${generatedId}-vscode` } : {}),
331 name,
327332 appearance,
328333 colors: { ...derived, ...overrides },
329334 });
......@@ -352,9 +357,11 @@ export function pairVsCodeThemes(
352357 const passthrough: Array<{ theme: ThemeDefinition; order: number }> = [];
353358 themes.forEach((theme, order) => {
354359 // Only single-appearance themes with an appearance word in the name can
355 // pair; anything else is already what the user asked for.
360 // pair, and only when the rest of the name can still identify the pair
361 // ("Dark+" and "Light+" would pair as "+"); anything else is already
362 // what the user asked for.
356363 const key = stripAppearance(theme.label);
357 if (getThemeModes(theme).length !== 1 || key === theme.label || key.length === 0) {
364 if (getThemeModes(theme).length !== 1 || key === theme.label || !/[a-z0-9]/i.test(key)) {
358365 passthrough.push({ theme, order });
359366 return;
360367 }