<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/docs/HANDOVER.md, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/docs/HANDOVER.md?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/docs/HANDOVER.md?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-13T20:51:59Z</updated>
<entry>
<title>release: 0.1.9, with a bun that runs without AVX2</title>
<updated>2026-08-13T20:51:59Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T20:51:59Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=7a50cd9f09a6ed1ca65ffccc84dab6023bb59446'/>
<id>urn:sha1:7a50cd9f09a6ed1ca65ffccc84dab6023bb59446</id>
<content type='text'>
The tarball shipped bun's default x64 build, which requires AVX2 -- so sl0ppi
installed cleanly on a plain KVM guest and then died with "Illegal instruction"
the first time anything ran pi. Known since 0.1.7 and carried as an open item;
not something to ship a third time.

packaging/fetch-vendor.sh now fetches bun-linux-x64-baseline.zip. Verified
without needing such a machine, on an emulated CPU that has none:

  qemu-x86_64 -cpu Nehalem &lt;baseline&gt;/bun --version   -&gt;  1.3.14
  qemu-x86_64 -cpu Nehalem &lt;default&gt;/bun  --version   -&gt;  Illegal instruction

Also drops the installer's closing note about the status bar asking for
permissions on first launch: shipped plugins are granted implicitly now, so the
prompt it told people to expect no longer appears. (That text is baked into the
0.1.9 tarball; it is wrong but harmless, and corrected for the next one.)

And gitignores *.core -- qemu writes one where you stand when it kills a target
on an illegal instruction, and a 100MB core dump nearly went into this commit.

0.1.9 contains everything since 0.1.8: the split button actually visible and
clickable where it is drawn, including on stacked panes; shipped plugins not
asking for permission; the status bar as a framed box in the pane style with a
new-tab button and margins matching the pane gap, collapsing to a single line
in the mobile layout; and runtime tabs keeping a status bar without losing the
purpose the CLI uses for identity.
</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>cli: carry pane templates into the generated layout</title>
<updated>2026-08-13T19:19:22Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T19:19:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=94a1fd85e524bbe079b6ce3c8bb17295b706834c'/>
<id>urn:sha1:94a1fd85e524bbe079b6ce3c8bb17295b706834c</id>
<content type='text'>
`sl0ppi up` died on every session:

    × Failed to parse Zellij configuration
      43 │         sl0pbar size=3
         ·                ╰── Invalid tab property: sl0pbar

The CLI writes its own layout file and inlines the tab template into each tab
-- a default_tab_template would destroy the tab purposes that are the CLI's
notion of identity -- so the generated file is standalone. The moment the
status bar became a pane_template, the inlined body referred to a name only
layouts/default.kdl knew about.

extractPaneTemplates pulls those definitions out with the same brace scanner
that already had to respect strings and comments, and both generated layouts
emit them once at the top. The result is smaller than before, too: one copy of
the bar's configuration per session instead of one per tab.

packaging/checks/sl0ppi-up-live.py runs the real thing -- CLI, binary, session,
attached client -- because the CLI's own tests are string assertions and were
all green while this was broken. It fails with exactly the user's error on the
parent commit.

Not done, deliberately, and the check says why in a comment: copying the user's
default.swap.kdl next to the generated layout to make the bar collapse on a
narrow terminal. It appears to work, and then auto_layout swaps the tab to one
of the other swap layouts, whose bare `tab` nodes expect a default_tab_template
that a generated layout does not have -- and the bar disappears entirely.
Measured on screen both ways, and the check now pins "the bar survives a
rearrangement" so nobody re-derives it from JSON.

Which is the other lesson: `action list-panes` stops reporting the borderless
plugin pane once a client is attached. It had me chasing a bar that was on
screen the whole time. dump-layout and the screen agree; list-panes does not.
</content>
</entry>
<entry>
<title>sl0p: the plugins we ship do not ask for permission</title>
<updated>2026-08-13T18:08:50Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T18:08:50Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=fb7076efd6d3ab5c1d36b6e554045d87b43c15b6'/>
<id>urn:sha1:fb7076efd6d3ab5c1d36b6e554045d87b43c15b6</id>
<content type='text'>
pane-finder requests three permissions as it loads, so the first press of a
keybinding we ship opened a consent screen instead of a fuzzy finder. Prompting
is right for a wasm you found on the internet and wrong for the ones that come
in our own tarball.

plugin_is_shipped_with_sl0ppi (zellij-utils/src/input/permission.rs) grants
those implicitly, through the same instruction a human "y" produces, so the
plugin's permissions are set and recorded exactly as before. "Ours" is narrow
on purpose:

- builtins, asked via the existing PluginConfig::is_builtin -- the same
  predicate check_command_permission already uses to exempt them, so the two
  cannot drift;
- files inside a plugin directory we install into: the config dir's plugins/
  and the data dir's plugins/;
- nothing else. Remote plugins are never trusted, paths are normalised
  lexically first so plugins/../../evil.wasm is not "inside" the dir, and the
  directories are derived from the environment and default locations -- so
  getting them wrong costs a prompt and can never grant one elsewhere.

The trade, documented in config/README.md: &lt;config dir&gt;/plugins/ is now a
trusted directory. Anything able to write there can already bind arbitrary
commands to a key in config.kdl, so a consent screen for the wasm beside it was
not buying anything -- but it does mean plugins/ is not a download folder.

packaging/checks/plugin-permissions-live.py drives both halves on a real
session: the shipped plugin must run without the prompt, and the SAME wasm
loaded from outside the plugin dir must still ask. Confirmed by disabling the
rule and watching the first half fail with the exact "asks permission to ...
Allow? (y/n)" screen it was written against.
</content>
</entry>
<entry>
<title>sl0p: make the split button work on stacked panes too</title>
<updated>2026-08-13T17:39:18Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T17:39:18Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=ae726b5bb3ff8e9e8f2bef0e9fbc9d4dcb62a79e'/>
<id>urn:sha1:ae726b5bb3ff8e9e8f2bef0e9fbc9d4dcb62a79e</id>
<content type='text'>
Reported still dead on a live session after the previous fix. Stacks move the
frame twice more, and neither move was in the hit-test:

- a stack list is drawn over the top rows of its visible member, pushing that
  pane's frame down. The renderer applies this as a frame geom override
  (reserved_top_rows); the click side did not, so every pane in a stack was
  clickable one row above its own title. frame_rect_with_top_reserved() now
  applies the same shift, before the gap inset, in the same order as render;
- a stack's collapsed members drew a button on their one-row title. Nothing
  could honour it -- that row is the stack's picker, and "split the pane that
  is not on screen" is not a thing to ask anyway. They no longer draw one, and
  the click side mirrors the condition. 120 snapshot lines change, every one
  of them a one-liner losing its button and nothing else (same width, same
  content otherwise; audited mechanically).

Both confirmed by reintroducing each bug and watching the check fail. A third
candidate fix -- resolving a click to the stack's visible member, on the theory
that get_pane_at could return a collapsed sibling -- turned out to be
unnecessary: collapsed members have one-row geoms, so position lookup already
lands on the right pane. Removed rather than kept "just in case".

The check itself was wrong twice before it was right, in the direction that
matters most (reporting failures that were its own): it now reads until the pty
goes quiet instead of for a fixed time, and parses only a freshly forced
repaint, so a half-arrived screen cannot look like a button drawn where it is
not. It clicks every button it can see, and runs against config/config.kdl as
well, whose stacks the minimal config never had.

When the two disagree, the server's own log settles it: log::info! in
intercept_split_button_click, read /tmp/sl0ppij-$UID/sl0ppij-log/.
</content>
</entry>
<entry>
<title>sl0p: make the split button visible, and clickable where it is drawn</title>
<updated>2026-08-13T16:46:58Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T16:46:58Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=2f0750285b1b174a552113c4aed9288f44985ae3'/>
<id>urn:sha1:2f0750285b1b174a552113c4aed9288f44985ae3</id>
<content type='text'>
The `+` shipped in 0.1.7 and 0.1.8 without ever appearing on a live session,
while rendering in 265 snapshots. Four faults, each enough on its own:

- title_line_without_middle computed the right side only inside the left
  side's and_then, so a pane with no title (hide_default_pane_names, the live
  default) got none at all;
- title_line_with_middle discarded the right side whenever the left was
  absent -- that is every frame in a session with a second client, which the
  live box always had, and "MY FOCUS AND:" on every frame was saying so;
- the title was budgeted before the button, so a title longer than the frame
  (any shell showing a path) consumed its columns. The button is anchored and
  hit-tested by pure geometry, so it cannot be the part that loses;
- the click hit-tested position_and_size() while the frame is drawn inset by
  the pane gap, putting the clickable columns two right of the glyph and a row
  above it at the top of the screen. Hence "the click works" and "nothing is
  drawn" both being true, and that click test proving nothing.

Adds Pane::frame_rect(), the rect the frame is really drawn in, which the
status bar buttons had already had to work around by hand.

Also fixes render_my_focus building its short form from separators that
Vec::append had already drained: 4 cells wide while reporting 6, a title line
two cells short of its pane. Found by asserting the width in the new test.

Verification, since unit tests have now been insufficient here four times:
split_button_is_drawn_where_it_is_hit_tested covers the combinations the live
config produces, and packaging/checks/split-button-live.py (make check-live)
reads the glyph off a real session, attaches a second client to force the
middle builder, and clicks the columns it found the glyph in. Both confirmed
to fail on the parent commit.
</content>
</entry>
<entry>
<title>docs: handover</title>
<updated>2026-08-13T14:32:42Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T14:32:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=694b9757423e8ed4b4bb0b57d4f8640687cc034c'/>
<id>urn:sha1:694b9757423e8ed4b4bb0b57d4f8640687cc034c</id>
<content type='text'>
Where the split-button work stopped, what is open, the traps that cost time, and
how to verify pane/layout/mouse work on a real session rather than in the test
harness.
</content>
</entry>
</feed>
