<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppij.git/zellij-server/src/tab/unit, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppij.git/atom/zellij-server/src/tab/unit?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppij.git/atom/zellij-server/src/tab/unit?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/'/>
<updated>2026-08-12T14:21:04Z</updated>
<entry>
<title>sl0p: hot-reload status_bar_button_style</title>
<updated>2026-08-12T14:21:04Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T14:21:04Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=78174b714461e510c6f8e703b29b796c443a35ee'/>
<id>urn:sha1:78174b714461e510c6f8e703b29b796c443a35ee</id>
<content type='text'>
The option was only read when a client connected, so changing it needed a new
session -- inconsistent with gap/padding/title_alignment, which apply live, and
the kind of inconsistency that reads as "my config change did nothing".

Threaded through update_frame_layout, the existing vehicle for the fork's
pane_frames options: Tab -&gt; tiled/floating containers -&gt; each pane's Style,
including suppressed panes so a hidden stack member is styled correctly when it
swaps back into view.

Verified by editing config.kdl against a running session and watching
1:Approve flip to [Approve] and back, then confirming a click still resolves
afterwards -- the button widths change with the style, so a stale layout would
have silently mis-targeted clicks.
</content>
</entry>
<entry>
<title>sl0p: separate the hover resize hint from the hover highlight</title>
<updated>2026-08-12T12:09:21Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T12:09:21Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=ea500ea70aa77da57870e8272bab593887819b0b'/>
<id>urn:sha1:ea500ea70aa77da57870e8272bab593887819b0b</id>
<content type='text'>
Upstream's mouse_hover_effects governs two unrelated things: the frame highlight
on hover, and the "Ctrl &lt;MouseScroll&gt; or &lt;drag borders&gt; to resize" hint. Its own
comment in default.kdl admits as much. So silencing a beginner reminder you have
outgrown also costs you a highlight that stays useful indefinitely.

mouse_hover_help_text (default true) gates only the hint. One line in
execute_send_to_terminal; the highlight is set in execute_update_hover and is
untouched.

Config-file only, deliberately. The option has no protobuf slot, so it does not
cross the client/server IPC contract -- the roundtrip test asserts exactly that,
with the fixture set to None and a comment explaining why. It still works from
the config file because the server parses the file itself and Options::merge
keeps the file value when the client sends None; a CLI flag would be silently
ignored.
</content>
</entry>
<entry>
<title>sl0p: stop hide_default_pane_names blanking the plugin API</title>
<updated>2026-08-11T19:30:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T19:30:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=568c45ba47dd27c4fdfcf6bd8303beeb853b93d4'/>
<id>urn:sha1:568c45ba47dd27c4fdfcf6bd8303beeb853b93d4</id>
<content type='text'>
`hide_default_pane_names` is a frame-rendering option, but it was applied
inside current_title() -- which is also what fills PaneInfo.title. Every plugin
that lists panes therefore received an empty string for any pane whose program
had never set a title.

For the bundled session manager that is untidy. For a fuzzy pane finder it is
broken: the rows are identical AND unsearchable, so the plugin's whole purpose
stops working.

The blanking moves to the frame, which already had a blank_title concept, and
current_title() goes back to reporting a real title to everything that asks.
Frames still show nothing for an unnamed pane, named panes still show their
name, and PaneInfo reports "Pane #N" again.

Changes a value rather than adding a field, so it needs no protobuf
regeneration.

Only observable without a shell that sets a title: verified by disabling the
zsh hook, where the pane finder previously listed three identical blank rows
and now lists Pane #1/#2/#3. That masking is also why this went unnoticed --
and why I flagged it twice without confirming it was actually reachable.
</content>
</entry>
<entry>
<title>sl0p: add a minimal style for the stacked-pane picker</title>
<updated>2026-08-11T18:08:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T18:08:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=3418e8218d91d916b3b2a45ac81d3032c27575c6'/>
<id>urn:sha1:3418e8218d91d916b3b2a45ac81d3032c27575c6</id>
<content type='text'>
The boxed entries read as clutter: every entry bracketed in vertical bars, and
the active one marked by a "&gt; " that sits outside its own cell.

`stack_list_style "minimal"` drops both. The active entry is shown by
inverting it instead, which needs no glyph and no column, and the padding is
moved inside the styled run so the inverted block covers the whole cell rather
than leaving unstyled gaps at its edges. Four fewer columns of decoration per
entry, so more fit per row in grid mode.

Uses ribbon_selected rather than text_selected. Themes give ribbon_selected
real contrast because it marks the selected item elsewhere in zellij's UI --
gruber yellow here, white-on-blue in the TempleOS theme -- whereas
text_selected's background is 40,40,40 against a 24,24,24 terminal and read as
essentially no highlight when tried.

Decoration width comes from the style itself, so Tab's column arithmetic and
the renderer cannot disagree about how wide an entry is. Default stays boxed,
so nothing changes for anyone who does not ask.
</content>
</entry>
<entry>
<title>sl0p: resolve responsive layouts from current state, not a remembered position</title>
<updated>2026-08-11T17:35:03Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T17:35:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=29eadcc1f06bfc5b06ea8a56aa1a18da318a049f'/>
<id>urn:sha1:29eadcc1f06bfc5b06ea8a56aa1a18da318a049f</id>
<content type='text'>
Every bug this feature has had came from the same place: it remembered the swap
layout position that was active before collapsing, and replayed it to expand.
Remembered positions stop applying when the pane count changes, are only stable
while nothing is inserted, and a failed apply left the state claiming a
transition that never happened.

Both directions now resolve fresh. Collapsing picks the configured layout, or a
synthesized stack if that one is missing OR cannot currently apply -- a
`stacked` layout with min_panes=4 and three panes open used to be found, fail
its constraint, and give up. Expanding picks BASE when it still applies, since
that reproduces the user's actual layout, and otherwise a synthesized grid.
Both chains end in an unconstrained layout, so neither can come up empty.

The only remaining state is one bool, flipped only after a layout was applied.

Also stops consulting is_tiled_damaged. It is meant to mean "the user
rearranged panes by hand" but is equally set when a relayout finds nothing
applicable, which happens on the first pane added to a tab with no usable swap
layout and then never clears -- it disabled this feature in four distinct
situations. It protected little regardless: expanding applies BASE or a grid,
so a manual arrangement does not survive a round trip either way.

Synthesized layouts are created once and reused; the test asserts the layout
list does not grow across repeated collapse/expand cycles, since they are now
appended lazily in both directions.
</content>
</entry>
<entry>
<title>sl0p: keep expanding possible after the pane count changes</title>
<updated>2026-08-11T17:16:39Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T17:16:39Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=731c011d79b375329ebb40e80fadf1c884e778f1'/>
<id>urn:sha1:731c011d79b375329ebb40e80fadf1c884e778f1</id>
<content type='text'>
A tab restores into whatever layout was active before it collapsed, which for a
bare layout is the implicit BASE. BASE is pinned to ExactPanes of the count it
was written with, so adding one pane while collapsed left nothing to expand
into and the tab stayed stacked on every subsequent widening.

Two fixes. When the stored layout no longer applies, synthesize a grid to
spread the panes out rather than leaving them stacked -- a wide terminal is not
a state that retries, so failing here strands the tab for good.

And only consume the restore position once a layout has actually been applied.
It was cleared unconditionally, so after a failed restore the tab believed it
had expanded while visibly still collapsed, and never tried again -- which is
why this looked like "expand stopped working" rather than "expand did nothing
once".
</content>
</entry>
<entry>
<title>sl0p: synthesize a stack layout when none exists to collapse into</title>
<updated>2026-08-11T15:00:37Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T15:00:37Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=508b5aa87ced4c7aab38bcb0e34c4158e5b4e101'/>
<id>urn:sha1:508b5aa87ced4c7aab38bcb0e34c4158e5b4e101</id>
<content type='text'>
The collapse selects a swap layout by name, and a custom layout file only gets
swap layouts from a hand-written sibling "&lt;layout&gt;.swap.kdl". So for most
layouts there was nothing to select and the feature silently did nothing --
requiring a boilerplate file per layout to use it at all.

Now a missing layout is synthesized: every pane flattened into one stack, in
layout order. Appended rather than inserted, so it cannot shift the BASE layout
at position 0, which is what the collapse restores back to.

Synthesized lazily, at the first collapse, rather than up front: a stack sitting
in the swap cycle would let auto_layout apply it spontaneously when a pane is
added, stacking panes on a perfectly wide terminal.

Also scopes the damaged-layout guard. With no swap layouts, relayout finds
nothing applicable on the first pane added, gives up and marks the tab damaged
-- permanently, and with nothing to do with user intent. Honouring that flag
disabled the collapse for exactly the bare layouts this fallback targets, so it
is now only consulted when the tab has swap layouts the user actually defined.
</content>
</entry>
<entry>
<title>sl0p: clear the stale picker region when the layout re-flows</title>
<updated>2026-08-11T14:45:20Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T14:45:20Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=a433a998139ad36c9725c8dd3598cd88306ad89a'/>
<id>urn:sha1:a433a998139ad36c9725c8dd3598cd88306ad89a</id>
<content type='text'>
Adding a pane left old borders on screen until a resize forced a full redraw.

Two causes. The picker draws each entry as its own chunk, so the space between
and around the entries was never painted and kept the previous frame's
contents; and adding a pane really does re-flow the whole grid, because a
freshly created pane has no title yet and its "Pane #N" fallback is far wider
than a named one, which widens every cell and drops the column count.

Separately, refresh_or_dissolve_stack_list changed the reserved row count
without forcing a clear, so when the list got shorter the visible pane moved up
and the rows it vacated kept their old contents.

Blanks only the spans no cell covers, never a cell's own columns: chunks are
NOT composited last-wins, so blanking the whole region erased the entries
instead of the stale contents under them -- the first attempt at this fix drew
exactly one entry.

stack_list_row_gaps is a free function so the covering invariant is testable:
gaps and cells must tile each row exactly, with no overlap, across a sweep of
row caps, label widths and member counts.
</content>
</entry>
<entry>
<title>sl0p: optionally render the stacked-pane picker as a grid</title>
<updated>2026-08-11T14:33:46Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T14:33:46Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=4e001669f5a9dcc883f58bf0c3710c7460ae93cc'/>
<id>urn:sha1:4e001669f5a9dcc883f58bf0c3710c7460ae93cc</id>
<content type='text'>
`stack_list_layout "grid"` packs picker entries several per row, which turns
stack_list_max_rows from a count into a space budget: eight panes in a wide
terminal now fit on one row instead of spending three and hiding five.

The entry renderer already drew each entry as a single chunk at its own x/y,
sized by its geom and centring its own label, so a grid needed no rendering
changes at all -- only the loop assigning every entry the same x and the full
width. Column count is derived from the widest label rather than configured, so
it adapts to the titles; one column is the floor and reproduces the previous
full-width rows byte for byte.

The row -&gt; member mapping now has a column axis, and it had already caused one
wrong-pane bug when two consumers disagreed. So the layout is computed once,
by one function, into explicit cells; the renderer, the reserved-row budget and
the mouse hit-test all read those same cells rather than each doing their own
arithmetic. Windowing is by row, so a grid scrolls a whole row at a time and
degrades to the old per-member window when there is one column.

Cells are separated by a two-column gap: without it the focused entry's "&gt; "
marker sits flush against the previous cell and reads as belonging to it.
</content>
</entry>
<entry>
<title>sl0p: click the picker's indicator row to expand a capped stack</title>
<updated>2026-08-11T14:19:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-11T14:19:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=c51e5caa6d5c9368db0ddbd6b406047dfc451e20'/>
<id>urn:sha1:c51e5caa6d5c9368db0ddbd6b406047dfc451e20</id>
<content type='text'>
The cap made the picker a scrolling viewport -- everything stays reachable by
moving focus -- but there was no way to see the whole stack at once.

Clicking "+N more" expands it to every member; the indicator becomes "- fewer"
and collapses it again. Collapse gets its own label because an invisible way
back out is not an affordance.

Hangs off the indicator row because that row is already reserved, already
rendered, and resolves to no member, so it is free real estate. It is also why
this needs no new keybinding: mouse handling is entirely server-side, while
Action is a protobuf enum we cannot extend without protoc.

A click rather than hover, as expanding changes the reserved row count and so
resizes the visible pane's PTY -- acceptable once per deliberate click, not on
every mouse move.

All three consumers of the row -&gt; member mapping (renderer, reserved-row
budget, mouse hit-test) now go through stack_list_window_for, so expansion
cannot desync them the way the cap desynced the hit-test in 844617b4.
</content>
</entry>
</feed>
