From e2bcc6bb14fe1b08ac91f5d69d3e8ade0fedd923 Mon Sep 17 00:00:00 2001 From: Julius Olsson Date: Tue, 28 Jul 2026 17:13:32 +0200 Subject: [PATCH] fix(settings): theme picker rendered the border colour as a fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Theme picker drew as a grey block instead of a grid of cells. ThemePickerRow uses a seam trick: a `gap-px` grid whose PARENT is painted with the border colour, so only the 1px gaps show it. That depends on every cell being opaque. The cells used `bg-row-bg` and `bg-row-selected-bg`, and both of those tokens are deliberately see-through — `transparent`, and a 15% accent mix against `transparent`. So the parent's border colour flooded every cell, and the picker rendered with the border colour used as a fill. Both are correct defaults for an ordinary row sitting on a panel; they are just wrong under a parent painted with the border colour. `bg-row-hover-bg` was always opaque (`--theme-surface-hi`), which is why hover was the one state that looked right, and the selected cell only looked plausible because a 15% tint over grey still reads as tinted. Cells now name opaque tokens explicitly rather than depending on tokens whose own definition documents them as transparent: `bg-panel-bg` for the resting state, and a new `--theme-row-selected-solid-bg` that mixes the same 15% accent against `--theme-surface` instead of `transparent`. Both derive from roles Custom Appearance already exposes, so custom themes keep tuning it. The new token is deliberately NOT added to the customAppearance role map: it is a derived companion to an existing role, not a new role to configure. ThemePickerRow is the only component using this seam pattern, so the blast radius is one file plus the token. Verified: tsc clean on both projects, 1721 tests / 251 files green. Co-Authored-By: Claude Opus 5 (1M context) --- .../features/settings/ui/ThemePickerRow.tsx | 26 +++++++++++++++---- src/renderer/src/styles.css | 10 +++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/features/settings/ui/ThemePickerRow.tsx b/src/renderer/src/features/settings/ui/ThemePickerRow.tsx index da8202d2..92cda1e6 100644 --- a/src/renderer/src/features/settings/ui/ThemePickerRow.tsx +++ b/src/renderer/src/features/settings/ui/ThemePickerRow.tsx @@ -27,6 +27,22 @@ export function ThemePickerRow({ settings, onSelect, onCreate, onEdit, onDelete // gap-px over a bg-panel-border parent draws the grid lines as gaps rather // than per-cell borders, which keeps the double-border seams out and // respects the codebase's no-border-radius rule. + // + // THE CELLS MUST BE OPAQUE for this to work, and that is not a style + // preference — it is what the trick depends on. The parent is painted with + // the BORDER colour; only the 1px gaps are meant to show it. Any cell that + // is transparent (or semi-transparent) lets that border colour flood the + // cell, and the whole picker renders as a grey block with the border colour + // used as a fill. + // + // That is exactly what happened: the cells used `bg-row-bg` and + // `bg-row-selected-bg`, and BOTH of those tokens are deliberately + // see-through (`transparent`, and a 15% accent mix against `transparent`). + // They are correct defaults for an ordinary row sitting on a panel — they + // are simply wrong here. So this component names opaque tokens explicitly + // rather than depending on tokens whose own definition documents them as + // transparent. `bg-row-hover-bg` was always opaque (`--theme-surface-hi`), + // which is why hover was the one state that looked right.
{THEME_MODES.map(mode => ( diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css index 02aa6af1..14568fad 100644 --- a/src/renderer/src/styles.css +++ b/src/renderer/src/styles.css @@ -73,6 +73,7 @@ --color-row-bg: var(--theme-row-bg); --color-row-hover-bg: var(--theme-row-hover-bg); --color-row-selected-bg: var(--theme-row-selected-bg); + --color-row-selected-solid-bg: var(--theme-row-selected-solid-bg); --color-row-selected-fg: var(--theme-row-selected-fg); --color-row-danger-selected-bg: var(--theme-row-danger-selected-bg); @@ -551,6 +552,15 @@ --theme-row-bg: transparent; --theme-row-hover-bg: var(--theme-surface-hi); --theme-row-selected-bg: color-mix(in srgb, var(--theme-accent) 15%, transparent); + /* Opaque twin of the row-selected tint, for the one layout that cannot use + the transparent one: a `gap-px` grid whose PARENT is painted with the + border colour so the gaps read as seams. That trick only works while every + cell is opaque — a transparent cell lets the parent's border colour flood + the whole cell, and a semi-transparent one tints it. Mixing against + `--theme-surface` instead of `transparent` gives the identical colour on a + surface-coloured panel while remaining safe over any backdrop. + See ThemePickerRow.tsx. */ + --theme-row-selected-solid-bg: color-mix(in srgb, var(--theme-accent) 15%, var(--theme-surface)); --theme-row-selected-fg: var(--theme-ink); --theme-row-danger-selected-bg: var(--theme-danger-soft);