diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 604b1985..7dd979dc 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -21354,6 +21354,22 @@ html[data-header-stats='compact'] .header-right > .btn-icon-header:hover { transform: none; } +/* An open grid or split keeps its pressed look in the box: the rules above + outrank the buttons' own accent (.tiles-open, .split-open), and that accent + is the only thing saying the next click closes the grid or the split. */ +html[data-header-stats='tiles'] .header-right > .btn-icon-header.btn-tile-grid.tiles-open, +html[data-header-stats='tiles'] .header-right > .btn-icon-header.btn-tile-grid.tiles-open:hover, +html[data-header-stats='tiles'] .header-right > .btn-icon-header.btn-split.split-open, +html[data-header-stats='tiles'] .header-right > .btn-icon-header.btn-split.split-open:hover, +html[data-header-stats='compact'] .header-right > .btn-icon-header.btn-tile-grid.tiles-open, +html[data-header-stats='compact'] .header-right > .btn-icon-header.btn-tile-grid.tiles-open:hover, +html[data-header-stats='compact'] .header-right > .btn-icon-header.btn-split.split-open, +html[data-header-stats='compact'] .header-right > .btn-icon-header.btn-split.split-open:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--accent-ink); +} + /* Before the first plan reading the chip holds a bare em dash; give it a tile of its own so it does not read as a stray mark between the tiles. */ html[data-header-stats='tiles'] .header-plan-usage:not(:has(.pu-row)) { diff --git a/test/header-stats-style.test.ts b/test/header-stats-style.test.ts index 9dd9e3c2..c784d7d9 100644 --- a/test/header-stats-style.test.ts +++ b/test/header-stats-style.test.ts @@ -23,6 +23,7 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { JSDOM } from 'jsdom'; +import postcss from 'postcss'; import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; const PUBLIC = join(process.cwd(), 'src/web/public'); @@ -225,6 +226,40 @@ describe('header stats wiring (static)', () => { expect(css).toMatch(/html\[data-header-stats='tiles'\] \.header-right > \.btn-icon-header > svg \{\s*width: 18px;/); }); + it('keeps the open Tiles and Split buttons pressed (accent) in the boxed styles, hover included', () => { + // The box rule (html[data-header-stats] .header-right > .btn-icon-header) + // outranks the buttons' own open-state accent, so nothing would show that + // the next click closes the grid or the split. + const found = new Map>(); + postcss.parse(css).walkRules((rule) => { + const decls: Record = {}; + rule.walkDecls((d) => { + decls[d.prop] = d.value; + }); + for (const sel of rule.selectors) found.set(sel, { ...found.get(sel), ...decls }); + }); + const missing: string[] = []; + for (const style of ['tiles', 'compact']) { + for (const open of ['btn-tile-grid.tiles-open', 'btn-split.split-open']) { + for (const hover of ['', ':hover']) { + // Starting from the box rule's own selector keeps it the more specific one. + const sel = `html[data-header-stats='${style}'] .header-right > .btn-icon-header.${open}${hover}`; + const d = found.get(sel); + if ( + d?.background !== 'var(--accent)' || + d['border-color'] !== 'var(--accent)' || + d.color !== 'var(--accent-ink)' + ) + missing.push(sel); + } + } + } + expect(missing).toEqual([]); + // Classic keeps the buttons' own rules: nothing scoped to it touches them. + expect([...found.keys()].filter((s) => s.includes("'classic'") && /tiles-open|split-open/.test(s))).toEqual([]); + expect([...found.keys()].filter((s) => /(tiles|split)-open/.test(s) && s.includes(':is('))).toEqual([]); + }); + it('stamps data-header-stats before first paint, tiles by default and classic on narrow screens', () => { expect(INDEX).toContain( "dataset.headerStats=(window.innerWidth<768||solo)?'classic':(H==='classic'||H==='compact')?H:'tiles'"