Skip to content

Commit 069bc40

Browse files
committed
Bugfix: selection-fg switches + dropdown description readable
Two reports from the same tester: (1) selected file names in the file list have low contrast against the pane bg, especially with the cursor over them and on tinted panes; (2) the secondary description text in the "File size format" dropdown is unreadable when an option is highlighted. - `--color-selection-fg` split into `--color-selection-fg-primary` + `--color-selection-fg-fallback` (= `--color-text-primary`). New CSS rule swaps to fallback in the dark + tinted pane + focused cursor-active corner where no recognizable gold can clear AA on top of the composite bg. - `--color-accent-fg` already auto-picks black/white per accent (earlier work). The dropdown description now inherits that pick via a `[data-highlighted]` rule in `SettingSelect.svelte` — secondary identity is lost in this state, but on a saturated accent bg there's no opacity-mixed gray that stays both secondary and AA for Apple Purple. - `data-pane-tint` attribute added to `.file-pane` (sourced from `volume-tint.svelte.ts:getPaneTintName`) so the selection-fg fallback rule can scope to tinted panes only. `scripts/check-a11y-contrast` now passes 0/1070 violations and surfaces every accent variant + tint + cursor state combination as a side effect.
1 parent 14a36dd commit 069bc40

4 files changed

Lines changed: 81 additions & 5 deletions

File tree

apps/desktop/src/app.css

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,18 @@
132132
--pane-tint-bg-pct: 90%;
133133
--pane-tint-fg-pct: 10%;
134134

135-
/* === Selection === */
136-
--color-selection-fg: #c9a227;
135+
/* === Selection ===
136+
* Selected-row text reads `--color-selection-fg`. The primary value carries
137+
* the gold "this row is selected" identity. The fallback drops back to
138+
* `--color-text-primary` for the few cases where the row bg is hostile
139+
* enough that no recognizable gold can clear AA on top — specifically
140+
* dark mode + a pane tint + cursor-active (yellow accent over a tinted
141+
* dark surface). The CSS rule that switches between them lives further
142+
* down in this file (search for "selection-fg fallback"). The contrast
143+
* checker's `row_state_matrix.go` synthesizer mirrors that rule. */
144+
--color-selection-fg-primary: #5a4000;
145+
--color-selection-fg-fallback: var(--color-text-primary);
146+
--color-selection-fg: var(--color-selection-fg-primary);
137147

138148
/* === Git portal ===
139149
* Distinct from `--color-accent` to keep the "you're in history-land" feel
@@ -318,6 +328,20 @@
318328
--color-age-old: var(--color-text-secondary);
319329
}
320330

331+
/* selection-fg fallback: dark mode + pane tint + focused cursor on a selected
332+
row. In this state the row bg becomes the translucent yellow accent
333+
composited over a tinted dark surface — Y around 0.09. Even the brightest
334+
gold we'd be willing to call "selection identity" can't reach 4.5:1 there.
335+
Drop to `--color-text-primary` so the row stays readable; the gold
336+
identity is sacrificed only in this one corner. The size tiers
337+
(`--color-size-*-selected`) derive from `--color-selection-fg`, so they
338+
switch in lockstep. Mirrored in `row_state_matrix.go`'s synthesizer. */
339+
@media (prefers-color-scheme: dark) {
340+
.file-pane[data-pane-tint] .full-list-container.is-focused .file-entry.is-selected.is-under-cursor {
341+
--color-selection-fg: var(--color-selection-fg-fallback);
342+
}
343+
}
344+
321345
/* Old-WebKit fallback (light mode).
322346
*
323347
* `color-mix()` arrived in Safari 16.2 (Dec 2022) and `color-mix(in oklch, …)`
@@ -473,8 +497,9 @@
473497
/* === Focus ring shadow === */
474498
--shadow-focus-contrast: 0 0 0 4px color-mix(in srgb, white, transparent 92%);
475499

476-
/* === Selection === */
477-
--color-selection-fg: #d4a82a;
500+
/* === Selection (dark) === see light mode for primary/fallback rationale */
501+
--color-selection-fg-primary: #d4a82a;
502+
--color-selection-fg-fallback: var(--color-text-primary);
478503

479504
/* Git portal: brighter teal in dark mode for readability against
480505
the dark background. */

apps/desktop/src/lib/file-explorer/pane/FilePane.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
9090
const log = getAppLogger('fileExplorer')
9191
import { isMtpVolumeId, getMtpDisplayPath } from '$lib/mtp'
92-
import { getPaneTintBg } from './volume-tint.svelte'
92+
import { getPaneTintBg, getPaneTintName } from './volume-tint.svelte'
9393
import * as benchmark from '$lib/benchmark'
9494
import { handleNavigationShortcut } from '../navigation/keyboard-shortcuts'
9595
import { resolveValidPath } from '../navigation/path-resolution'
@@ -360,6 +360,13 @@
360360
* `null` when the user picked "no tint" for this volume's kind (the common case).
361361
*/
362362
const paneTintBg = $derived(getPaneTintBg(volumeId, currentVolumeInfo?.fsType, currentVolumeInfo?.category))
363+
/**
364+
* Active tint name (or null) for `data-pane-tint` on `.file-pane`. The
365+
* selection-fg fallback rule in `app.css` keys off this attribute to
366+
* switch text color when the tinted bg + cursor-active would otherwise
367+
* push selection-fg below AA. Always tracks `paneTintBg`.
368+
*/
369+
const paneTintName = $derived(getPaneTintName(volumeId, currentVolumeInfo?.fsType, currentVolumeInfo?.category))
363370
/**
364371
* Reactive: the per-volume reconnect cycle state, or `null` if no cycle is
365372
* running. The manager is the single source of truth for the view. By the
@@ -2254,6 +2261,7 @@
22542261
role="region"
22552262
aria-label="{paneId === 'left' ? 'Left' : 'Right'} file pane"
22562263
style={paneTintBg ? `background-color: ${paneTintBg}` : undefined}
2264+
data-pane-tint={paneTintName ?? undefined}
22572265
>
22582266
<!-- svelte-ignore a11y_no_static_element_interactions -->
22592267
<div class="header" oncontextmenu={handleBreadcrumbContextMenu}>

apps/desktop/src/lib/file-explorer/pane/volume-tint.svelte.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,24 @@ export function getPaneTintBg(
174174
void mediaTick
175175
return computeTintHex(tint)
176176
}
177+
178+
/**
179+
* Reactive: returns the active tint name for a pane (`"red"`, `"amber"`, …)
180+
* or `null` when no tint is set. Mirrors `getPaneTintBg` but exposes the
181+
* name rather than the CSS value, so callers can wire `data-pane-tint` on
182+
* `.file-pane` for selectors that need to react to "tinted vs. not."
183+
* The selection-fg fallback rule in `app.css` uses this attribute.
184+
*/
185+
export function getPaneTintName(
186+
volumeId: string,
187+
fsType: string | undefined,
188+
category: LocationCategory | undefined,
189+
): VolumeTintColor | null {
190+
const kind = volumeKindFor(volumeId, fsType, category)
191+
const tint = tintForKind(kind)
192+
if (tint === 'none') return null
193+
// Touch the reactive trigger for the old-WebKit path so callers stay
194+
// reactive to media flips (same rationale as `getPaneTintBg`).
195+
if (!hasColorMix) void mediaTick
196+
return tint
197+
}

apps/desktop/src/lib/settings/components/SettingSelect.svelte

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,26 @@
372372
color: var(--color-text-tertiary);
373373
font-size: var(--font-size-sm);
374374
}
375+
376+
/* When the parent option is highlighted (cursor over, or checked), the bg
377+
flips to `--color-accent`. The description's resting `--color-text-tertiary`
378+
(#666 / #a0a0a0) drops to ~1–2.4:1 contrast on every system accent. Switch
379+
it to `--color-accent-fg` (auto-picked black/white via `readableFgOn`) so
380+
it matches the label's color and stays readable. The secondary visual
381+
weight is lost in this state — but on a saturated bg there's no opacity
382+
that stays both secondary and AA-compliant for Apple Purple (the worst
383+
dark-fg case). The contrast checker's `dropdown_states.go` matrix
384+
validates this against every accent variant. */
385+
:global(.select-item[data-highlighted]) .option-description,
386+
:global(.select-item[data-state='checked']) .option-description {
387+
color: var(--color-accent-fg);
388+
}
389+
390+
/* Exception: when `Custom...` is highlighted, the other checked items lose
391+
their accent bg (see the `.custom-highlighted .select-item...` rule
392+
above). Revert the description color so it stays readable on the
393+
now-transparent bg. */
394+
:global(.custom-highlighted .select-item[data-state='checked']:not([data-highlighted])) .option-description {
395+
color: var(--color-text-tertiary);
396+
}
375397
</style>

0 commit comments

Comments
 (0)