<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/packaging/checks/status-bar-live.py, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/packaging/checks/status-bar-live.py?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/packaging/checks/status-bar-live.py?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-13T20:29:03Z</updated>
<entry>
<title>checks: kill sessions with the form that actually kills them</title>
<updated>2026-08-13T20:29:03Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T20:29:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=ed40b1656a38f7277d79eadd8aa0f4fb8d6c5dab'/>
<id>urn:sha1:ed40b1656a38f7277d79eadd8aa0f4fb8d6c5dab</id>
<content type='text'>
`sl0ppij -s NAME kill-session` parses, exits 0, and leaves the session running;
the top-level `sl0ppij kill-session NAME` is the one that works. The checks used
the first form, so cleanup was resting entirely on the delete-session that
follows it -- which is how a test session was still listed and its server still
running right after a check "cleaned up" on the test box.
</content>
</entry>
<entry>
<title>sl0p: keep a templated tab's purpose, so runtime tabs get a status bar</title>
<updated>2026-08-13T20:07:45Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T20:07:45Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=a8ea4e888938e562885b1a0f009f5743939a879b'/>
<id>urn:sha1:a8ea4e888938e562885b1a0f009f5743939a879b</id>
<content type='text'>
Clicking the bar's `+` made a tab with no status bar. The button only exposed
it: pressing `n` in TAB mode did the same, in any `sl0ppi up` session.

The chain: a generated layout had no default_tab_template, so a tab created at
runtime had nothing to take a bar from. It had none because
parse_tab_node_with_template read a tab node's name, cwd and focus but never
its `purpose` -- so templating erased the purpose that IS identity here
(`sl0ppi ls` finds project tabs by it), and the CLI worked around that by
inlining the template into every tab instead of emitting it.

Fixed at the root: a templated tab keeps its own purpose, and the CLI emits
`default_tab_template` again. The generated layout also shrinks -- one copy of
the bar's configuration instead of one per tab.

The two properties trade off against each other, so sl0ppi-up-live.py now
asserts both at once: `sl0ppi ls` still sees a tab per project, and every tab
including one created at runtime has exactly one status bar. Confirmed by
reverting each half separately: without the parser fix the tabs lose their
purpose and `ls` shows "alpha —"; without the CLI change the runtime tab has
zero bars, which is the reported bug.

Two measurement traps cost an hour here and are now written down: counting bars
by grepping dump-layout also counts the template and every swap layout ("4 bars
for 3 tabs"), and list-panes stops reporting borderless plugin panes for a tab
once a client has rendered it.

Also in here, from the same session:

- the split button check found buttons by searching for " + " on any row with a
  frame glyph, which matches the stack list's "+N more" once two panes sit side
  by side, and then reports the product broken. It now requires the frame
  corner the button is drawn against.
- the checks killed their sessions but did not delete them, so each run left an
  "EXITED - attach to resurrect" entry: 35 had quietly filled the session
  picker. They now delete-session by name.
- and the trap that cost the user their running client: I killed a bare
  `sl0ppij` because "there are no user sessions on this box". That was a guess,
  not evidence, and wrong. A check's processes always carry that run's own
  socket dir or `attach sl0ptest-...`; a bare `sl0ppij` with a cwd in the repo
  is a human's. Written into the handover next to the `pkill -f` trap, which is
  the same mistake in a different costume.
</content>
</entry>
<entry>
<title>sl0p(bar): draw the bar as a framed box, and a single line when collapsed</title>
<updated>2026-08-13T18:54:29Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T18:54:29Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=e220bb7cb784140ba5c9cc1fe59b71fdb9dbf4b9'/>
<id>urn:sha1:e220bb7cb784140ba5c9cc1fe59b71fdb9dbf4b9</id>
<content type='text'>
Given three rows the bar now draws the same rounded box the panes do, so the
bottom of the screen reads as one more pane instead of a strip of text under
them. Given one row it stays the line it always was, which is what a terminal
narrow enough to collapse the layout should spend on chrome.

The height is the LAYOUT's decision, because a plugin cannot resize its own
pane:

- default.kdl defines the bar as a pane_template `sl0pbar` and places it at
  size=3;
- default.swap.kdl defines tab_template "mobile" placing the same template at
  size=1, and the `stacked` swap layout -- what responsive_layout collapses to
  below responsive_min_cols -- uses it.

That reuse works because zellij parses a layout and its .swap.kdl with the same
parser, on purpose, "so that we can reuse assets (eg. pane and tab templates)".
Otherwise the collapsed template would have to duplicate the whole 100-line
zjstatus configuration, and the two would drift within a week.

ModuleConfig::viewport is the one definition of where the content line lives:
three rows or more and it is framed, fewer and it is not, plus the column the
content starts at. Rendering and click routing both read it, so the frame moves
the drawing and the hit-testing together -- a click on the new border hits
nothing rather than the widget it sits above. Clicks now check the LINE too,
which a single-line bar never had to.

status-bar-live.py grew the other half of the feature: the borders must span
exactly the columns the pane frames span with rounded corners at their ends, a
click on the border must not make a tab, and after shrinking the terminal below
the responsive threshold the bar must be one line with no box -- with its button
still working. The collapse is real: the bar lands on the last row, which only
happens if the swap layout actually switched.

One test bug worth naming: locating the button with str::find gave a BYTE
offset, and the box glyphs are three bytes each, so the click landed two columns
right of the thing it found. Same trap the pane frame tests document.
</content>
</entry>
<entry>
<title>sl0p(bar): a new-tab button, and margins that match the pane gap</title>
<updated>2026-08-13T18:36:45Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T18:36:45Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=91f40dccd9def02d147ea41615f7bc159cb306a6'/>
<id>urn:sha1:91f40dccd9def02d147ea41615f7bc159cb306a6</id>
<content type='text'>
First pass of the status bar overhaul, in the zjstatus fork plus the layout
that configures it.

margin_left / margin_right: the bar is a full-width pane while every pane above
it is inset by the pane gap, so its ends stuck out past every frame on screen.
The margins are applied by rendering into an inset viewport (inset_state),
which is the same thing click positions are translated by -- so what is drawn
and what is clickable come from one definition. That is the split button's
lesson applied before shipping rather than after.

{new_tab}: a clickable button that opens a tab and focuses it, with no name and
no cwd, i.e. exactly what the `n` binding in TAB mode does. Clicking a tab
already switches to it and double-clicking renames it; making one was the hole
in the strip. Rendering and hit-testing are the same measured string here, so
the button covers exactly the columns it draws.

Both keys are inert when unset, so an unmodified zjstatus config renders
identically.

packaging/checks/status-bar-live.py reads the bar off a real session and
asserts it spans exactly the columns the pane frames span, clicks the + and
expects one more tab, then clicks beside it and expects none. Confirmed by
setting the margins to 0 and watching the span assertion fail: (0, 99) against
the frames' (2, 97).

The check counts painted cells rather than ink, because a bar segment begins
with a padding space inside its coloured pill -- strip the colour and the left
edge reads one column off, which is half the margin being measured.
</content>
</entry>
</feed>
