fix(css): tablet By-case strip, compositor-only needs pulse, phone chip height

Tab Layout "By case" at tablet widths (600 to 767px, getDeviceType's
'tablet'): the case boxes could shrink in the tablet's one-row scrolling
strip, so they squeezed and wrapped their tabs inside themselves and every
tab past a box's first line was clipped under the fixed 48px header. The
boxes now dissolve into the chip row as they already do on phones, which
keeps the tablet's own 40px chip geometry. The rule sits in its own
600 to 767px block, not the 768px tablet block, so the desktop path at
768px and up (boxes keep their width, the strip wraps box by box) is
untouched.

Needs-you tile pulse: the glow animated box-shadow on the tile itself, so
the whole tile (DOM-rendered terminal rows included, the whole stage when
zoomed) was repainted every frame for as long as a prompt waited. The tile
keeps its red border; the glow is now a static inset shadow on a
.tile--needs::after overlay (inset because .tile is overflow: hidden and
clips an outer one, z-index 3 above .tile-attach, pointer-events none)
and only its opacity animates. The entering-and-needs animation shorthand
is gone, since it would now blink the whole tile's opacity, and reduced
motion keeps the static ring and hides the overlay.

Welcome chips: the phone block set 36px, below the 40px that styles.css
gives touch screens, and it wins on every phone, so phones got shorter
chips than tablets. It now restates 40px.

Tests: the tablet widths and the 768px boundary in tab-clusters, the
opacity-only pulse overlay in tile-grid-motion, and the phone chip height
in run-mode-ui; each fails against the previous CSS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-09 09:01:31 +02:00
parent 37ddcbe2f0
commit 41643f2b38
5 changed files with 222 additions and 21 deletions
+27 -3
View File
@@ -334,6 +334,27 @@ html.mobile-init .file-browser-panel {
}
}
/* Tab Layout "By case" at tablet widths, getDeviceType()'s 'tablet' (600 to
767px). The strip is the tablet block's one scrolling row and
updateTabOverflowMode() never wraps it here, so the boxes, which may shrink
below 768px, squeezed and wrapped their tabs inside themselves instead of
overflowing: the strip never scrolled, and every tab past a box's first
line was clipped under the fixed header. The boxes dissolve into the chip
row as on phones (the 599px block), which keeps the tablet's own 40px chip
geometry; a 44px box would hang below the 48px header. Ends at 767px, not
at the tablet block's 768: from 768 getDeviceType() says 'desktop', and the
desktop rule in styles.css (flex-shrink: 0, wrapping box by box) owns the
strip. */
@media (min-width: 600px) and (max-width: 767px) {
:where(.header) .session-tabs-host > .session-tabs.tabs-clusters > .tab-cluster {
display: contents;
}
:where(.header) .tab-cluster-label {
display: none;
}
}
/* Edge fade for the phone's header tab strip (used in the block below).
Registered so the keyframes can interpolate them as lengths; @property is
only valid at the top level, hence out here. */
@@ -1718,7 +1739,10 @@ html.mobile-init .file-browser-panel {
}
/* Only reached with the phone overview switched off. The primary spans the
column; the chips keep wrapping, a little taller for a thumb. */
column; the chips keep wrapping at the finger size styles.css gives touch
screens (40px, its pointer: coarse rule). This rule loads later at equal
specificity, so a lower value here made a phone's chips shorter than a
tablet's; restating 40px also covers a narrow window with a mouse. */
.welcome-primary {
width: 100%;
justify-content: center;
@@ -1727,8 +1751,8 @@ html.mobile-init .file-browser-panel {
}
.welcome-chip {
height: 36px;
border-radius: 18px;
height: 40px;
border-radius: 20px;
}
.history-show-more {
+33 -16
View File
@@ -4519,7 +4519,8 @@ html[data-skin="og"] .welcome-primary:hover {
color: var(--green);
}
/* Touch screens wide enough for this view (tablets) get finger-sized chips. */
/* Touch screens get finger-sized chips. Phones too: mobile.css's 599px block
loads later and wins at equal specificity, so it restates the 40px. */
@media (pointer: coarse) {
.welcome-chip {
height: 40px;
@@ -19741,29 +19742,50 @@ body.tile-grid-resizing--row * {
color: var(--accent, #4a9eff);
}
/* A permission prompt or question, visible across the room. Only the border
colour and a box-shadow animate: anything that moves layout would resize
the tile's PTY every frame. */
/* A permission prompt or question, visible across the room: a red border and
a glow that pulses for as long as the prompt waits, which can be hours.
Anything that moves layout would resize the tile's PTY every frame, and a
box-shadow animated on the tile itself repainted the whole tile (its DOM
rendered terminal rows included, the whole stage when zoomed) every frame.
So the glow is a STATIC shadow on an overlay and only its opacity
animates, which the compositor runs without repainting the tile. The
shadow is inset because .tile is overflow: hidden and would clip an outer
one on its own pseudo-element. Above .tile-attach (z-index 2), and never
in the way of a click. */
.tile.tile--needs {
border-color: var(--red, #ef4444);
}
.tile.tile--needs::after {
content: '';
position: absolute;
inset: 0;
z-index: 3;
border-radius: inherit;
pointer-events: none;
box-shadow: inset 0 0 10px 2px color-mix(in srgb, var(--red, #ef4444) 55%, transparent);
opacity: 0;
animation: tile-needs-pulse 1.6s ease-in-out infinite;
}
@keyframes tile-needs-pulse {
0%,
100% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--red, #ef4444) 0%, transparent);
opacity: 0;
}
50% {
box-shadow: 0 0 10px 2px color-mix(in srgb, var(--red, #ef4444) 55%, transparent);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.tile.tile--needs {
animation: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--red, #ef4444) 55%, transparent);
}
.tile.tile--needs::after {
display: none;
}
}
/* The tab of a tiled session (the focused tile's tab is .active as usual). */
@@ -20030,13 +20052,6 @@ body.tile-grid-resizing--row * {
animation-delay: calc(var(--tile-enter-index, 0) * 24ms);
}
/* A tile that needs you enters AND pulses (one animation property). */
.tile.tile--entering.tile--needs {
animation:
tile-enter 180ms cubic-bezier(0.2, 0.8, 0.2, 1) calc(var(--tile-enter-index, 0) * 24ms) both,
tile-needs-pulse 1.6s ease-in-out infinite;
}
@keyframes tile-enter {
from {
opacity: 0;
@@ -20121,7 +20136,7 @@ body.tile-grid-resizing--row * {
@media (prefers-reduced-motion: reduce) {
.tile-count-menu,
.tile.tile--entering,
.tile.tile--entering.tile--needs,
.tile.tile--needs::after,
.tile.tile--loading .tile-body::after,
.tile-grid-ghosts .tile.tile--leaving,
.tile-grid-ghosts.tile-grid-ghosts--release .tile.tile--leaving,
@@ -20981,7 +20996,9 @@ html[data-session-list='sidebar'][data-sidebar='collapsed'] .session-sidebar .ta
the routes ran through tabs, labels and box borders. Only a case wider than
the whole strip still wraps inside its box (max-width: 100%), and the strip
wraps around it too (_tabClustersWrapInside). The narrower screens keep
their scrolling strip. */
their scrolling strip: mobile.css dissolves the boxes into its chip row on
phones (599px block) and on tablets (600 to 767px block), since a box
allowed to shrink there wrapped its tabs out of the one-row strip. */
@media (min-width: 768px) {
.session-tabs-host > .session-tabs.tabs-clusters > .tab-cluster {
flex-shrink: 0;