<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/sl0ppij/zellij-server/src/tab/unit, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/sl0ppij/zellij-server/src/tab/unit?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/sl0ppij/zellij-server/src/tab/unit?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-13T17:39:18Z</updated>
<entry>
<title>sl0p: make the split button work on stacked panes too</title>
<updated>2026-08-13T17:39:18Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T17:39:18Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=ae726b5bb3ff8e9e8f2bef0e9fbc9d4dcb62a79e'/>
<id>urn:sha1:ae726b5bb3ff8e9e8f2bef0e9fbc9d4dcb62a79e</id>
<content type='text'>
Reported still dead on a live session after the previous fix. Stacks move the
frame twice more, and neither move was in the hit-test:

- a stack list is drawn over the top rows of its visible member, pushing that
  pane's frame down. The renderer applies this as a frame geom override
  (reserved_top_rows); the click side did not, so every pane in a stack was
  clickable one row above its own title. frame_rect_with_top_reserved() now
  applies the same shift, before the gap inset, in the same order as render;
- a stack's collapsed members drew a button on their one-row title. Nothing
  could honour it -- that row is the stack's picker, and "split the pane that
  is not on screen" is not a thing to ask anyway. They no longer draw one, and
  the click side mirrors the condition. 120 snapshot lines change, every one
  of them a one-liner losing its button and nothing else (same width, same
  content otherwise; audited mechanically).

Both confirmed by reintroducing each bug and watching the check fail. A third
candidate fix -- resolving a click to the stack's visible member, on the theory
that get_pane_at could return a collapsed sibling -- turned out to be
unnecessary: collapsed members have one-row geoms, so position lookup already
lands on the right pane. Removed rather than kept "just in case".

The check itself was wrong twice before it was right, in the direction that
matters most (reporting failures that were its own): it now reads until the pty
goes quiet instead of for a fixed time, and parses only a freshly forced
repaint, so a half-arrived screen cannot look like a button drawn where it is
not. It clicks every button it can see, and runs against config/config.kdl as
well, whose stacks the minimal config never had.

When the two disagree, the server's own log settles it: log::info! in
intercept_split_button_click, read /tmp/sl0ppij-$UID/sl0ppij-log/.
</content>
</entry>
<entry>
<title>sl0p: make the split button visible, and clickable where it is drawn</title>
<updated>2026-08-13T16:46:58Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T16:46:58Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=2f0750285b1b174a552113c4aed9288f44985ae3'/>
<id>urn:sha1:2f0750285b1b174a552113c4aed9288f44985ae3</id>
<content type='text'>
The `+` shipped in 0.1.7 and 0.1.8 without ever appearing on a live session,
while rendering in 265 snapshots. Four faults, each enough on its own:

- title_line_without_middle computed the right side only inside the left
  side's and_then, so a pane with no title (hide_default_pane_names, the live
  default) got none at all;
- title_line_with_middle discarded the right side whenever the left was
  absent -- that is every frame in a session with a second client, which the
  live box always had, and "MY FOCUS AND:" on every frame was saying so;
- the title was budgeted before the button, so a title longer than the frame
  (any shell showing a path) consumed its columns. The button is anchored and
  hit-tested by pure geometry, so it cannot be the part that loses;
- the click hit-tested position_and_size() while the frame is drawn inset by
  the pane gap, putting the clickable columns two right of the glyph and a row
  above it at the top of the screen. Hence "the click works" and "nothing is
  drawn" both being true, and that click test proving nothing.

Adds Pane::frame_rect(), the rect the frame is really drawn in, which the
status bar buttons had already had to work around by hand.

Also fixes render_my_focus building its short form from separators that
Vec::append had already drained: 4 cells wide while reporting 6, a title line
two cells short of its pane. Found by asserting the width in the new test.

Verification, since unit tests have now been insufficient here four times:
split_button_is_drawn_where_it_is_hit_tested covers the combinations the live
config produces, and packaging/checks/split-button-live.py (make check-live)
reads the glyph off a real session, attaches a second client to force the
middle builder, and clicks the columns it found the glyph in. Both confirmed
to fail on the parent commit.
</content>
</entry>
<entry>
<title>sl0p: draw the split button always, not only on hover</title>
<updated>2026-08-13T12:46:03Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T12:46:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=a4a5e7b8287ef4e8421f4a7e9c659ac14eebafed'/>
<id>urn:sha1:a4a5e7b8287ef4e8421f4a7e9c659ac14eebafed</id>
<content type='text'>
The hover-gated version did not work. `mouse_is_hovering_over_pane` never became
true in a live session, so the `+` was invisible while its hit-test -- which is
geometric and never consulted hover -- kept working. Clickable but invisible is
the worst of both, and it is exactly the failure the shared
`split_button_columns()` was supposed to prevent: that guarantees render and
hit-test agree on WHERE, not on WHETHER.

Reported as "I hover over panes but don't see a +". My end-to-end test had
proved the click, not the button, and the passing snapshots only proved the
renderer works when the flag is set.

Why the flag is false live is still open and is written into the code comment:
the plumbing from `mouse_hover_pane_id` through `FrameParams` reads correctly,
and the config that reproduces it sets both `advanced_mouse_actions` and
`mouse_hover_effects`. Re-gate on hover only once that is understood AND shown
on a real session rather than in a snapshot.

265 snapshots updated: every pane frame now carries the button. 233 are
mechanically provable as button-only (the ` + ` replaces three horizontal glyphs
before a corner); the remaining 32 differ only by a header line offset in the
comparison, with the same substitution underneath.
</content>
</entry>
<entry>
<title>sl0p: a mouse affordance for splitting a pane</title>
<updated>2026-08-13T12:04:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T12:04:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=8e93e00e2c8f5692b6fbc200d989646bd088ddad'/>
<id>urn:sha1:8e93e00e2c8f5692b6fbc200d989646bd088ddad</id>
<content type='text'>
Switching panes, switching tabs, renaming, reordering and resizing all have a
mouse path in this fork. Spawning a pane did not: it was keyboard-only.

A `+` now appears at the right end of a pane's title line while the pointer is
over that pane, and clicking it splits the pane. Hover-driven rather than always
drawn, for the same reason the resize hint and the status bar button highlight
are: a permanent `+` on every frame is noise, and the pointer being there is
exactly when the button is useful.

  render  ui/pane_boundaries_frame.rs::render_split_button
  hit     tab/mouse_handler.rs::intercept_split_button_click
  shared  split_button_columns(), so what is clickable is what is visible

The button sits immediately left of the top-right corner and the existing scroll
and pin indications get the room that is left. That ordering is deliberate: a
fixed position relative to the pane's right edge is one the mouse handler can
hit-test directly, instead of re-deriving the whole title layout the way
`status_bar_button_at` has to.

The clicked pane is focused first, then the split is requested exactly as a
keybinding would (`PtyInstruction::SpawnTerminal` with `NoPreference`), so mouse
and keyboard produce the same arrangement and the split lands on the pane whose
button was clicked rather than whichever pane happened to be focused.

Not drawn on floating panes: their PIN control owns that corner and is
hit-tested by its drawn position, so anything to its right silently moves it out
from under the pointer. Caught by
test_ctrl_click_on_floating_pin_button_toggles_pin, which stopped pinning.

Verified on the test box by injecting real SGR mouse events into an attached
client's pty at 160x45:

  click on the button        1 -&gt; 2 panes
  click on the title         1 -&gt; 1
  click mid-frame            1 -&gt; 1

Two hover snapshots updated, and only those two: the `+` on the hovered pane
(including an unfocused one -- splitting a pane you are not in is useful).
zellij-server 1502 pass, zellij-utils 473 pass.
</content>
</entry>
<entry>
<title>sl0p: rename to sl0ppij behind a branding seam</title>
<updated>2026-08-12T18:02:26Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T18:02:26Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=f0a56905d9d64034797bae1a457ada8f659321b7'/>
<id>urn:sha1:f0a56905d9d64034797bae1a457ada8f659321b7</id>
<content type='text'>
Makes the fork installable and runnable beside a stock zellij: separate binary
name, config dir, cache dir, socket dir, log file and plugin URL scheme.

Everything user-visible is defined once in zellij-utils/src/branding.rs and
referenced from the ~15 places that used to hold a literal, rather than sed-ing
the tree -- upstream has 3300 commits of future rebases, and each touched
upstream line is a conflict forever. A conflict in branding.rs itself always
resolves the same way: keep our constant, take their code.

  binary        zellij            -&gt; sl0ppij
  config dir    ~/.config/zellij  -&gt; ~/.config/sl0ppij
  system cfg    /etc/zellij       -&gt; /etc/sl0ppij
  cache/data    ProjectDirs(Zellij) -&gt; ProjectDirs(foo.sl0p.sl0ppij)
  socket/tmp    /tmp/zellij-$UID  -&gt; /tmp/sl0ppij-$UID
  log           zellij-log/zellij.log -&gt; sl0ppij-log/sl0ppij.log
  plugin urls   zellij:tab-bar    -&gt; sl0ppij:tab-bar (zellij: still parses)

Two things deliberately NOT renamed:

  - The cargo package stays `zellij`; only [[bin]] is renamed. Renaming the
    package would ripple through every intra-workspace dep, the plugin API and
    the 0.45.0+sl0p1 version trick for a name nobody sees.

  - The $ZELLIJ* environment variables stay, because the ecosystem (shell
    prompts, "am I in a multiplexer" checks, our own pi-ext) looks for them. We
    now export $SL0PPIJ* alongside them, and those take precedence on read.
    branding::import_compat_env() folds an inherited ZELLIJ_CONFIG_DIR into its
    SL0PPIJ_ spelling at startup, before clap reads the environment -- clap can
    only be told one variable name per argument.

Snapshots: 14 updated, all of them the plugin URL scheme or a session message.
The insta churn (assertion_line metadata, trailing newlines) that a bulk
--force-update introduces across 340+ other snapshots was reverted, so the diff
here is only what actually changed.

Tests: zellij-utils 471 passed, zellij-server 1499 passed. branding.rs carries
its own tests, including one asserting no shipped default path contains the
string "zellij".

Verified live: sl0ppij creates /tmp/sl0ppij-1000 next to an existing
/tmp/zellij-1000, and `sl0ppij list-sessions` reports none while a stock zellij
reports three running sessions.
</content>
</entry>
<entry>
<title>sl0p: hot-reload status_bar_button_style</title>
<updated>2026-08-12T17:34:19Z</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/sl0ppi.git/commit/?id=13553ec312de430417e80929c0a795946d1e2212'/>
<id>urn:sha1:13553ec312de430417e80929c0a795946d1e2212</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-12T17:34:19Z</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/sl0ppi.git/commit/?id=a6ead47dcfeb8f019c1e98c826c4f4a09e05d174'/>
<id>urn:sha1:a6ead47dcfeb8f019c1e98c826c4f4a09e05d174</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-12T17:34:19Z</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/sl0ppi.git/commit/?id=9e085dee241b13cf01b8f7b5914729451e493a90'/>
<id>urn:sha1:9e085dee241b13cf01b8f7b5914729451e493a90</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-12T17:34:19Z</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/sl0ppi.git/commit/?id=276f57a5a5f1f935d1f83e7aa8a1abcffc9049af'/>
<id>urn:sha1:276f57a5a5f1f935d1f83e7aa8a1abcffc9049af</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-12T17:34:19Z</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/sl0ppi.git/commit/?id=78680f23adf4dc220abb78f0a5c2b529b9add138'/>
<id>urn:sha1:78680f23adf4dc220abb78f0a5c2b529b9add138</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>
</feed>
