At spacing 0 a row of panels read as separate tiles, each closing its own
silhouette. BarPanel can now drop either cut corner — rightChamfer (top-right)
and leftChamfer (bottom-left), the only two the shape cuts — so an edge that a
neighbour butts against runs square into it. The bar turns off the host's right
and the vitals' left; the tray keeps both, since the spacer between them is not
a panel and that edge is free.
A side with its chamfer off also draws a connector: the shared edge restroked
in accent at outlineWidth + 2, so the join reads as a deliberate seam rather
than two outlines that happen to touch. The outline's own width becomes a
property so the connector can be defined against it instead of as a second
literal.
The lower-right accent strip takes accentLineWidth, the same slug-derived width
the upper-left one already used, instead of its own Math.min(49, width / 3).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU
Two causes, both outside the panel animation itself.
bodyBottom and bodyEndPadding are animated reals, so their sum spends the tail
of every transition on a fraction. A layout rounds that up, then drops a pixel
the moment the animation lands on its exact value — a 1px hop after the motion
has visibly finished. implicitHeight and targetHeight now round.
The rest was the surface. Hyprland animates layer-surface resizes (animations
enabled, `layers` left at its default), and the deferred shrink put that resize
exactly where the panel motion ended. The bar is now sized once to the expanded
rail via the new BarPanel.expandedHeight and never resizes; only exclusiveZone
tracks the state, so the desktop still reflows once per toggle, at the start.
That retires barHeight, the content-height handler and the 340ms shrink timer.
A surface that stays tall would swallow clicks across the screen while the rail
is collapsed, so input is masked to the panel row.
Instrumenting BarPanel per frame ruled the panels themselves out first: slug
and summary hold the same absolute y through an entire collapse.
The bar and scrim also get their own layer namespaces. Nothing depends on them
yet; they are the handle for a layerrule that would exempt the rail from
compositor animations without catching the launchers, which share the default
"quickshell" namespace and do want their fade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU
A full-screen scrim carrying the dense bar's drafting grid, shown while the
rail is expanded and faded out with it.
It sits on the TOP layer while the bar moves to OVERLAY. Both on one layer
would stack by surface creation order, which is not something to rely on; a
layer apart makes "above windows, below the bar" a guarantee. The mask is an
empty Region, so the scrim takes no clicks and reserves nothing.
The grid is the dense bar's, at twice the spacing and with registration
crosses on every other intersection. Its 0.018 opacity was tuned against a
near-black panel and is invisible over a scrim on lit windows, so grid and
crosses are both properties rather than constants, and the lines take a
desaturated accent derived from the palette instead of plain text colour.
Cross geometry rounds with Math.floor on both the mark's offset and the bars
inside it. anchors.*Center halves the box unfloored, which put an even-sized
mark half a pixel off the 1px rule it registers against. crossThickness is in
steps for the same reason: 1 -> 1px, 2 -> 3px, 3 -> 5px, since only an odd
width straddles a rule symmetrically.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU
A second shell chrome under dotfiles/quickshell/hyprchrome, built around a
BarPanel that carries TWO renderings of its data: the default children are the
expanded detail view, `summary` the terse one shown while collapsed. Both stay
bound to the same sources, so the densities cannot disagree, and the panel
cross-fades between them while its height animates.
Panels: HostPanel (hostname, user, timezone, clock), VitalsPanel (CPU load and
temperature, memory, GPU load and temperature, all metered), TrayPanel (system
tray, self-sizing). HyprChromeBar pins them to DP-2 and owns `expanded` for the
whole rail — SUPER A, via GlobalShortcut "chrome".
GPU busy comes off sysfs rather than the node_exporter scrape VitalsData
already does: the hwmon collector carries the card's temps, power and clocks
but not its utilisation.
The bar's height binds to each panel's `targetHeight` — where it will settle,
not where the animation currently is — because the exclusive zone is
window-sized by default, and binding to the animated height relayouts every
tiled window on the output twelve times per toggle. The zone follows the target
immediately so the desktop reflows once, at the start; the surface itself
shrinks only after the panels finish, or it would clip them mid-animation.
DebugWindow stages a widget in the middle of the secondary monitor
(SUPER CTRL D), masked so only the staged widget takes pointer input.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEtXvseVb5gwAtKNhFx2PU