<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppij.git/README.md, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppij.git/atom/README.md?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppij.git/atom/README.md?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/'/>
<updated>2026-08-12T16:44:40Z</updated>
<entry>
<title>sl0p: document --purpose and tab purposes</title>
<updated>2026-08-12T16:44:40Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T16:44:40Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=1c93b2f8e4bde3e812f71c513bedbe98ce08dc05'/>
<id>urn:sha1:1c93b2f8e4bde3e812f71c513bedbe98ce08dc05</id>
<content type='text'>
</content>
</entry>
<entry>
<title>sl0p: document workspace hints for consumers</title>
<updated>2026-08-12T15:06:29Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T15:06:29Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=82df0861b06529e881209e90a039dd86d1d7db20'/>
<id>urn:sha1:82df0861b06529e881209e90a039dd86d1d7db20</id>
<content type='text'>
</content>
</entry>
<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: add an indexed style for status bar buttons</title>
<updated>2026-08-12T14:15:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T14:15:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=c3764fca00c1d91c06612a33162b2354e9d42be0'/>
<id>urn:sha1:c3764fca00c1d91c06612a33162b2354e9d42be0</id>
<content type='text'>
`status_bar_button_style "indexed"` renders `1:Approve 2:Reject` instead of
`[Approve] [Reject]`, so the bar doubles as a key legend for a harness that binds
alt+1..9 to the same actions.

A config option rather than a protocol field: whether the numbers mean anything
depends on what the USER has bound, not on what the program knows. It needs no
wire change and so no version bump, and the click report still carries the id --
never the index -- so a program is unaffected by which style is in force.

The style is threaded into layout_status_bar, the one function the renderer draws
from and the hit-test resolves against. That is what makes this safe: changing a
button's rendered width necessarily changes its clickable span too, verified by a
test asserting span width equals the drawn width and that every drawn column
resolves back, for both styles.

Also fixes the web client's Style construction to read the option from config
rather than defaulting it -- my first pass claimed that code had no pane_frames
config available, which was wrong; it uses it two lines further down.
</content>
</entry>
<entry>
<title>sl0p: document the pane status bar protocol</title>
<updated>2026-08-12T13:30:11Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T13:30:11Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=f9376a5ab8708c95d6ba40f069c9c29dedf67421'/>
<id>urn:sha1:f9376a5ab8708c95d6ba40f069c9c29dedf67421</id>
<content type='text'>
Reference for programs wanting to drive the bar: the OSC 5577 verbs, escaping
rules, click reports, limits, rendering behaviour, persistence, and the security
model.

Both worked examples were run against a real session rather than written from
memory, which caught four bugs in my own samples:

- the Python reader kept a trailing buffer window, so a processed report was
  re-matched and every click fired twice
- it used tty.setraw, which also disables signals, leaving the sample unkillable
  with Ctrl-C
- it exited via traceback and left the bar behind; both samples now clear it on
  exit
- the shell reader echoed the report to the screen, and an `stty -echo` did not
  help because bash's `read -n` saves and restores terminal settings around each
  call. `read -s` is the fix.

Also verified and documented two behaviours I would otherwise have guessed at: a
BEL terminator works as well as ST, and the bar is not drawn when pane frames are
off (it lives in the frame row), reappearing when they return.
</content>
</entry>
<entry>
<title>sl0p: clickable action buttons in the pane status bar</title>
<updated>2026-08-12T13:18:33Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T13:18:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=549f052ae5451d57b58f5de22de57b2a586e6d23'/>
<id>urn:sha1:549f052ae5451d57b58f5de22de57b2a586e6d23</id>
<content type='text'>
Second slice of the status/action frame. A program declares buttons, and a click
is reported back to it:

    printf '\033]5577;1;buttons;approve:Approve;reject:Reject\033\\'
    -&gt; click -&gt; program's stdin receives  OSC 5577 ; 1 ; click ; approve  ST

The report is a FIXED format carrying only the id, never bytes the payload chose,
and ids are restricted to [A-Za-z0-9_-]. The sequence that creates a button is
in-band and untrusted -- `cat` of a hostile file emits it as readily as a
cooperating program -- so a click that could send arbitrary bytes would let
viewing a file inject into the stdin of whatever is running, a shell included.
Labels are '%'-escaped since the wire format is ';'-separated, and both the
button count and id length are capped because the list lives as long as the pane.

Positions come from one layout_status_bar function that the renderer draws from
and the hit-test resolves against. Computing that geometry twice is how the
stacked-pane picker once selected the wrong pane; a test asserts every drawn
column resolves back to the button drawn there, across 100 widths.

Two things the drive caught that unit tests could not:

- The first delivery path pushed onto grid.pending_messages_to_pty, which is
  only drained when the PANE produces output -- so a click on an idle program sat
  in the queue forever. The write now happens at the tab level, which owns the
  senders.
- The hit-test derived the frame row from the pane's raw geometry, but this fork
  draws the frame inset by gap_offset, so with `gap 1` every click missed by one
  row. It now derives from the CONTENT rect, which is by definition the inside of
  the drawn frame and therefore the same columns the renderer lays out.

Buttons serialise with the session like the status text, so a resurrected pane
comes back with its buttons intact.
</content>
</entry>
<entry>
<title>sl0p: serialise the pane status bar for resurrection</title>
<updated>2026-08-12T12:52:43Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T12:52:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=598e77563b8c6cace224c763e00d2fdb4157a869'/>
<id>urn:sha1:598e77563b8c6cace224c763e00d2fdb4157a869</id>
<content type='text'>
The status bar survived detach/reattach (it lives on Grid) but not a
serialise/restore cycle, so a resurrected pane came back blank.

It now travels the whole per-pane serialisation pipeline, mirroring the fork's
existing default_fg/default_bg: pane -&gt; PaneLayoutMetadata -&gt;
PaneLayoutManifest -&gt; TiledPaneLayout -&gt; a status_bar property in the serialised
KDL, then back through the normal layout parser and applied by LayoutApplier
when the pane is recreated.

The sanitiser moved from grid.rs into zellij-utils so both entry points share
one definition. That matters: a layout file on disk is no more trustworthy than
the escape sequence that wrote it -- it may have been hand-edited, or copied from
somewhere else -- so restored text is stripped and length-capped on the way in
just like the OSC payload was.

Verified end to end rather than by unit test alone: set the bar, waited for a
serialisation pass, confirmed status_bar="..." in session-layout.kdl, killed the
session until list-sessions reported EXITED, then attached and watched it come
back from disk. The earlier attempt only proved detach/reattach because Quit had
left the session alive.
</content>
</entry>
<entry>
<title>sl0p: per-pane status bar via OSC 5577 (experimental)</title>
<updated>2026-08-12T12:39:46Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T12:39:46Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=ee50f2661a141040f7f8579efe1283a0ea5b644c'/>
<id>urn:sha1:ee50f2661a141040f7f8579efe1283a0ea5b644c</id>
<content type='text'>
First slice of the status/action frame: one row of program-supplied text in a
pane's bottom frame row.

    printf '\033]5577;1;status;building 3/7\033\\'
    printf '\033]5577;1;clear\033\\'

Rendered in the EXISTING frame row rather than in reserved rows. Reserving would
shrink the pane's content area and resize its PTY whenever the bar appeared or
disappeared, firing SIGWINCH at the program that asked for it -- the same churn
that made the stack-list picker use a fixed cap. The frame row is already a text
surface with a precedence chain, so this cost no geometry work at all.

State lives on Grid like the window title, so it survives re-renders, resizes
and detach/reattach with no draining. Session resurrection is NOT covered yet.

Versioned from the start, and unknown versions or verbs are ignored, so the
planned button verbs cannot be half-interpreted by an older build. Text is the
remaining params rejoined with ';', which means a single text field needs no
escaping -- the trick OSC 0/2 already uses for titles.

Treated as untrusted, because it is: an in-band sequence is emitted just as
readily by `cat` of a hostile file as by a cooperating program. Control
characters are stripped -- a raw ESC could otherwise repaint anything on screen,
a newline would corrupt the frame row -- and the length is capped, since the
string is retained for the life of the pane.
</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: add close_if_focused to LaunchOrFocusPlugin</title>
<updated>2026-08-12T11:16:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T11:16:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppij.git/commit/?id=733ac78ca77cc61b52bb627e4c02eafa80282944'/>
<id>urn:sha1:733ac78ca77cc61b52bb627e4c02eafa80282944</id>
<content type='text'>
Upstream's action focuses an already-running plugin, so a chord bound to it can
only ever open one -- there is no way to dismiss with the same key.

This cannot be fixed in a plugin. Keybindings are resolved into actions in
route.rs before a key reaches a pane, so the launching chord never arrives at
the plugin. intercept_key_presses does not help either: the intercept lives in
screen.rs and only sees keys that already became Action::Write, i.e. keys
destined for a pane. Confirmed empirically -- an intercepting build received
Ctrl-u but never Ctrl-;.

Closes only when the plugin is the focused pane in the client's ACTIVE tab, so
the chord cannot close whatever pane the user happens to be on. A plugin that
exists but is hidden behind a floating-layer toggle, or is focused in a
different tab, is revealed or brought over as before.

Defaults to false, so every existing binding is unaffected. Config-only, with no
protobuf representation: defaulted at both boundaries and dropped when an action
crosses the plugin API, so no prost regeneration is needed.

The 24 snapshot lines are the new field appearing in serialised keybind dumps.
</content>
</entry>
</feed>
