<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/src/client.c, branch v0.1.1</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/src/client.c?h=v0.1.1</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/src/client.c?h=v0.1.1'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-08-18T19:26:00Z</updated>
<entry>
<title>slosh: rename the project from sl0ppty</title>
<updated>2026-08-18T19:26:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-18T19:26:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b9e53cace4eb75263077df8623b011e66f5f2241'/>
<id>urn:sha1:b9e53cace4eb75263077df8623b011e66f5f2241</id>
<content type='text'>
sl0p + pty was error-prone to type and the zero made it worse; slosh
(slop + sh) is one syllable, impossible to typo, and slosh.foo is ours.

Mechanical rename everywhere it appears: binary, env vars (SLOSH,
SLOSH_SESSION, SLOSH_BIN, SLOSH_CONFIG), socket dir (/tmp/slosh-&lt;uid&gt;,
$XDG_RUNTIME_DIR/slosh), config path (~/.config/slosh), header guards,
src/sl0ppty.h -&gt; src/slosh.h, contrib/sl0ppty-dev -&gt; contrib/slosh-dev,
the driving-slosh skill, docs and tests. The sl0ppi fork references and
the sl0p theme stay: one is history, the other is the brand.

The rename surfaced a latent bug in test_osc5577: the hello-reply
answer always appeared on screen twice (ECHOCTL echo plus cat -v), but
a seven-character implementation name happened to wrap the second copy
across the 60-column boundary, so counting substrings in wrapped text
found one and passed for the wrong reason. A five-character name moved
the wrap and exposed it. The loop guard itself is correct -- with echo
off before the request there is exactly one reply -- so the test now
counts on de-wrapped text and tolerates the pane's own echo.
</content>
</entry>
<entry>
<title>sl0ppty: clang-format the tree</title>
<updated>2026-08-17T12:22:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-17T12:22:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=ec91ccffe631ae34b111bf6161fd0e3c487f684c'/>
<id>urn:sha1:ec91ccffe631ae34b111bf6161fd0e3c487f684c</id>
<content type='text'>
The bumpy one. `make fmt` over every `.c` and `.h` outside `vendor/`: 32 files,
1,843 lines rewritten, no behaviour changed. Nothing here is worth reading -- the
next commit adds it to `.git-blame-ignore-revs` so `git blame` walks past it.

Two thirds of it is alignment the formatter now owns: trailing comments in a
struct, continuation lines under a signature. The rest is the hand-packed tables
opening up to one entry per line -- `ACTIONS`, `NAMED_KEYS`, `ACTION_HELP`. That
is a real loss of density and a real gain in diffs: adding an action was a reflow
of the block and is now one line. Anything worth keeping packed can be fenced with
`// clang-format off`; nothing was, on the grounds that a table is the thing a
formatter is for.

**One change is not formatting.** `src/config.h` had three trailing comments glued
onto one field:

    uint16_t hover_delay_ms; /* how long the pointer must rest to arm a guide */
    /* how long an announcement stays up */ /* rows per wheel notch */

-- `toast_ms`'s and `scroll_lines`' comments, landed on their neighbour by an
earlier edit, documenting the wrong things ever since. It is in this commit
because it is what caught it: clang-format could not settle on that line, wrapping
it one way and then re-indenting the wrap on a second pass, so `make fmt` was not
idempotent. The comments are back where they belong and it is.

Verified after the reformat, not before: `make clean &amp;&amp; make all` is
warning-free, `make retest` is 1801 checks green, and `make fmt` twice in a row
changes nothing the second time. A formatter that alters behaviour would be worse
than none, so the gate was the whole suite rather than a reading of the diff.
</content>
</entry>
<entry>
<title>sl0ppty: carry the terminal's cell size, so images can have a size</title>
<updated>2026-08-14T21:13:56Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T21:13:56Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=4b380e9b4cbd24dca99bef4991541aa1aece847f'/>
<id>urn:sha1:4b380e9b4cbd24dca99bef4991541aa1aece847f</id>
<content type='text'>
"Kitty graphics pass through" was true of the tests and not of anything else.
Every test transmitted and placed in one command with explicit c=/r=; real
programs upload once with `a=t` and place per frame with `a=p`, saying
nothing about cells, and that path was broken from end to end.

A placement that does not say how many cells it covers is sized from the
image's pixels and the cell size. We never had a cell size:

- pty_spawn and pty_resize left ws_xpixel/ws_ypixel at zero, so a program
  asking its own tty how big a cell is got nothing and fell back to guessing;
- ghostty_terminal_resize was called with 0, 0 for the cell size, so lib-vt
  computed zero cells for such a placement -- and a placement covering zero
  cells is skipped, which on screen is an image that simply is not there.

So the size now runs the whole way through. The client reads it from
TIOCGWINSZ (re-read on every resize, because a window can move to a monitor
with different metrics), MSG_HELLO and MSG_RESIZE carry it -- a 4-byte
payload still means "I did not say", so an older client still attaches -- the
app hands it to every pane in every tab, and each pane gives it to both its
pty and its terminal.

Where nobody knows, the default is 8x16 rather than 0. That is the whole
lesson of this bug: a plausible cell size means an image appears at roughly
the right size, and a zero means it does not appear at all.

`{"cmd":"resize"}` takes optional cell_w/cell_h, so a script can be a client
that knows its own metrics, and so the tests can be one.

The tests now use the protocol the way programs use it, including a check
that a program can read real pixel dimensions out of its own pty -- the thing
that was silently zero. The remaining half of the fix is one layer down, in
vendor/patches/0001.
</content>
</entry>
<entry>
<title>sl0ppty: rename from sl0ptty</title>
<updated>2026-08-14T10:13:18Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T10:13:18Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=20f269a0cdf65f196aea09f0279f5927f26d8192'/>
<id>urn:sha1:20f269a0cdf65f196aea09f0279f5927f26d8192</id>
<content type='text'>
A pty is a pseudo-terminal, and that is what this multiplexes: the extra t
was never standing for anything. Renaming now, while the only users are the
people who typed the old name on purpose, is cheaper than renaming later.

Mechanical, but not only cosmetic. The name is also the runtime contract, so
this moves $SL0PTTY and $SL0PTTY_CONFIG, ~/.config/sl0ptty/config.kdl, the
socket directory ($XDG_RUNTIME_DIR/sl0ptty, /tmp/sl0ptty-&lt;uid&gt;), and the
identity string a program gets back from an OSC 5577 hello. A session left
running under the old binary keeps its old socket directory and is invisible
to the new one; there is no migration and it does not seem worth writing one.

The sl0ppi fork keeps its name: it is a different thing, and D1 still points
at it.
</content>
</entry>
<entry>
<title>sl0ptty: focus follows the mouse, with the guards that make it liveable</title>
<updated>2026-08-13T21:39:20Z</updated>
<author>
<name>sl0p</name>
<email>sl0p@sl0p.foo</email>
</author>
<published>2026-08-13T21:39:20Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=80425ad3683510f016a954315902c78bb1f77379'/>
<id>urn:sha1:80425ad3683510f016a954315902c78bb1f77379</id>
<content type='text'>
Hovering a pane focuses it. The feature is three lines; the reason it is not
obnoxious is the guards, and each one is a test:

  - not while the prefix is held, so a chord cannot be hijacked by a nudge of
    the mouse;
  - not through the finder, whose rows are on top of the hit list anyway;
  - not during a drag, which owns the mouse until the button comes up;
  - not onto a collapsed pane. Only 'pane:' and 'title:' targets count, never
    the frame rect's 'focus:', so the pointer crossing a stack of headers does
    not expand three panes on its way past.

Off with 'focus_follows_mouse false'; clicking still focuses either way.

This needed the client to ask for ?1003 (any motion) rather than ?1002 (motion
while a button is held), which fixes something that was quietly broken: a pane
requesting any-event tracking could never receive hover, because the outer
terminal was never asked to report it. It can now, and a pane that asked only
for clicks still gets none — both tested.

One real bug behind that: our decoder represents 'no button held' as
MBTN_UNKNOWN, and pane_send_mouse was passing it to the encoder as a concrete
button. lib-vt has ghostty_mouse_event_clear_button() for exactly this, and
without it bare motion encoded to nothing at all.

320 checks green.
</content>
</entry>
<entry>
<title>sl0ptty: M2 — server/client split, detach/reattach, control socket</title>
<updated>2026-08-13T20:52:33Z</updated>
<author>
<name>sl0p</name>
<email>sl0p@sl0p.foo</email>
</author>
<published>2026-08-13T20:52:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=7a6fa40a9c49245cf569e10e6ed635f9a6cbbcb7'/>
<id>urn:sha1:7a6fa40a9c49245cf569e10e6ed635f9a6cbbcb7</id>
<content type='text'>
The session now outlives its client, which is the point: agents keep running
while you are gone. The server owns the panes, the layout and the renderer;
the client is a terminal in raw mode and a socket, forwarding bytes and
painting what it is sent. It does not even decode input, so the decoder can
change without redeploying clients.

  sl0ptty            attach to 'main', creating it if nobody is home
  sl0ptty -s NAME    a named session
  sl0ptty ls         live sessions
  sl0ptty cmd LINE   one control command
  C-a d              detach

Connections are not clients. A connection becomes the display client only by
sending HELLO; control commands and liveness probes are just connections and
never displace anybody. This was found the hard way, one hour after the server
first ran: 'sl0ptty ls' kicked the attached client off, because ls connects to
see whether a socket answers. That is precisely the fault that gave the zellij
fork a 20-25% CLI failure rate, rebuilt and then fixed structurally. The
regression runs 20 x (ls + control) against a live client and demands 0
failures and a client that can still type.

cmd.c now holds the command vocabulary once, shared by the headless driver and
the control socket, so a script written against one works against the other —
'sl0ptty cmd "snapshot text"' against a detached session prints its screen.

tests/test_session.py: 18 checks over create, split, probe-resistance, detach,
survival, drive-while-detached, reattach, displacement and cleanup.
123 checks green overall.
</content>
</entry>
</feed>
