diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 25c816bd..2f423cd4 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -20009,12 +20009,19 @@ html[data-session-list='sidebar'][data-sidebar='collapsed'] .session-sidebar .ta display: grid; grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); align-content: start; + /* The strip's own align-items: flex-start applies to grid items too; a + cell must stretch, or the active cell (taller with its actions) breaks + its row. */ + align-items: stretch; gap: 4px 6px; } .session-tabs-host > .session-tabs.tabs-ledger > .session-tab { --ledger-bar: var(--green); min-width: 0; + /* Tall enough for the active cell's actions, so every row is one height. */ + min-height: 30px; + box-sizing: border-box; gap: 7px; padding: 4px 8px 4px 11px; border: 1px solid var(--control-border); @@ -20057,6 +20064,16 @@ html[data-session-list='sidebar'][data-sidebar='collapsed'] .session-sidebar .ta .session-tabs-host > .session-tabs.tabs-ledger > .session-tab:is(.tab-agent-exited, .session-tab--web, :has(> .tab-status.stopped)) { --ledger-bar: var(--text-muted); } + + /* The active cell has to win against a grid of look-alike cells. The skin's + active rule (1px accent border, no shadow, both !important) is too quiet + here, so an inset second pixel doubles the border without resizing the + cell, and the status bar turns accent. */ + .session-tabs-host > .session-tabs.tabs-ledger > .session-tab.active { + background: rgba(var(--accent-rgb), 0.2) !important; + box-shadow: inset 0 0 0 1px var(--accent), inset 4px 0 0 var(--accent) !important; + color: var(--text) !important; + } } /* ============================================================ @@ -20355,6 +20372,60 @@ html[data-header-stats='tiles'] .header-plan-usage .pu-win-idle { opacity: 0.55; } +/* --- Header buttons next to the clustered stats ------------------------ */ +/* Bare 16px glyphs beside 36px tiles read as a different design. In Tiles the + header's icon buttons take the tile box (same height, border and fill, a + larger icon); in Compact they become round chips the height of the pills. + Only the box changes: the marker classes still hide them, and the gear + keeps its quarter turn on hover, on the glyph rather than the whole box. */ +html[data-header-stats='tiles'] .header-right > .btn-icon-header, +html[data-header-stats='compact'] .header-right > .btn-icon-header { + box-sizing: border-box; + background: var(--control-bg); + border: 1px solid var(--control-border); + color: var(--text-dim); +} + +html[data-header-stats='tiles'] .header-right > .btn-icon-header { + width: 36px; + height: 36px; + border-radius: 7px; +} + +html[data-header-stats='compact'] .header-right > .btn-icon-header { + width: 26px; + height: 26px; + border-radius: 13px; +} + +html[data-header-stats='tiles'] .header-right > .btn-icon-header > svg { + width: 18px; + height: 18px; +} + +html[data-header-stats='compact'] .header-right > .btn-icon-header > svg { + width: 15px; + height: 15px; +} + +html[data-header-stats='tiles'] .header-right > .btn-icon-header:hover, +html[data-header-stats='compact'] .header-right > .btn-icon-header:hover { + background: var(--control-bg-hover); + border-color: var(--control-border-hover); + color: var(--text); + transform: none; +} + +html[data-header-stats='tiles'] .header-right > .btn-icon-header > svg, +html[data-header-stats='compact'] .header-right > .btn-icon-header > svg { + transition: transform var(--transition-smooth); +} + +html[data-header-stats='tiles'] .header-right > .btn-settings:hover > svg, +html[data-header-stats='compact'] .header-right > .btn-settings:hover > svg { + transform: rotate(45deg); +} + /* 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 6e78f975..180b6c3f 100644 --- a/test/header-stats-style.test.ts +++ b/test/header-stats-style.test.ts @@ -205,6 +205,23 @@ describe('header stats wiring (static)', () => { ); }); + it('lays a tile out as three rows, so its height never depends on the font', () => { + // A bar laid over the bottom of a fixed-height tile is what let a taller + // system mono (SF Mono) push the value into it. + expect(css).toMatch(/grid-template-rows: 9px 14px 2px;\s*row-gap: 2px;/); + const tiles = css.slice(css.indexOf('/* --- Tiles: label over value')); + const bar = tiles.slice(tiles.indexOf("html[data-header-stats='tiles'] .header-system-stats .stat-bar,")); + expect(bar.slice(0, bar.indexOf('}'))).not.toContain('position: absolute'); + expect(bar.slice(0, bar.indexOf('}'))).toContain('grid-row: 3;'); + }); + + it('gives the header buttons the tile box beside the tiles', () => { + expect(css).toMatch( + /html\[data-header-stats='tiles'\] \.header-right > \.btn-icon-header \{\s*width: 36px;\s*height: 36px;/ + ); + expect(css).toMatch(/html\[data-header-stats='tiles'\] \.header-right > \.btn-icon-header > svg \{\s*width: 18px;/); + }); + 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'" diff --git a/test/tab-clusters.test.ts b/test/tab-clusters.test.ts index 59423c15..bf0521aa 100644 --- a/test/tab-clusters.test.ts +++ b/test/tab-clusters.test.ts @@ -287,6 +287,15 @@ describe('tab layouts by case and ledger (static)', () => { expect(before).not.toContain('.tabs-ledger'); }); + it('keeps every ledger row one height and makes the active cell stand out', () => { + const ledger = css.slice(css.indexOf('@media (min-width: 768px) {\n .session-tabs-host > .session-tabs.tabs-ledger {')); + expect(ledger).toContain('align-items: stretch;'); + expect(ledger).toContain('min-height: 30px;'); + expect(ledger).toMatch( + /\.tabs-ledger > \.session-tab\.active \{[^}]*box-shadow: inset 0 0 0 1px var\(--accent\), inset 4px 0 0 var\(--accent\) !important;/ + ); + }); + it('dissolves the boxes into the one chip row on phones', () => { expect(mobileCss).toMatch( /:where\(\.header\) \.session-tabs-host > \.session-tabs\.tabs-clusters > \.tab-cluster \{\s*display: contents;/