diff --git a/CLAUDE.md b/CLAUDE.md
index e75e12cf..9eef5ce9 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -99,6 +99,13 @@ native ``, no Acrylic or Accent opacity slider. Spec and plan:
browser paints no ground; its address row, list, preview slot and status paint `--p-bg`, the places
panel (and `.browse-viewer-places`) `--p-side`, the Settings rail's and project tree's colour, one
coat each. Every catalogue theme's panel differs from its ground. The `sidebarGround` e2e holds both.
+ **THE ADDRESS ROW WEARS IT TOO** (#306; owner, 2026-10-07, of a mockup with the row in the panel
+ colour: "yes make this the same color as the sidebar"): back/forward/up/refresh, the address field,
+ preview and search, across the window (`.folder-browser > .browse-toolbar`, and
+ `.browse-viewer-toolbar` over an opened file), `--p-side`, its hairline kept, so only the list,
+ preview slot and status are `--p-bg`. The address field's fill and edge step off the ROW
+ (`--p-side-flat`), not the page: off the page they sat at 1.01:1 on the row on every dark style
+ (MEASURED). Near-black is still judged by the page (`nearBlackField(bg, text, row)`).
## Scope
diff --git a/package-lock.json b/package-lock.json
index c5797b71..fd813750 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "prism",
- "version": "0.93.0",
+ "version": "0.93.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "prism",
- "version": "0.93.0",
+ "version": "0.93.1",
"license": "MIT",
"dependencies": {
"@codemirror/commands": "^6.11.0",
diff --git a/package.json b/package.json
index af0b71d2..dba9f3bc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prism",
- "version": "0.93.0",
+ "version": "0.93.1",
"description": "A fast, universal media viewer for Windows.",
"author": "Max",
"license": "MIT",
diff --git a/src/renderer/src/components/browse/BrowseToolbar.tsx b/src/renderer/src/components/browse/BrowseToolbar.tsx
index 0b44730c..ad472486 100644
--- a/src/renderer/src/components/browse/BrowseToolbar.tsx
+++ b/src/renderer/src/components/browse/BrowseToolbar.tsx
@@ -199,7 +199,7 @@ export function BrowseToolbar(props: Props): JSX.Element {
name={crumb.name}
color="var(--p-tree-zip)"
size={14}
- bg="var(--p-control)"
+ bg="var(--p-field)"
/>
)}
{crumb.name}
diff --git a/src/renderer/src/components/browse/browse.css b/src/renderer/src/components/browse/browse.css
index c319973c..45e8a7fa 100644
--- a/src/renderer/src/components/browse/browse.css
+++ b/src/renderer/src/components/browse/browse.css
@@ -25,16 +25,22 @@
see-through coats are an opaque slab on glass): the address row, the list,
the preview slot and the status line --p-bg, the places panel --p-side.
The four cover the grid between them, with no gap. A peeking panel keeps
- its flat --p-side-flat (below), being laid over the list. */
+ its flat --p-side-flat (below), being laid over the list.
+ THE ADDRESS ROW WEARS IT TOO (#306; owner, 2026-10-07, of a mockup with
+ the row in the panel colour: "yes make this the same color as the
+ sidebar"): back, forward, up, refresh, the address field, the preview
+ toggle and search, across the whole window, so the sidebar colour runs
+ along the top and down the left and the list is the one --p-bg area.
+ Its hairline stays. Still one coat: the row is its own grid cell. */
.folder-browser > .browse-toolbar,
+.folder-browser > .browse-places {
+ background-color: var(--p-side);
+}
.folder-browser > .browse-list-area,
.folder-browser > .browse-preview-slot,
.folder-browser > .browse-status {
background-color: var(--p-bg);
}
-.folder-browser > .browse-places {
- background-color: var(--p-side);
-}
.folder-browser[data-preview] {
grid-template-columns: var(--browse-places-width) minmax(0, 1fr) var(
--browse-preview-width,
@@ -176,9 +182,9 @@
min-height: 48px;
flex-shrink: 0;
color: var(--p-text);
- /* No ground of its own (#294): it sits on the folder browser's ground, or
- on .browse-viewer-toolbar's over a file, and a second see-through coat
- of --p-bg made the address bar an opaque band on a glass style. */
+ /* No ground in this rule (#294): the folder browser paints its cell above
+ (#306: --p-side), .browse-viewer-toolbar paints it over a file, and a
+ second see-through coat made the address bar an opaque band on glass. */
font-family: 'Segoe UI', sans-serif;
font-size: 15px;
}
@@ -225,7 +231,8 @@
"make the url box more visible"). The breadcrumb sat on the toolbar with no
box, so only the search looked like somewhere to type. Both wear this: one
height, radius, fill and edge, from the --p-field tokens (lib/fieldColours:
- on a near-black ground a darker fill and an edge that carries the shape). */
+ on a near-black ground a darker fill and an edge that carries the shape).
+ Both are stepped off the ROW's ground, the sidebar colour (#306). */
.browse-field {
height: 36px;
border: 1px solid var(--p-field-edge);
diff --git a/src/renderer/src/components/browse/workspace.css b/src/renderer/src/components/browse/workspace.css
index 70968956..21e1c0ae 100644
--- a/src/renderer/src/components/browse/workspace.css
+++ b/src/renderer/src/components/browse/workspace.css
@@ -4,9 +4,11 @@
.browse-surface-host {
z-index: 1;
}
+/* Over an opened file the address row is the same row, so the same sidebar
+ colour (#306), one coat: the .browse-toolbar inside paints nothing here. */
.browse-viewer-toolbar {
flex-shrink: 0;
- background: var(--p-bg);
+ background: var(--p-side);
}
.browse-viewer-toolbar > .browse-toolbar {
height: 48px;
diff --git a/src/renderer/src/lib/fieldColours.test.ts b/src/renderer/src/lib/fieldColours.test.ts
index d0d4f0e4..42cc47e5 100644
--- a/src/renderer/src/lib/fieldColours.test.ts
+++ b/src/renderer/src/lib/fieldColours.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
-import { contrastOf, hintOn, isNearBlack, nearBlackField } from './fieldColours'
+import { contrastOf, controlFieldOn, hintOn, isNearBlack, nearBlackField } from './fieldColours'
import { STYLES, variablesFor } from './theme'
const byId = (id: string) => STYLES.find((s) => s.id === id)!
@@ -38,17 +38,36 @@ describe('the toolbar fields (#267)', () => {
expect(contrastOf('#707174', '#e7e7e8')).toBeLessThan(4.5)
})
- it('Void wears the dark field; Paper keeps the fill it had', () => {
+ it('Void wears the dark field; Paper the control step; both off the row (#306)', () => {
const v = variablesFor(byId('new-void'))
- expect(v['--p-field']).toBe('#060606')
- expect(contrastOf(v['--p-field-edge'], '#000000')).toBeGreaterThanOrEqual(1.6)
- expect(contrastOf(v['--p-field-edge'], '#000000')).toBeLessThan(1.8)
+ const vRow = v['--p-side-flat']
+ expect(vRow).toBe('#070707')
+ expect(v['--p-field']).toBe(nearBlackField('#000000', '#e8eaf0', vRow)!.fill)
+ expect(contrastOf(v['--p-field-edge'], vRow)).toBeGreaterThanOrEqual(1.6)
+ expect(contrastOf(v['--p-field-edge'], vRow)).toBeLessThan(1.8)
const p = variablesFor(byId('paper'))
- expect(p['--p-field']).toBe(p['--p-control'])
- expect(p['--p-field']).toBe('#e7e7e8')
+ expect(p['--p-field']).toBe(controlFieldOn(p['--p-side-flat'], '#1b1d21', true))
expect(p['--p-field-edge']).toBe(p['--p-divider'])
})
+ it('a near-black style is judged by its page, its field coloured off the row', () => {
+ // Aurora's page is near-black, its sidebar (#121419) is not: it keeps
+ // the quiet field, stepped off the sidebar.
+ const f = nearBlackField('#0b0d12', '#e8eaf0', '#121419')!
+ expect(f).not.toBeNull()
+ expect(contrastOf(f.fill, '#121419')).toBeGreaterThan(1.02)
+ expect(contrastOf(f.edge, '#121419')).toBeGreaterThanOrEqual(1.6)
+ })
+
+ it('every shipped style: the address field stands off the row it sits on (#306)', () => {
+ // MEASURED before: 1.01:1 on every dark style, the fill a step off the
+ // page and the row a step off it the other way.
+ for (const s of STYLES) {
+ const v = variablesFor(s, true)
+ expect(contrastOf(v['--p-field'], v['--p-side-flat']), s.id).toBeGreaterThan(1.03)
+ }
+ })
+
it('every shipped style: the field text and hint read on the field', () => {
for (const s of STYLES) {
const v = variablesFor(s, true)
diff --git a/src/renderer/src/lib/fieldColours.ts b/src/renderer/src/lib/fieldColours.ts
index 0e1864a2..f33583b2 100644
--- a/src/renderer/src/lib/fieldColours.ts
+++ b/src/renderer/src/lib/fieldColours.ts
@@ -78,16 +78,29 @@ export interface FieldColours {
/**
* The field's colours on a near-black `ground` (both flat hex), or null where
* the style's own control fill and edge stay in force.
+ *
+ * `row` is the ground the field actually SITS on, when that is not the page's
+ * (#306: the address row wears the sidebar colour, a step off the page). The
+ * style is judged near-black by its page, so a black style whose sidebar is
+ * lifted keeps its quiet field; the colours step off the row, or a fill one
+ * shade off the page vanished into a sidebar one shade off it the other way
+ * (MEASURED: 1.01:1 on Void and Aurora).
*/
-export function nearBlackField(ground: string, text: string): FieldColours | null {
+export function nearBlackField(ground: string, text: string, row: string = ground): FieldColours | null {
if (!isNearBlack(ground)) return null
return {
- fill: mixHex(ground, text, FILL_STEP),
- edge: lineAt(ground, text, EDGE),
- edgeHover: lineAt(ground, text, EDGE_HOVER)
+ fill: mixHex(row, text, FILL_STEP),
+ edge: lineAt(row, text, EDGE),
+ edgeHover: lineAt(row, text, EDGE_HOVER)
}
}
+/** The field's fill anywhere else: the control step (3.5% towards the text on
+ * dark, 9% on light), taken from the ground the field sits on. */
+export function controlFieldOn(row: string, text: string, light: boolean): string {
+ return mixHex(row, text, light ? 0.09 : 0.035)
+}
+
/** A hint (the search's placeholder) held to 4.5:1 on the field it sits in:
* the dim ink is chosen against the page, and on a light style's field fill
* it fell to 3.95:1 (MEASURED on Paper). Walks the hint towards the text
diff --git a/src/renderer/src/lib/theme.ts b/src/renderer/src/lib/theme.ts
index fc811106..2c07fc05 100644
--- a/src/renderer/src/lib/theme.ts
+++ b/src/renderer/src/lib/theme.ts
@@ -14,7 +14,7 @@ import {
withAlpha
} from 'prism-term-core/renderer/lib/colour'
import { accentAlphaOf, fillOf } from './accentAlpha'
-import { hintOn, nearBlackField } from './fieldColours'
+import { controlFieldOn, hintOn, nearBlackField } from './fieldColours'
import { THEME_STYLES } from './themes/catalogue'
import { migrateThemeStorage, RETIRED_KEY } from './themes/migrate'
import { RETIRED_MAP } from './themes/retired'
@@ -781,9 +781,12 @@ export function variablesFor(input: Style, opaque = false): Record {
places = await groundOf('.folder-browser > .browse-places')
list = await groundOf('.folder-browser > .browse-list-area')
- return places === side
+ row = await groundOf('.folder-browser > .browse-toolbar')
+ return places === side && row === side
}, 4000, 100)
ok(places !== null && places !== list, `${style}: the places panel's ground is not the list's (${places} vs ${list})`)
ok(places === side, `${style}: the places panel wears the sidebar colour (${places}, --p-side ${side})`)
+ // 3. THE ADDRESS ROW (#306; owner, 2026-10-07: "yes make this the same
+ // color as the sidebar"): the places panel's ground, not the list's,
+ // its hairline kept, and its field still a box on it.
+ ok(row === places, `${style}: the address row wears the places panel's ground (${row} vs ${places})`)
+ ok(row !== list, `${style}: and not the list's (${row} vs ${list})`)
+ const rowLook = await win.evaluate(() => {
+ const tb = document.querySelector('.folder-browser > .browse-toolbar')
+ const field = tb?.querySelector('nav.browse-path')
+ const s = tb ? getComputedStyle(tb) : null
+ return {
+ line: s ? { w: parseFloat(s.borderBottomWidth), c: s.borderBottomColor } : null,
+ field: field ? getComputedStyle(field).backgroundColor : null,
+ wide: tb ? Math.abs(tb.getBoundingClientRect().width - document.querySelector('.folder-browser').getBoundingClientRect().width) < 1 : false
+ }
+ })
+ ok(rowLook.wide, `${style}: the row runs across the whole browser`)
+ ok(rowLook.line && rowLook.line.w > 0 && rowLook.line.c !== row, `${style}: the row keeps its bottom hairline (${JSON.stringify(rowLook.line)})`)
+ ok(rowLook.field && rowLook.field !== row, `${style}: the address field is not the row's colour (${rowLook.field} on ${row})`)
await win.mouse.move(2, 400)
await sleep(300)
await win.screenshot({ path: join(SHOTS, `sidebar-ground-${style}.png`) })
+ const vw = await win.evaluate(() => window.innerWidth)
+ await win.screenshot({ path: join(SHOTS, `address-row-${style}.png`), clip: { x: 0, y: 0, width: vw, height: 220 } })
await settingsPage(win, 'appearance')
await win.waitForSelector('[data-settings-page] > nav', { timeout: 10000 })
const rail = await groundOf('[data-settings-page] > nav')
ok(rail === places, `${style}: and that is the Settings rail's ground (${rail})`)
+ ok(rail === row, `${style}: and the address row's (${row})`)
await win.click('[aria-label="Settings"]')
await sleep(300)
}
@@ -9954,13 +9977,17 @@ async function addressFieldScenario(fixtures) {
const cr = contrast(rgb(l.crumb), rgb(l.path.fill))
ok(cr >= 4.5, `${name}: a name reads on the field (${cr.toFixed(2)}:1)`)
}
- // On Void the fill is DARKER than the old control step (rgb 8,8,8), still
- // a step off the black, and a quiet edge carries the box (owner,
- // 2026-10-04: "the white border stands out too much on the black theme").
- ok(lum(rgb(v.path.fill)) < lum([8, 8, 8]) && lum(rgb(v.path.fill)) > 0, `Void: the field is a darker grey than before (${v.path.fill})`)
+ // On Void the fill is only a shade off the row it sits on, and a quiet
+ // edge carries the box (owner, 2026-10-04: "the white border stands out
+ // too much on the black theme"). Since #306 the row is the sidebar
+ // colour, and both step off THAT: a fill stepped off the page sat at
+ // 1.01:1 on the row (MEASURED), a field with no shape but its edge.
+ const vFill = contrast(rgb(v.path.fill), rgb(v.ground))
+ ok(vFill > 1.02 && vFill < 1.1, `Void: the field is a shade off the row, not a slab (${v.path.fill} on ${v.ground}, ${vFill.toFixed(3)}:1)`)
const edge = contrast(rgb(v.path.edge), rgb(v.ground))
ok(edge >= 1.5 && edge < 1.9, `Void: the field's edge is a quiet line, not a white frame (${edge.toFixed(2)}:1)`)
- ok(p.path.fill === 'rgb(231, 231, 232)', `Paper: the field wears the control fill (${p.path.fill})`)
+ const pFill = contrast(rgb(p.path.fill), rgb(p.ground))
+ ok(pFill > 1.1 && lum(rgb(p.path.fill)) < lum(rgb(p.ground)), `Paper: the field wears the control step off the row (${p.path.fill} on ${p.ground}, ${pFill.toFixed(3)}:1)`)
ok(v.path.fill !== p.path.fill, 'Void and Paper fill the field differently')
// A hover strengthens the edge and leaves the fill alone.
const pathBox = win.locator('.folder-browser [data-testid="browse-toolbar"] nav.browse-path')