<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/include, branch v0.1.6</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/include?h=v0.1.6</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/include?h=v0.1.6'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-06T18:34:26Z</updated>
<entry>
<title>app: cut the modals, toasts and splash out of the images under them</title>
<updated>2026-09-06T18:34:26Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-09-06T18:34:26Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=79a2812808061129bf1449d9f4c3356b36bf0061'/>
<id>urn:sha1:79a2812808061129bf1449d9f4c3356b36bf0061</id>
<content type='text'>
Overlays are painted over the panes and the compositor needs no more
than that: painted later is on top. A placement is not painted, though
-- the client's terminal draws the image over the cells, in whatever
order they arrived -- so an overlay that is only drawn is an overlay an
image sits on top of. The cheatsheet came up under a picture and was
unreadable through it, in the terminal and in the mirror both.

So a frame now records where its overlays landed, and the graphics pass
takes them out of a placement exactly as it takes out a float: the
modal's box (the cheatsheet and the picker wear the same frame), each
toast, and the splash. The list is rebuilt every compose, so a rect that
is not registered again is one that is no longer on screen.

Only the opaque things are registered. The scrim behind a modal tints
what is behind it rather than hiding it, and an image cannot be tinted
by us at all -- so a picture beside the cheatsheet stays bright while
the text around it is pushed back, which is wrong-looking but a great
deal better than the image disappearing while a modal is up.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
</content>
</entry>
<entry>
<title>graphics: place what a float leaves of an image, not nothing</title>
<updated>2026-09-06T17:59:13Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-09-06T17:59:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=af82aff3cac5370cc08b2e0d7358097bfb2d9e45'/>
<id>urn:sha1:af82aff3cac5370cc08b2e0d7358097bfb2d9e45</id>
<content type='text'>
Occlusion for placements is clipping: the cell compositor gets it free
from paint order, but images are sent after the diff and drawn over it,
so what a float covers has to be taken out of the placement itself. A
clean edge came out as a smaller rectangle; anything else -- a float
across the middle, a float on a corner, a float wholly inside -- was
refused, on the grounds that the remainder is an L and a placement is a
rectangle. The image vanished, all of it, the moment a floating pane
crossed it, on the terminal and in the mirror alike, and came back when
the float moved off.

The remainder is not one rectangle, but it is always a few: the strips
above and below what is covered, and what is left beside it between
them. So rect_subtract() cuts the visible remainder into rectangles and
each is placed in its own right, with the source rectangle the same
arithmetic the pane edge already used -- generalised to a cut on any
side, and to a first piece that keeps the sub-cell offset while the
others start on a cell boundary. Eight pieces is the ceiling (two
floats' worth); past it the old refusal stands.

The pieces of one placement need an id each, and the program's own
placement id is one number that cannot be several, so the model now
hands out the placement ids the client is told and keeps the program's
as the identity it was. `graphics` reports the piece alongside it.

Checked against a float parked over an image: the picture is cut around
the float, the strips tile exactly the image minus what the float
covers, and their source rectangles land where their cells do (a float
inside a 40x10-cell natural-size image gives 320x32 above, 320x32
below, 56x96 left and 72x96 right).

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
</content>
</entry>
<entry>
<title>graphics: place an image once, not once per frame</title>
<updated>2026-09-06T17:41:18Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-09-06T17:41:18Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=06b22770e0ff2d03dffafed223c63d7d4608020c'/>
<id>urn:sha1:06b22770e0ff2d03dffafed223c63d7d4608020c</id>
<content type='text'>
Every frame re-emitted `a=p` for every visible placement. On a terminal
that is invisible: a kitty placement is a standing instruction, not a
per-frame one ("the other commands to erase text must have no effect on
graphics"), so a repaint of the cells under a picture does not disturb
it and saying it again says nothing new.

It is not free anywhere else. A mirror cannot know that the identical
placement is identical: shellglass re-decodes and re-encodes the image
on every one, holds the placement back while that runs, and -- because
an image change rides only in a full frame -- republishes the whole
screen each time. Measured on one image and a pane printing text for
twenty seconds, the mirror published 430 full frames and re-sent the
image list 215 times; 215 of those fulls carried no image at all,
because the placement was still mid-decode. That alternation is what
was visible in the browser: a picture that flickered while anything on
screen moved and vanished when it stopped. The same twenty seconds now
costs one full frame, 412 diffs and one image list.

So a placement is emitted only when it is new or has actually changed,
and the client's state records what it is holding. Three things have to
be right for that to be safe: a dropped frame un-marks the placements it
carried (they are owed again, exactly as transmissions and deletions
already were); a retransmit of the pixels invalidates the placement,
because kitty deletes an image's placements when its data is replaced;
and a full repaint clears the screen, which takes the images with it, so
every placement is owed again -- gfx_repaint(), not gfx_reset(), because
the pixels the terminal was sent survive a clear and a deletion still
owed from a dropped frame is still owed.

The live check drives a picture that does not move while the screen
around it repaints: one placement across 121 frames, where the old
behaviour emitted 120.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
</content>
</entry>
<entry>
<title>pane: drop the render snapshot of panes on hidden tabs -- 154K -&gt; 66K each</title>
<updated>2026-09-03T14:26:41Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-03T14:26:41Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=e3059676a6b342ea9ff07753d5b976e854d4b111'/>
<id>urn:sha1:e3059676a6b342ea9ff07753d5b976e854d4b111</id>
<content type='text'>
An updated GhosttyRenderState retains a full copy of the pane's viewport
(rows, cells, per-row arenas, styles) so renders can read it without the
terminal lock. Measured with bpftrace and a lib-vt micro-benchmark, that
copy is ~140KB for an 80x24 pane -- more than twice the terminal it
mirrors (~57KB) -- and every pane kept one forever, composed or not.

Only the current tab is ever composed, so app_compose now drops the
snapshot of every pane on the other tabs (a pointer test once dropped),
and pane_new no longer creates one at all: pane_compose and
pane_select_word build it on first use. Both already begin with
ghostty_render_state_update(), so a dropped snapshot is rebuilt, never
missed, and the tab switch already forces a full repaint.

Server memory per empty 80x24 terminal drops from 154K to 66K anon
(Superlogical reports 68K on macOS; tmux 4K, zellij 1.2-6.2M). Fifty
background tabs: 8.2M -&gt; 3.8M, reclaimed on close, and cycling through
tabs does not ratchet. What remains is the lib-vt terminal itself.
</content>
</entry>
<entry>
<title>pty: install the terminfo entry implicitly when it is missing everywhere</title>
<updated>2026-09-02T14:24:41Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T14:24:41Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=c58a875ef3bcb464cdbea72bdbd0a874b6f93729'/>
<id>urn:sha1:c58a875ef3bcb464cdbea72bdbd0a874b6f93729</id>
<content type='text'>
The lookup miss now writes the embedded entry to ~/.terminfo and
uses it, so a bare box gets the full TERM with no step at all. An
entry that exists anywhere is respected -- possibly newer, certainly
somebody's -- and a HOME that cannot be written leaves the
xterm-256color fallback standing. --install-terminfo stays as the
by-hand spelling; the writer moved to pty.c where the panes are.
</content>
</entry>
<entry>
<title>attach tag: just the count, in theme colours</title>
<updated>2026-09-02T13:09:36Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T13:09:36Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=cafa48e0a7e1eb48a783bfde6862c5fd82c7aede'/>
<id>urn:sha1:cafa48e0a7e1eb48a783bfde6862c5fd82c7aede</id>
<content type='text'>
The +x+y offset goes -- 'N clients' says the thing that matters. The
inverse-video chip goes with it: the tag now draws in attach_fg /
attach_bg from the theme, defaulting to gray ink on the terminal's
own background. Standing information, not an announcement.
</content>
</entry>
<entry>
<title>multi-attach: say who else is watching, and where you are</title>
<updated>2026-09-02T13:03:53Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T13:03:53Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=1a3ba183ae8f2613cae847c8f6460782512acc40'/>
<id>urn:sha1:1a3ba183ae8f2613cae847c8f6460782512acc40</id>
<content type='text'>
A small tag in each client's own top-right corner when several
terminals share the session: the viewer count, and on a view that is
a crop being panned by somebody else's cursor, the crop's offset into
the shared screen ("2 clients +12+4") -- which is exactly the moment
a screen moving on its own needs explaining. Stamped per client after
projection, because every terminal has its own count of others and
its own offset; the shared status bar is composed once and can be
cropped out of a panned view anyway. attach_indicator false hides
it.
</content>
</entry>
<entry>
<title>multi-attach: the config knobs</title>
<updated>2026-09-02T12:57:13Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T12:57:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=d698946464f25a0eb43730ffad15656f3247f56b'/>
<id>urn:sha1:d698946464f25a0eb43730ffad15656f3247f56b</id>
<content type='text'>
The patch made shared attach unconditional; these make it a policy.
multi_attach false restores the classic displacing attach
(EXIT_REPLACED, which old clients already understand). size_follows
picks whose size the shared screen takes: active (the patch's rule),
smallest (the tmux rule: filler, never a crop), or largest. The size
fold lives in apply_canvas, so any attach, resize or departure can
move the canvas under the folding policies -- including a non-active
client leaving, which under smallest may have been the very client
holding the floor down. A config reload re-applies both knobs to the
running session.
</content>
</entry>
<entry>
<title>Add competitive multiplayer support.</title>
<updated>2026-09-02T12:51:02Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-08-31T12:43:32Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=29061a4aca5e3d645c0e23b885342036ac48f97b'/>
<id>urn:sha1:29061a4aca5e3d645c0e23b885342036ac48f97b</id>
<content type='text'>
Last input wins, canvas size is adjusted to active connection.
</content>
</entry>
<entry>
<title>shaders: remove the .so plugin loader</title>
<updated>2026-09-02T12:43:09Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T12:43:09Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=18d1aef0f9bf4620135fd9f77f26683e6f2f8c00'/>
<id>urn:sha1:18d1aef0f9bf4620135fd9f77f26683e6f2f8c00</id>
<content type='text'>
The static musl release builds cannot dlopen -- musl compiles it out
-- so loadable shader plugins were a feature most shipped binaries
could not use. Nothing we ship used it either: every shader in
contrib/shaders is an expression. Gone: the dlopen/glob loader in
shader.c, shader_dir in the config, the plugin ABI table and entry
macro, the Windows dl*/glob shims that existed only for it, the
contrib/shader-plugin skeleton, its tests, and every doc mention.
shader_abi.h stays as the shader model (cell_t, shade_ctx_t,
shader_t) with a note about what it used to be.
</content>
</entry>
</feed>
