From 0412a4b3eeeb62db69e104954605dd96b2c4bf7f Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:57:46 +0200 Subject: [PATCH] fix(settings): every switch is the core's switch (#318) The Win+E row drew its own switch (accent track, white knob), as did the phone server dialog and the player menu's toggles, beside the core's Switch everywhere else. They now use the core's Switch (or its SWITCH_ON / SWITCH_KNOB_ON at a menu's size), so once the core bump lands (PrismTerminal #139, core 0.26.0) every switch wears the style's accent with the ink chosen for it. A unit test refuses any role="switch" outside the core; settingsLook holds every Explorer switch to one look. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FHHaWKR4M5QtW7Wecyuk4t --- CLAUDE.md | 11 +++++- package-lock.json | 4 +- package.json | 2 +- src/renderer/src/components/PhoneDialog.tsx | 20 ++-------- src/renderer/src/components/PlayerMenu.tsx | 9 +++-- .../components/settings/WinEShortcutRow.tsx | 27 ++++++------- .../settings/settingsControls.test.ts | 20 ++++++++-- tools/e2e/run.mjs | 38 +++++++++++++++++++ 8 files changed, 88 insertions(+), 43 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index da012e53..b21262e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -775,8 +775,15 @@ native ``, no Acrylic or Accent opacity slider. Spec and plan: Switch (on: `--p-text-soft` track, `--p-bg` knob), the pressed Segmented option and the row buttons (Default apps, Clear) are the terminal core's own `fields.tsx` since #292 (PrismTerminal #42 holds them there), and no page keeps a copy. Selection marks (style cards, swatches) and - Reset links are not buttons and keep the accent, as does the Win+E switch; the chosen rail page - is grey since #292. `settings/settingsControls.test.ts` holds it. + Reset links are not buttons and keep the accent; the chosen rail page is grey since #292. + `settings/settingsControls.test.ts` holds it. **EVERY SWITCH IS THE CORE'S** (#318; owner, + 2026-10-07: "toggles differ in look i like the teal with black not the green and white", then + "it should depend on the theme so only teal on the teal theme"): the Win+E row (once its own + accent-and-white exception), the phone server switch and the player menu's toggles use the + core's `Switch` or its `SWITCH_ON` / `SWITCH_KNOB_ON`, and no file outside the core draws a + `role="switch"`. The LOOK is the core's call: from core 0.26.0 (PrismTerminal #139) an on switch + is `--p-sel-bg` with an `--p-on-accent` knob. The `settingsLook` e2e holds every switch on the + Explorer page to one track and one knob colour when on. - **NOTHING WEARS A FOCUS BOX; FOCUS IS THE HOVER'S FILL** (#272; owner, 2026-10-04, of a white box round the title bar's menu button: "remove the focus effect. go through the ui and remove focus effects like this"). `index.css`'s base-layer rule takes Chromium's ring off every focused diff --git a/package-lock.json b/package-lock.json index 4f6150d3..f26af95d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prism", - "version": "0.92.1", + "version": "0.92.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prism", - "version": "0.92.1", + "version": "0.92.2", "license": "MIT", "dependencies": { "@codemirror/commands": "^6.11.0", diff --git a/package.json b/package.json index be068b68..ba1300f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prism", - "version": "0.92.1", + "version": "0.92.2", "description": "A fast, universal media viewer for Windows.", "author": "Max", "license": "MIT", diff --git a/src/renderer/src/components/PhoneDialog.tsx b/src/renderer/src/components/PhoneDialog.tsx index c1e170de..7131392d 100644 --- a/src/renderer/src/components/PhoneDialog.tsx +++ b/src/renderer/src/components/PhoneDialog.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState, type JSX } from 'react' import type { PhoneState } from '@shared/types' +import { Switch } from 'prism-term-core/renderer/settings/fields' import { Dialog } from './Dialog' /** @@ -53,22 +54,9 @@ export function PhoneDialog({
{/* Said BEFORE the switch goes on: a per-user installer cannot add the firewall rule, and a declined prompt is a phone that cannot connect diff --git a/src/renderer/src/components/PlayerMenu.tsx b/src/renderer/src/components/PlayerMenu.tsx index 273275e5..ac4b92d0 100644 --- a/src/renderer/src/components/PlayerMenu.tsx +++ b/src/renderer/src/components/PlayerMenu.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState, type JSX } from 'react' +import { SWITCH_KNOB_ON, SWITCH_ON } from 'prism-term-core/renderer/settings/fields' import type { MediaControls } from '../lib/useMediaControls' import { setPlayerPref, usePlayerPrefs } from '../lib/playerPrefs' import type { SubTrackInfo } from '../lib/useSubtitles' @@ -33,8 +34,10 @@ function Toggle({ > {label} diff --git a/src/renderer/src/components/settings/WinEShortcutRow.tsx b/src/renderer/src/components/settings/WinEShortcutRow.tsx index 0273c496..ddbfb68a 100644 --- a/src/renderer/src/components/settings/WinEShortcutRow.tsx +++ b/src/renderer/src/components/settings/WinEShortcutRow.tsx @@ -1,5 +1,6 @@ import type { WinEShortcutStatus } from '@shared/winEShortcut' import { useEffect, useRef, useState, type JSX } from 'react' +import { Switch } from 'prism-term-core/renderer/settings/fields' import { SettingRow } from 'prism-term-core/renderer/settings/layout/SettingRow' import { appOpt } from './appOptions' import { iconPath } from './icons' @@ -11,8 +12,12 @@ import { iconPath } from './icons' * live state lives in the subtext, so a sighted user and a screen reader get * the same words), and a live region carries it only while there is one. * - * The switch keeps the ACCENT when on, the one switch in Settings that does - * (#202's existing exception): it hands a Windows shortcut to Prism. + * The switch is the core's, like every other (#318; owner, 2026-10-07: + * "toggles differ in look i like the teal with black not the green and + * white", then "it should depend on the theme"). It used to draw its own, + * the accent with a white knob (#202's exception), the one switch in + * Settings that did; the core's on switch now wears the style's accent with + * the ink chosen for it, so there is nothing left to be an exception for. */ export function WinEShortcutRow(): JSX.Element { const [status, setStatus] = useState({ @@ -94,20 +99,12 @@ export function WinEShortcutRow(): JSX.Element { {said} )} - + /> ) } diff --git a/src/renderer/src/components/settings/settingsControls.test.ts b/src/renderer/src/components/settings/settingsControls.test.ts index 7f0e9744..ac166e4a 100644 --- a/src/renderer/src/components/settings/settingsControls.test.ts +++ b/src/renderer/src/components/settings/settingsControls.test.ts @@ -25,15 +25,27 @@ describe('settings controls', () => { }) // Still accented, since they are not buttons: the chosen card's ring, the - // chosen swatch's ring, the band slider's thumb, the progress bars drawn - // inside a style's preview card, and the Win+E switch (its existing - // exception: it hands a Windows shortcut to Prism). + // chosen swatch's ring, the band slider's thumb and the progress bars drawn + // inside a style's preview card. The Win+E switch was an exception until + // #318; it is the core's switch now, like every other. it('the accent is only on marks, never on a button', () => { - const allowed = ['cards.tsx', 'ColourSchemes.tsx', 'MediaPage.tsx', 'TransportMini.tsx', 'WinEShortcutRow.tsx'] + const allowed = ['cards.tsx', 'ColourSchemes.tsx', 'MediaPage.tsx', 'TransportMini.tsx'] const worn = files.filter(({ src }) => ACCENT.test(src)).map(({ f }) => f) expect(worn.filter((f) => !allowed.includes(f))).toEqual([]) }) + // EVERY SWITCH IS THE CORE'S (#318; owner, 2026-10-07: "toggles differ in + // look"). The Win+E row and the phone server each drew their own, the accent + // with a white knob, beside the core's. No file in the renderer may draw a + // switch of its own: one look, and the core decides it. + it("every switch in the app is the core's", () => { + const root = join(__dirname, '..', '..') + const own = (readdirSync(root, { recursive: true }) as string[]) + .filter((f) => /\.tsx$/.test(f) && !/\.test\.tsx$/.test(f)) + .filter((f) => /role="switch"/.test(readFileSync(join(root, f), 'utf8'))) + expect(own).toEqual([]) + }) + it('Default apps and Clear are row buttons', () => { const explorer = files.find(({ f }) => f === 'ExplorerPage.tsx')!.src expect(explorer).toMatch(/id="default-apps"[\s\S]{0,120}className=\{ROW_BUTTON\}/) diff --git a/tools/e2e/run.mjs b/tools/e2e/run.mjs index 036981dc..3b25caae 100644 --- a/tools/e2e/run.mjs +++ b/tools/e2e/run.mjs @@ -13846,6 +13846,27 @@ const settingsLookOf = (win) => } }) +// EVERY SWITCH IS ONE SWITCH (#318; owner, 2026-10-07: "toggles differ in +// look i like the teal with black not the green and white"). The switches on +// the page, grouped by state: each group must be ONE look (the button's and +// the knob's classes, the track's and the knob's computed colours). The Win+E +// row drew its own, the accent with a white knob, beside the core's. +const switchLooksOf = (win) => + win.evaluate(() => { + const bg = (el) => getComputedStyle(el).backgroundColor + return [...document.querySelectorAll('[data-settings-page] [role="switch"]')].map((sw) => { + const knob = sw.firstElementChild + return { + label: sw.getAttribute('aria-label'), + on: sw.getAttribute('aria-checked') === 'true', + look: JSON.stringify([sw.className, knob?.className ?? '', bg(sw), knob ? bg(knob) : '']), + // The markup with the state's own colour classes taken out: the same + // for every switch drawn by the one component, on or off. + shape: JSON.stringify([sw.className, knob?.className ?? ''].map((c) => c.split(/\s+/).filter((t) => !/^(enabled:hover:|disabled:opacity-)?(bg|brightness|opacity)-/.test(t)).join(' '))) + } + }) + }) + /** * THE SETTINGS LOOK (#292; owner, 2026-10-05: the approved v1 "Grouped cards", * with no accent bar on the chosen rail item). Every page in a dark and a @@ -13914,6 +13935,23 @@ async function settingsLookScenario(fixtures) { await win.mouse.move(5, 5) await sleep(450) const name = view ? `${page}-${view}` : page + if (page === 'explorer') { + const sws = await switchLooksOf(win) + for (const state of [true, false]) { + const group = sws.filter((x) => x.on === state) + const looks = [...new Set(group.map((x) => x.look))] + ok( + looks.length <= 1, + `${scheme} explorer: every ${state ? 'on' : 'off'} switch has one look (${group.map((x) => x.label).join(', ')}: ${looks.join(' | ')})` + ) + } + ok(sws.some((x) => x.on), `${scheme} explorer: an on switch is measured (${sws.filter((x) => x.on).map((x) => x.label).join(', ')})`) + // Win+E is off and unavailable outside a packaged build, so it cannot + // be measured on: it is held to the same markup as the others, which + // is the core's component and so the core's on look. + const winE = sws.find((x) => x.label === 'Open in place of File Explorer') + ok(!!winE && sws.every((x) => x.shape === winE.shape), `${scheme} explorer: the Win+E switch is the core's (${winE?.shape})`) + } const m = await settingsLookOf(win) ok(m.label >= 4.5 && m.sub >= 4.5, `${scheme} ${name}: label and subtext read on the panel (${m.label.toFixed(1)}:1, ${m.sub.toFixed(1)}:1)`) ok(m.icon >= 3, `${scheme} ${name}: the icon reads 3:1 on its tile (${m.icon.toFixed(1)}:1)`)