<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git, 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/?h=v0.1.6</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/?h=v0.1.6'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-07T14:33:15Z</updated>
<entry>
<title>release: v0.1.6</title>
<updated>2026-09-07T14:33:15Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-07T14:33:15Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=f35c8b80a768cc8645647e8f8dd770914ce47889'/>
<id>urn:sha1:f35c8b80a768cc8645647e8f8dd770914ce47889</id>
<content type='text'>
</content>
</entry>
<entry>
<title>graphics: no cell arithmetic at the far edge of an uncut image</title>
<updated>2026-09-07T14:21:56Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-07T14:21:56Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=83ca4072aa1934be3456638d3b2597876aa7d2e2'/>
<id>urn:sha1:83ca4072aa1934be3456638d3b2597876aa7d2e2</id>
<content type='text'>
A smooth bounce shimmered: anything placed with sub-cell X=/Y= offsets
had its source rectangle shaved by a different sliver every frame, so
the picture's edges breathed as it moved. dvd.py made it obvious --
the logo's crop wobbled between w=124 and w=130 around a true 142.

The pixels-per-cell a placement reports are its own pixels over its
own grid, floored, and the grid includes the cell the sub-cell offset
spills into, so the figure runs a shade small -- 142px over 19 cells
says 7 when the terminal's cell is 8. That was harmless while the
number was only used proportionally at real cut edges. The pieces
rewrite ran it over every placement: axis_crop() clamped even an
uncut image's source to cols * cell_px - offset, and as the offset
moved the clamp moved with it.

The far edge of a piece that reaches the placement's last cell is
where the source runs out; only an interior cut needs the cell
arithmetic. With that, an uncut placement's source rect passes
through untouched, which is what the old emitter always sent.

Two companions in the same sum. A scaled piece so narrow its share
of the source rounds to nothing now keeps the pixel it is inside,
because w=0 on the wire means "the whole image", which is worse than
wrong. And a cut piece whose source is genuinely empty is cells the
image never reaches, so it is not placed at all -- the uncut
placement stays exempt, since the pane-edge crop has always used 0
for "the rest of it".

The headless check places with X=5: the whole source rect must
survive the offset, where the clamped code sent w=0.
</content>
</entry>
<entry>
<title>graphics: a retransmit invalidates every placement of the image</title>
<updated>2026-09-07T13:54:34Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-07T13:54:34Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=2a440fbc01c4e4afd58e15ace5eddd4a171d15ef'/>
<id>urn:sha1:2a440fbc01c4e4afd58e15ace5eddd4a171d15ef</id>
<content type='text'>
Replacing an image's pixels deletes all of its placements on the
client -- kitty drops them when the data is replaced, which is the very
fact the place-once logic leans on. But the invalidation rode a local
in one gfx_place() call, so only the placement said in the same breath
as the transmit was owed again. The image's other placements -- a
second one elsewhere, the other pieces of one a float has cut -- were
judged unchanged against a client that no longer holds them, and
stayed unsaid: a picture the client deleted and we believe it still
has, gone until something moves it.

Masked before place-once by the every-frame re-emission, so this is
the regression that fix bought. The transmit now un-marks every
placement of its image, and each is said again as its gfx_place()
comes around.

The live check drives one image placed twice, then replaces its
pixels and places both again: both must reach the client after the
second transmit, where the old code re-sent one.
</content>
</entry>
<entry>
<title>Merge branch 'graphics-fixes' (PR #2 from iksteen): five fixes to the kitty passthrough, found through a mirror</title>
<updated>2026-09-07T13:52:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-07T13:52:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=e9147fba11b735259a90089dd7207853ff49a3ad'/>
<id>urn:sha1:e9147fba11b735259a90089dd7207853ff49a3ad</id>
<content type='text'>
One synchronized message per frame with cells and images together, a
placement said once rather than every frame, z=0 stated so a mirror
knows where the picture goes, a float cutting an image into pieces
instead of erasing it, and overlays cut out of the images under them.
Found running slosh inside shellglass, real on a terminal too.
</content>
</entry>
<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: state z=0 on a placement, so a mirror paints it over the text</title>
<updated>2026-09-06T17:41:56Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-09-06T17:41:56Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b6fbc2184ff26425e212d88bbb3de04066a9e5f6'/>
<id>urn:sha1:b6fbc2184ff26425e212d88bbb3de04066a9e5f6</id>
<content type='text'>
kitty's default z is 0, which draws an image above the text, and that is
the value slosh has always relied on -- it places after the cell diff
precisely so the picture ends up on top. Saying nothing works because
the terminal supplies the default.

Anything mirroring the stream has no such default to fall back on. It
cannot tell "the emitter wants this over the text" from "the emitter
said nothing", and shellglass's viewer paints an unstated image under
the cells. That is fine until a cell over the picture carries an
explicit background -- and dim_unfocused gives one to every cell of a
pane at once, so the moment focus moved elsewhere the whole image
disappeared from the mirrored copy while the terminal, which took the
default, still showed it.

So the placement states z=0. Nothing changes on a terminal, since that
is the value it was already assuming; the mirror now paints the picture
over the cells and the dim pass runs underneath it, where it belongs.

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>server: one synchronized message per frame, cells and images together</title>
<updated>2026-09-06T17:39:57Z</updated>
<author>
<name>Ingmar Steen</name>
<email>iksteen@gmail.com</email>
</author>
<published>2026-09-06T17:39:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=de3dacc0d23b6ca75fbe63370510f101c1e6a9e7'/>
<id>urn:sha1:de3dacc0d23b6ca75fbe63370510f101c1e6a9e7</id>
<content type='text'>
A frame went out as two messages: the cell diff, then the kitty
placements that belong on top of it. The client wrote each to the
terminal with its own write, and directly on a tty the two land in one
read often enough that nobody saw the gap. Put anything that re-chunks
the stream in between -- a mirror like shellglass, which reads the pty
8KB at a time and writes each read straight through -- and the gap
becomes a screen refresh of its own: the cells where the picture is,
repainted, with no picture on them, and the placement arriving a moment
later to put it back. Every image on screen blinks once per frame, which
is how it was reported: unstable, as if constantly redrawn.

So the two halves travel as one message now, and the whole frame is
wrapped in synchronized output (DEC private mode 2026), which a terminal
buffers and presents as one update. That covers the case the single
message cannot: a frame larger than one read downstream is still split,
and the markers are what stop the halves being presented separately.
Terminals that do not implement 2026 ignore the private mode, so it goes
out unconditionally -- the same thing tmux does, and the reason a redraw
under tmux never showed this.

The live check asserts what the fix is: no image command reaches the
client outside a synchronized update.

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>screen: commit a wide cell's tail to prev, or the diff leaves a hole</title>
<updated>2026-09-03T18:32:05Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-03T18:32:05Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=fd80818889fc58b555748180b228980318e6bc10'/>
<id>urn:sha1:fd80818889fc58b555748180b228980318e6bc10</id>
<content type='text'>
A wide glyph's tail column is never painted or compared, and prev never
learned it either. Overwrite the head later and the terminal blanks the
tail cell -- but if that column's new content matches the stale prev, the
diff skips it, leaving a one-cell hole where the glyph's second half was.

Seen as a missing rule cell in a pane's top border after spawning a new
tab: the old tab's centered title started with a wide glyph, the new
tab's border put the rule back, and the tail column stayed blank.
</content>
</entry>
</feed>
