<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/tests/test_hints.py, branch docs-new</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/tests/test_hints.py?h=docs-new</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/tests/test_hints.py?h=docs-new'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-08-20T22:42:01Z</updated>
<entry>
<title>slosh: put-away panes are miniature pane frames</title>
<updated>2026-08-20T22:42:01Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-20T22:42:01Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=54dc47f801a444fe0d710f393b04c684df8fa31d'/>
<id>urn:sha1:54dc47f801a444fe0d710f393b04c684df8fa31d</id>
<content type='text'>
Brackets were tried and still read as text. Each min-bar entry is now a
three-row chip wearing a real pane frame -- the same corners, because a
frame is what a pane *looks like* in this program, so a shrunken one reads
as exactly what it is: a pane, clickable. The put-away mark rides inside
each chip and the lone legend is gone; a rung pane's bell mark keeps its
own colour inside the frame; hover lights the whole chip and the whole
chip is the hit. The bar costs three rows instead of one while anything
is minimised (and nothing when nothing is), which is what an affordance
that works is worth.
</content>
</entry>
<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: the middle of a border is the button, not all of it</title>
<updated>2026-08-18T13:24:25Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-18T13:24:25Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=8231838e92e5a31e85f53f8efa1b2a1e407e7cd1'/>
<id>urn:sha1:8231838e92e5a31e85f53f8efa1b2a1e407e7cd1</id>
<content type='text'>
Clicking a pane's border to split toward it is the right gesture and was the
wrong target. A border is the longest thing on the screen and it sits between
two things you did mean to click -- a title, a gap, the pane beyond it -- so
reaching a cell too far changed the layout, and the top row was worse than the
sides: clicking a pane's header to focus it split it upward.

So the target shrank. Each side carries a *handle* of three to seven cells in the
middle, and only the handle splits; the rest of the edge is the *rim*, which
arms the guide and does nothing when pressed. Three is the floor the pane
buttons already settled on for the reason written there -- a one-cell target is
a thing you miss with a mouse -- and seven is the ceiling, because a third of a
tall pane's border is most of that border again and brings the accidents back
with it.

Hover now answers two questions in two stages, because they are two questions.
Anywhere on an edge, the edge goes heavy and the handle thickens by a cell into
the pane: that says where the button is, which is the thing you can no longer
infer. On the handle, the dashed boundary and the arrow as well: that says what
the click will do. The hint follows the same split, so sweeping an edge neither
draws a boundary nor offers a split you cannot get from where the pointer is.

Three things this had to get right.

**The thickened handle is registered, not just painted.** The guide adds the
hit for the inner strip in the frame that draws it, and reads the outer rect
back from the hit list rather than recomputing it, so there is one opinion about
where the handle is (D6). A handle you can see and cannot click would be worse
than either mistake alone.

**The top row cannot put its handle in the middle.** The buttons own its right
and a centred title -- the default -- owns exactly the middle. The title winning
is the existing rule, and it is what keeps a double-click rename from splitting
twice on its way, so the handle is placed last, in the widest run of cells the
row has left and nearest the middle among equals. A row with nothing left offers
no upward split; its other three edges are unaffected. Each handle cell is then
painted only if the hit list still agrees it belongs to the handle, so a block
never lands on a pane's name.

**The row keeps the drag and loses the split.** `title:&lt;id&gt;` takes no side now
and `title:&lt;id&gt;:t` -- the handle -- carries one, which is why the press path
reads the side out of the action instead of assuming it. Press-and-hold skips
the dwell on a handle, where a press means something, rather than anywhere on an
edge.

Verified: `make retest`, 1801 checks, all green. Nine of them are new or
rewritten: the two-stage guide, the caption belonging to the handle, the rim
splitting nothing on any of the four sides, the top row's handle still splitting
upward on a titled pane, and the rim of the top row not. Tests read the handle's
cell out of the hit list (`Snapshot.handle`/`.rim`) rather than deriving the
geometry, so a test cannot agree with a broken formula.

Also checked by hand on a live session, which is where the placement rule earned
itself: hovering the rim draws the heavy edge and the block and no dashed line,
hovering the handle adds the line and the arrow, three clicks on pane headers
split nothing, and on a pane titled `sl0ppty` the top handle lands to the right
of the title and left of the buttons -- `drag to move · click to split up` at
x=42, `drag to move` either side of it.
</content>
</entry>
<entry>
<title>sl0ppty: ruff format the Python</title>
<updated>2026-08-17T12:36:59Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-17T12:36:59Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=c0022776d0912042819e4ea330cf7491a8196b45'/>
<id>urn:sha1:c0022776d0912042819e4ea330cf7491a8196b45</id>
<content type='text'>
The bumpy one, again. Every `.py` outside `vendor/` plus the three `contrib/`
scripts that are Python with no extension: 60 files, 6,714 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.

It is a bigger reformat than the C was, 60% of the lines against 17%, and for a
reason worth writing down rather than tuning away: ruff and black break a call that
does not fit onto one argument per line, where this code aligned the continuation
under the opening paren. The test suite is thousands of `check("some prose", cond,
detail)` calls, so nearly every one of them changed shape and the suite grew 3,308
lines. Line length was the one lever that moved it -- 120 columns would have been a
40% reformat and no growth at all -- and it was left at 88, because picking an
unusual width to dodge a one-time diff is paying forever to avoid paying once.

Import sorting came with it. Two files had `import os, pty, uuid, select, struct,
sys, termios, time, fcntl, signal` on one line and now have ten.

Verified after, not before, because a formatter that alters behaviour would be
worse than none:

- `make retest`, 1801 checks green -- and this reformats the suite that does the
  checking, including `harness.py`, which every one of those files imports.
- `make test-live`, the tty tests the fast suite skips. They matter here more than
  usual: `live_input.py` and `live_m0.py` are two of the files whose comma-imports
  were split, and nothing in the fast suite would have noticed if that broke them.
- All 60 files still compile, `make fmt` twice in a row changes nothing the second
  time, and `ruff check --select I` has nothing left to say.
</content>
</entry>
<entry>
<title>sl0ppty: centre the middle of the status line on the row</title>
<updated>2026-08-15T11:30:14Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-15T11:30:14Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=d8e3c956ed7bf257949e603bdc21635c23b9dbe2'/>
<id>urn:sha1:d8e3c956ed7bf257949e603bdc21635c23b9dbe2</id>
<content type='text'>
It was centred in whatever space the two ends had left over, which meant it
moved whenever a pane title, a session name or a state indicator changed
length. The one thing on that line you might want to glance at was never
twice in the same place -- which is most of the value of having a fixed place
to put it.

It is centred on the row now. And when the row is too full for it *there*, it
is not drawn: not squeezed, not slid along, and not written over the ends.
The first attempt at this did overwrite them, which read fine on a wide
terminal and, on a narrow one, ate the words "exited: status 3" -- caught by
a test for the dead-pane indicator rather than by anything looking at the
status line, which is the good kind of accident.

The rule that came out of it is the honest one: the ends say which session
and pane you are in and are not written anywhere else on screen, while the
middle is a hint you can get back by hovering or a version you can read a
moment later. So the middle is the part that disappears. It keeps a blank
column on each side while it is there, because touching reads as overlapping.
</content>
</entry>
<entry>
<title>sl0ppty: say which build you are talking to</title>
<updated>2026-08-15T11:21:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-15T11:21:35Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=d24a07a6048e6d6416e97a26483739bdd879e01b'/>
<id>urn:sha1:d24a07a6048e6d6416e97a26483739bdd879e01b</id>
<content type='text'>
The middle of the status line is empty unless the pointer is on something,
which is most of the time. It says which sl0ppty this is now.

That reads as decoration and is not. A session keeps the binary it started
with, so after a rebuild there are two versions in play: the one on disk and
the one you are attached to. This session has been dogfooding sl0ppty for a
while, and "which build is this?" has already been the answer to two bugs
that looked like something else -- once when a config reload appeared to do
nothing, and once when kitty graphics were fixed and still broken on screen.
The banner answers it without asking, and `sl0ppty --version` answers for the
binary, from the same string.

The version is baked in by the Makefile from `git describe --always --dirty`,
into a generated header that is only rewritten when the value changes -- so a
commit rebuilds and a second `make` does nothing.

It takes the hint's slot and yields to it: a hint is about what you are doing
right now, the banner is about always, and it comes back the moment the
pointer moves off. Both give way to the ends of the line, which was already
the rule for hints and is why a narrow terminal loses the middle rather than
its session name. `version_banner false` for anyone who finds it noisy; it is
a separate knob from `hints`, and the tests hold both directions of that.
</content>
</entry>
<entry>
<title>sl0ppty: a tab is something you can name and move</title>
<updated>2026-08-14T20:26:55Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T20:26:55Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=7247c2e8967bc31776ec6468f5e2a2abf37e0ba8'/>
<id>urn:sha1:7247c2e8967bc31776ec6468f5e2a2abf37e0ba8</id>
<content type='text'>
Tabs had names -- a layout could set one, the control API could set one --
and no way to type one. A tab you made yourself was stuck being a number.
And the strip's order was the order tabs happened to be created in, forever.

Both are the same gesture as the pane equivalent, on the other thing that
has a name:

  double-click a tab   rename it in place, in its own cell in the strip
  drag a tab           move it along the strip

They land together because they share the machinery they are built on, and
splitting them would have meant writing the shared half twice.

The rename editor is the pane's, generalised from "renaming a pane" to
"renaming a pane or a tab" -- one editor with a subject rather than two that
would drift. Enter keeps, Escape abandons, clicking away commits, empty
gives the tab back its number. It is seeded with the name only: a pane seeds
from its title because a title is always something, but a tab shows its
*number* until it is named, and offering "3" to backspace over would be
offering to edit the wrong thing.

The drag is the existing drag machine's third verb, and it reorders **as you
go**. The strip is the only thing that could show an insertion point, and a
strip already showing the result needs no such invention -- no ghost tab, no
caret, nothing remembered between frames. Dragging off the strip is not a
cancel: it is simply not a place to put a tab.

Two things that were not obvious until they were written down:

- pane ids and tab ids are separate sequences, so pane 2 and tab 2 both
  exist. The double-click detector remembered only the id, so a click on one
  followed by a click on the other read as a double-click on whichever came
  second. It remembers the kind too now, and the test asserts the ids really
  do collide before asserting they are told apart -- otherwise it would
  quietly stop testing anything the day the sequences diverge.
- `cur` is a *position*, not an identity, so a move has to recompute it.
  Carried across, you would be looking at whichever tab slid into the index
  you used to be at.

`{"cmd":"move-tab","id":N,"index":M}` does the same thing from a script, and
is what tests the arithmetic from a tab that is not the one being moved --
the case a drag can never produce, because a drag selects what it grabs.
</content>
</entry>
<entry>
<title>sl0ppty: the new-tab button is a bare +, and yours to choose</title>
<updated>2026-08-14T20:26:30Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T20:26:30Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=83f569c162e94609f35a33eb126af4244aa5f68f'/>
<id>urn:sha1:83f569c162e94609f35a33eb126af4244aa5f68f</id>
<content type='text'>
It read "+tab" because of this, in the code, right above it:

    Not a bare "+": the frame already has one, for splitting a pane. Two
    verbs that look identical is a UI bug the fork shipped and noticed.

That was true when it was written and has not been since f2f3d67, which
removed the frame's "+" when the border became the button. The collision it
was avoiding no longer exists, and the word was left explaining itself to
nobody -- while every other affordance in this UI is a bare mark: _ # x on a
frame, and a hint under the pointer for anyone who wants the word.

So: "+", padded to three cells, because their own comment applies -- a
one-cell target is a thing you miss with a mouse.

Also configurable, as `newtab_mark`, which it should have been already: it
was the only mark here that was not (zoom_mark, zoom_on_mark, close_mark,
min_mark, bell_mark all are). Try "[+]" or "new" if you disagree; a session
picks it up on save.

Four tests were asserting the literal "+tab". Three of them only needed the
new string, but two were using it to *locate* the button -- bar.index("+tab")
-- which is exactly the mistake of asserting something incidental: what the
button says is now config, and a test that spells it out fails the day
somebody changes their mind. They look it up through the hit list now, which
is what they meant.

test_no_more_plus keeps the invariant that makes the bare mark safe, and now
says so: there is exactly one "+" in the UI, so it means one thing. If one
ever returns to a frame, that button needs its word back.
</content>
</entry>
<entry>
<title>sl0ppty: the status line says what the pointer is on</title>
<updated>2026-08-14T18:32:16Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T18:32:16Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=686421cee2ebf7dac0d4da28cfa23fe201272352'/>
<id>urn:sha1:686421cee2ebf7dac0d4da28cfa23fe201272352</id>
<content type='text'>
The frame's affordances are nearly all one character wide, and the widest one
is invisible: a border that splits when you click it looks exactly like a
border. Hovering anything now puts a word in the middle of the status line.

Read off the hit list rather than tracked. Anything that registers a hit gets a
hint by being named in one table, and nothing has to remember to raise or clear
one — which also means the hint cannot disagree about what is under the pointer
with the thing that would actually be clicked, since both are the same lookup.
The status line is drawn after everything else has registered, so the list is
complete by the time it asks.

It is centred in what is left between the two ends rather than in the row.
Centring it in the row would put it under the session name on a narrow screen,
and a hint that overwrites the thing it is explaining is worse than no hint; it
is simply not drawn when it does not fit, which has a test with a deliberately
long pane title.

Two of that test's checks were wrong before they were right, in the ordinary
way: one searched for a substring that also occurs in the text on the right,
and one assumed forty-four columns was too narrow to fit a hint when it was
not. Both now assert against measured positions rather than my guess at them.
</content>
</entry>
</feed>
