From 2f87fa66adae2dc99f99c046aed516dda6ea8cd7 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 26 Aug 2026 13:44:14 -0500 Subject: [PATCH] fix(desktop): don't gate hover-reveal on the hover media query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tailwind v4 wraps hover:/group-hover: in @media (hover: hover). Windows hosts with a digitizer often answer false even with a mouse, so those controls stay opacity-0 — clickable, invisible. Trust :hover itself. Co-authored-by: xxxigm <54813621+xxxigm@users.noreply.github.com> --- apps/desktop/src/hover-variant.test.ts | 40 ++++++++++++++++++++++++++ apps/desktop/src/styles.css | 4 +++ 2 files changed, 44 insertions(+) create mode 100644 apps/desktop/src/hover-variant.test.ts diff --git a/apps/desktop/src/hover-variant.test.ts b/apps/desktop/src/hover-variant.test.ts new file mode 100644 index 0000000000..b067ddf521 --- /dev/null +++ b/apps/desktop/src/hover-variant.test.ts @@ -0,0 +1,40 @@ +// @vitest-environment node +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { compile } from '@tailwindcss/node' +import { describe, expect, it } from 'vitest' + +const SRC = dirname(fileURLToPath(import.meta.url)) + +const CANDIDATES = ['hover:opacity-100', 'group-hover/attachment:opacity-100', 'group-hover/code:opacity-100'] + +async function utilitiesFor(css: string): Promise { + const { build } = await compile(css, { base: SRC, onDependency() {} }) + const out = build(CANDIDATES) + const start = out.indexOf('@layer utilities {') + expect(start).toBeGreaterThanOrEqual(0) + return out.slice(start) +} + +function gatesHoverOnCapabilityQuery(css: string): boolean { + return /@media\s*\(\s*hover\s*:\s*hover\s*\)/.test(css) +} + +describe('hover variant (Windows hover-reveal)', () => { + it('still wraps hover utilities in the capability query without an override', async () => { + const utilities = await utilitiesFor('@import "tailwindcss";\n') + + expect(utilities).toContain('group-hover') + expect(gatesHoverOnCapabilityQuery(utilities)).toBe(true) + }) + + it('does not wrap the app stylesheet hover utilities in the capability query', async () => { + const utilities = await utilitiesFor('@import "./styles.css";\n') + + expect(utilities).toContain('.group-hover\\/attachment\\:opacity-100') + expect(utilities).toContain('.group-hover\\/code\\:opacity-100') + expect(utilities).toContain('.hover\\:opacity-100') + expect(gatesHoverOnCapabilityQuery(utilities)).toBe(false) + }) +}) diff --git a/apps/desktop/src/styles.css b/apps/desktop/src/styles.css index 53efc28478..054153e018 100644 --- a/apps/desktop/src/styles.css +++ b/apps/desktop/src/styles.css @@ -10,6 +10,10 @@ font-size: var(--titlebar-icon-size); } +/* v4 gates hover: on @media (hover: hover). Some Windows hosts with a + digitizer answer false even with a mouse, so hover-reveal controls stay + opacity-0 (clickable, invisible). Trust :hover itself. */ +@custom-variant hover (&:hover); @custom-variant dark (&:is(.dark *)); /* Blanket reduced-motion override: kill ALL CSS animations and transitions