Skip to content

Commit f43563b

Browse files
authored
fix(theme): give migrated V1 dialogs an opaque background (#60016)
1 parent 77a0fd6 commit f43563b

3 files changed

Lines changed: 59 additions & 6 deletions

File tree

‎packages/theme/src/tui/v1-migrate-new-designs.ts‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
categoricalTokens,
1010
chromaticHues,
1111
detectMode,
12+
dialogBackground,
1213
hex,
1314
inferHues,
1415
referenceHues,
@@ -28,15 +29,15 @@ export function migrateV1NewDesigns(theme: ThemeV1Json): ThemeDocument {
2829
const darkMode = detectMode(dark)
2930
if (lightMode === darkMode) {
3031
const definition = migrateMode(lightMode === "light" ? light : dark, lightMode)
31-
if (lightMode === "light") return { base: base(definition), light: lightOverrides(definition) }
32+
if (lightMode === "light") return { base: base(definition), light: lightOverrides(definition, light) }
3233
return { base: base(definition), dark: { hue: definition.hue } }
3334
}
3435
}
3536
const lightDefinition = migrateMode(light, "light")
3637
const darkDefinition = migrateMode(dark, "dark")
3738
return {
3839
base: base(lightDefinition),
39-
light: lightOverrides(lightDefinition),
40+
light: lightOverrides(lightDefinition, light),
4041
dark: darkDefinition,
4142
}
4243
}
@@ -53,8 +54,12 @@ const lightDialog = {
5354
},
5455
} satisfies ModeDefinition["@dialog"]
5556

56-
function lightOverrides(definition: ThemeDefinition): ModeDefinition {
57-
return { hue: definition.hue, "@dialog": lightDialog }
57+
function lightOverrides(definition: ThemeDefinition, theme: Theme): ModeDefinition {
58+
if (theme.backgroundPanel.a === 1) return { hue: definition.hue, "@dialog": lightDialog }
59+
return {
60+
hue: definition.hue,
61+
"@dialog": { ...lightDialog, background: { ...lightDialog.background, base: dialogBackground(theme) } },
62+
}
5863
}
5964

6065
// Mirrors the opencode2 token layout: text at 100/200/300, the page at 900, raised surfaces at 700/600/500,
@@ -125,7 +130,7 @@ function migrateMode(theme: Theme, mode: Mode): ThemeDefinition {
125130
...paletteTokens(theme),
126131
"@dialog": {
127132
background: {
128-
base: "$background.raised.base",
133+
base: dialogBackground(theme),
129134
action: {
130135
primary: { $hovered: "$background.raised.max" },
131136
secondary: { $hovered: "$background.raised.max" },

‎packages/theme/src/tui/v1-migrate.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,25 @@ function migrateMode(theme: Theme, mode: Mode): ThemeDefinition {
217217
},
218218
"@dialog": {
219219
background: {
220-
base: "$background.raised.base",
220+
base: dialogBackground(theme),
221221
action: { primary: { $hovered: "$background.raised.high" } },
222222
},
223223
},
224224
})
225225
}
226226

227+
// Dialogs float over the session, so a see-through panel would let the content underneath show through them.
228+
// Fall back to the first visible surface color, made opaque.
229+
export function dialogBackground(theme: Theme) {
230+
if (theme.backgroundPanel.a === 1) return "$background.raised.base"
231+
const visible = [theme.backgroundPanel, theme.backgroundMenu, theme.backgroundElement, theme.background].find(
232+
(color) => color.a > 0,
233+
)
234+
if (!visible) return luminance(theme.text) > 0.5 ? "#000000" : "#ffffff"
235+
const [red, green, blue] = visible.toInts()
236+
return hexInts(red, green, blue, 255)
237+
}
238+
227239
export function referenceHues(theme: ThemeDefinition): ThemeDefinition {
228240
const definitions = theme.hue as Record<string, string | Partial<Record<HueStep, string>>> | undefined
229241
if (!definitions) return theme

‎packages/tui/test/theme/v2/v1-migrate.test.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,42 @@ test("collapses identical V1 backgrounds when both variants infer one mode", ()
279279
expect(selectThemeMode(migratedLight, "dark").mode).toBe("light")
280280
})
281281

282+
test("gives dialogs an opaque background when V1 panels are see-through", () => {
283+
const lucent = DEFAULT_THEMES["lucent-orng"]!
284+
for (const mode of ["dark", "light"] as const) {
285+
const legacy = resolveV1(lucent, mode)
286+
const dialog = resolveThemeDocument(migrateV1(lucent), mode).surface("dialog")
287+
expect(legacy.backgroundPanel.a).toBe(0)
288+
const [red, green, blue] = legacy.backgroundMenu.toInts()
289+
expect(legacy.backgroundMenu.a).toBeLessThan(1)
290+
expect(dialog.background.base.toInts()).toEqual([red, green, blue, 255])
291+
}
292+
293+
const source = structuredClone(opencodeV1)
294+
source.theme.background = "transparent"
295+
source.theme.backgroundPanel = "transparent"
296+
source.theme.backgroundElement = "transparent"
297+
source.theme.backgroundMenu = "transparent"
298+
expect(hex(resolveThemeDocument(migrateV1(source), "dark").surface("dialog").background.base)).toBe("#000000")
299+
expect(hex(resolveThemeDocument(migrateV1(source), "light").surface("dialog").background.base)).toBe("#ffffff")
300+
301+
const opaque = resolveThemeDocument(migrateV1(opencodeV1), "dark")
302+
expect(hex(opaque.surface("dialog").background.base)).toBe(hex(opaque.background.raised.base))
303+
})
304+
305+
test("gives new designs dialogs an opaque background when V1 panels are see-through", () => {
306+
const lucent = DEFAULT_THEMES["lucent-orng"]!
307+
for (const mode of ["dark", "light"] as const) {
308+
const [red, green, blue] = resolveV1(lucent, mode).backgroundMenu.toInts()
309+
const dialog = resolveThemeDocument(migrateV1NewDesigns(lucent), mode).surface("dialog")
310+
expect(dialog.background.base.toInts()).toEqual([red, green, blue, 255])
311+
}
312+
313+
const opaque = migrateV1NewDesigns(DEFAULT_THEMES.tokyonight!)
314+
expect(opaque.base["@dialog"]?.background?.base).toBe("$background.raised.base")
315+
expect(opaque.light?.["@dialog"]?.background?.base).toBe("$hue.neutral.800")
316+
})
317+
282318
test("keeps both modes when a shared background has different contrast", () => {
283319
const source = structuredClone(opencodeV1)
284320
source.theme.background = "#808080"

0 commit comments

Comments
 (0)