diff --git a/CLAUDE.md b/CLAUDE.md index 1d884993..f471b663 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -825,8 +825,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 222becf4..85792dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prism", - "version": "0.95.0", + "version": "0.95.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prism", - "version": "0.95.0", + "version": "0.95.1", "license": "MIT", "dependencies": { "@codemirror/commands": "^6.11.0", diff --git a/package.json b/package.json index e66be39d..c182ff68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prism", - "version": "0.95.0", + "version": "0.95.1", "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 a57bb7ee..f296637b 100644 --- a/tools/e2e/run.mjs +++ b/tools/e2e/run.mjs @@ -14292,6 +14292,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 @@ -14360,6 +14381,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)`)