<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/tests/test_finder.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_finder.py?h=docs-new</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/tests/test_finder.py?h=docs-new'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-08-20T21:22:33Z</updated>
<entry>
<title>slosh: floating panes and the drop grammar</title>
<updated>2026-08-20T21:22:33Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-20T21:22:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b33e8c13ff7981effc7ec2735e6aeaaaf1705af7'/>
<id>urn:sha1:b33e8c13ff7981effc7ec2735e6aeaaaf1705af7</id>
<content type='text'>
A pane can float: lifted out of the layout, drawn over the tiled panes and
under the modals, freely moved and shaped. The model is a flag and a rect on
the leaf, exactly like `minimized` -- the seat in the tree is kept, layout
skips it, the siblings absorb its share, and un-floating returns it home.
The drawn rect is the wanted one clamped to the tab every frame, never
written back, so a terminal squeeze round-trips exactly; an explicit move or
resize *does* write back, because the hand is the author of the intent.
Floating pops the pane to the centre: the jump is what says it happened.
Painted after everything tiled, so the hit list -- searched backwards --
resolves an overlap to the float you can see: the z-order and the click
order are the same list. It casts a shadow (float_shadow, one number), and
`floating` joins the states table above `unfocused`, so a float is never
dimmed as merely-unfocused.

The hands: the title moves it, any other border resizes it (a bottom corner
on both axes; the grabbed edge pins at walls and floors, never sliding the
window), H J K L move a focused float, the =/+ key grows it about its
centre, `-` shrinks it (its split was a refusal anyway), and hovering shows
which edges a grab would take, from the same derivation the press uses.
Equalize moved to `0` to free the key; `C-a f` floats (finder went to `s`,
where it reads as search); `C-a F` opens a new floating shell -- the
throwaway terminal; the function row f1..f12 became nameable in configs
along the way. Guards throughout: the last tiled pane cannot float or
minimize away, closing it lands the top float, splits are refused with
their own words, floats are not swap targets.

Dropping got a grammar: while a pane is carried, the pane under the pointer
subdivides into a swap centre and four edge bands meaning "insert me beside
this pane, on this side" -- detach_leaf + place_beside, the surgery
cross-tab moves already had. Bands are thirds (the first map of quarters
showed the swap owning half of each axis), the hovered band fills the half
the drop would hand over, the release resolves against the painted frame's
hits so it cannot disagree with the fill, and a drop between siblings along
the axis is a reorder that always fits -- refusing it was refusing the most
ordinary drag there is.

Also: rename-pane/rename-tab as palette actions (renaming was a
double-click and nothing else, the one verb the palette could not reach),
and C-u clears the rename editor like it clears the picker's query.

Design log in PLAN-FLOATING-PANES.md; to fold into DESIGN.md as D22/M10-11.
</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: make the finder a modal, and a picker worth using</title>
<updated>2026-08-15T18:10:27Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-15T18:10:27Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=bd84065613df44f131476d4b66fd39b2c76da801'/>
<id>urn:sha1:bd84065613df44f131476d4b66fd39b2c76da801</id>
<content type='text'>
It was a bare coloured rectangle painted over whatever pane it landed
on: no border, no title, no scrim, no way out you could see. It read as
output from the program underneath rather than as a window in front of
it, which is the one thing an overlay has to get right. It is now a
modal like the cheatsheet -- same frame, same scrim, same close button
in the place a pane keeps one -- differing in the way that matters: the
cheatsheet is read, so any key dismisses it, while this one is used, so
it keeps the keyboard until it is answered.

The list was the other half of the problem. It showed `1:` for a tab
with no name, which looks like something failed to load; a row for a
pane with no purpose was three columns of nothing; there was no mark for
the pane you are already in, no count, and nothing to say a list had
more below the tenth row -- so a list that scrolled looked like a list
that ended. An empty result was an empty box, which reads as a broken
finder rather than a wrong query.

Now: a query field that looks like one (so it shows where the typing
goes before you type), the current pane marked, tab/title/purpose told
apart by weight, the row under the pointer lit, "N of M" in the footer,
arrows for what is off each end, and "no pane matches that" when there
is nothing.

Keys, because a picker in this shape is reached for by hands that
already know one: C-n/C-p, tab/shift-tab, home/end, page up/down, C-u to
clear the query, and wrapping at both ends -- one press of Up for the
last entry is worth more in a short list than overshoot protection.
Backspace deleted a *byte*, so one press through a multi-byte character
left the rest of it in the query; it deletes a character now, the way
the rename editor already did.

The finder owns the pointer as well as the keyboard: a press that is not
on one of its rows dismisses it and stops there, rather than also
landing on the layout behind and focusing the pane you were clicking
away from.

Found on the way: CSI Z (shift+tab) was not parsed at all, so it reached
neither a binding nor the program in the pane -- every terminal sends
back-tab that way, and it is the one chord whose shift lives in the
final byte rather than in a modifier param.
</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 counts this tab's panes</title>
<updated>2026-08-14T14:42:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-14T14:42:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=93d9ed9863cd509950fb8beae66d40d37288004d'/>
<id>urn:sha1:93d9ed9863cd509950fb8beae66d40d37288004d</id>
<content type='text'>
The strip along the top counts every pane in the session, which is the right
number for a strip that lists every tab. It is the wrong number for "how big is
the thing I am working in", and nothing answered that.

So the line along the bottom carries its own count, of this tab only. The two
disagree as soon as there is more than one tab, which is the point of having
both — and the test says so directly rather than checking each in isolation.

It sits hard against the right edge with the pane's state inboard of it, which
is the order the strip above already uses for its own pair: the number is
always there, the mode only sometimes, and a thing that comes and goes should
not shove a thing that does not.

Both counts now say "1 pane" rather than "1 panes". The top has been ungrammatical
since it was written and a test had faithfully pinned it that way; adding a second
place to say it made that worth fixing rather than copying.

One of my own tests had to go: it asserted the bottom line contained no pane
count at all, on the reasoning that the top already had one. That was right
when it was written and is exactly what this change reverses, so it now checks
the thing that is still true — the bottom line is not a second copy of the tab
strip.
</content>
</entry>
<entry>
<title>sl0ptty: M5 — responsive collapse, status bar, pane finder</title>
<updated>2026-08-13T21:14:29Z</updated>
<author>
<name>sl0p</name>
<email>sl0p@sl0p.foo</email>
</author>
<published>2026-08-13T21:14:29Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=a194e3f84299c943447c43f5d48d953a095cd4ca'/>
<id>urn:sha1:a194e3f84299c943447c43f5d48d953a095cd4ca</id>
<content type='text'>
D6 as promised: layout(tree, rect) recomputed every frame, and a split that
cannot give every child a floor (24 cols / 6 rows) collapses *itself* into a
stack — one expanded child, every sibling a one-row header. No swap layouts,
no remembered position, no synthesized anything.

Three properties fall out of the design rather than being coded:
- the expanded child is the one holding focus, so focusing a collapsed pane
  expands it: from the finder, by clicking its header, or by walking into it;
- a hidden pane is never resized, so its program does not reflow off-screen;
- a narrow/wide cycle returns the exact rects it started with, and a pane
  added afterwards is not stacked.

That last one is the fork's 'panes stacked after a responsive collapse/expand
cycle' bug, asserted directly. It cannot regress here without the layout
function growing memory it does not have.

A collapsed header shows the pane's title and its OSC 5577 status, so a
stacked agent is still legible — the monitored strip, without a 'rail' mode.

Below the size where headers fit, a node shows only the focused subtree: at
12x4 you get one pane instead of rects that do not fit on the screen. Every
size from 200x60 down to 12x4 is asserted for overlap and containment.

Status bar: tabs, a +tab button, live pane count, and the held prefix shown as
a mode. The button is '+tab', not '+', because the frame already has a '+' for
splitting — two verbs sharing a glyph is a UI bug the fork shipped, noticed,
and wrote down; the collision showed up here as two hit-list tests failing.

Pane finder (C-a f): substring filter over title, purpose and tab name across
every tab, keyboard and mouse, painted last so its rows win the hit list. It
is how you reach a pane the screen has collapsed away, which is tested.

Deferred to M6: drag-to-reorder, which wants the same drag-state machine as
pane resizing and should be built with it.

213 checks green (186 headless + 27 live).
</content>
</entry>
</feed>
