From 5b7fed27008f3f4330cd64bde8b77cf86012d841 Mon Sep 17 00:00:00 2001 From: Nagisa-3000 Date: Thu, 24 Sep 2026 19:52:46 +0800 Subject: [PATCH] fix(desktop): keep quick-entry options readable in dark themes --- .../app/quick-entry/quick-entry-app.test.tsx | 55 +++++++++++++++++++ .../src/app/quick-entry/quick-entry-app.tsx | 18 +++++- 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/app/quick-entry/quick-entry-app.test.tsx diff --git a/apps/desktop/src/app/quick-entry/quick-entry-app.test.tsx b/apps/desktop/src/app/quick-entry/quick-entry-app.test.tsx new file mode 100644 index 0000000000..fe08c2857e --- /dev/null +++ b/apps/desktop/src/app/quick-entry/quick-entry-app.test.tsx @@ -0,0 +1,55 @@ +/** Regression coverage for #77429: native picker options must paint a readable surface. */ + +import { act, cleanup, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import type { QuickEntryStatePush } from '@/store/quick-entry' + +import { QuickEntryApp } from './quick-entry-app' + +const initialHermesDesktop = window.hermesDesktop + +describe('QuickEntryApp', () => { + let pushState: ((payload: QuickEntryStatePush) => void) | undefined + + beforeEach(() => { + pushState = undefined + window.hermesDesktop = { + quickEntry: { + dismiss: vi.fn(), + onShown: vi.fn(() => vi.fn()), + onState: vi.fn(callback => { + pushState = callback + + return vi.fn() + }), + submit: vi.fn() + } + } as never + }) + + afterEach(() => { + cleanup() + window.hermesDesktop = initialHermesDesktop + vi.restoreAllMocks() + }) + + it('paints every native target option with matching theme foreground and background tokens', () => { + render() + + act(() => { + pushState?.({ + connected: true, + sessions: [{ id: 'session-1', title: 'A recent session' }] + }) + }) + + const options = screen.getAllByRole('option') as HTMLOptionElement[] + expect(options).toHaveLength(3) + + for (const option of options) { + expect(option.style.color).toBe('var(--ui-text-primary, var(--foreground))') + expect(option.style.backgroundColor).toBe('var(--ui-bg-elevated, var(--background))') + } + }) +}) diff --git a/apps/desktop/src/app/quick-entry/quick-entry-app.tsx b/apps/desktop/src/app/quick-entry/quick-entry-app.tsx index 7f99e8b7fb..69e8e581f2 100644 --- a/apps/desktop/src/app/quick-entry/quick-entry-app.tsx +++ b/apps/desktop/src/app/quick-entry/quick-entry-app.tsx @@ -10,6 +10,14 @@ import { type QuickComposerState } from '@/store/quick-entry' +// Native select popups do not reliably inherit the closed control's colors. +// Paint both sides of the contrast pair on every option so Chromium cannot +// combine a dark-theme foreground with an OS-provided light popup surface. +const QUICK_TARGET_OPTION_STYLE = { + backgroundColor: 'var(--ui-bg-elevated, var(--background))', + color: 'var(--ui-text-primary, var(--foreground))' +} + /** * The Quick Entry composer — the whole renderer surface of the global-hotkey * mini window. Deliberately one input plus a session-target picker and nothing @@ -187,10 +195,14 @@ export function QuickEntryApp() { }} value={state.target} > - - + + {state.sessions.map(session => ( - ))}