<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/config, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/config?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/config?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-13T18:54:29Z</updated>
<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>
<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>docs: record the target-dir symlink coupling and refresh stale provenance</title>
<updated>2026-08-12T18:47:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T18:47:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=ec5c8d9381edebdd2a43ce3504783d7983a025bd'/>
<id>urn:sha1:ec5c8d9381edebdd2a43ce3504783d7983a025bd</id>
<content type='text'>
The disk risk in the roadmap described a situation that no longer exists (12G
free, no monorepo build yet). Replaced with the real standing risk: sl0ppij/target
is a symlink into the old ~/lol/zellij checkout, so deleting that checkout breaks
builds until 'rm sl0ppij/target &amp;&amp; make build plugins'.
</content>
</entry>
<entry>
<title>build: ship sl0pstatus, and install every plugin the package carries</title>
<updated>2026-08-12T18:46:39Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T18:46:39Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=045ed6a48a4f65abdafe8222e548b758fb6626a0'/>
<id>urn:sha1:045ed6a48a4f65abdafe8222e548b758fb6626a0</id>
<content type='text'>
Phase 3: `make package` now produces a tarball that provably works on a machine
with no node, npm, bun or rust.

Two bugs the clean-room test found, neither visible until a session actually
started:

  - the default layout builds its status bar out of zjstatus, which we were not
    shipping at all (vendored as sl0pstatus/ in the preceding commits)
  - install.sh installed a hardcoded plugin list, so zjstatus.wasm would not
    have reached the config dir even once staged

Also: layouts/*.kdl still pointed at ~/.config/zellij, missed by the rename pass
because that pass only rewrote config.kdl.

Verified end to end, from the tarball, in a throwaway HOME with node/npm/bun/
cargo shadowed by stubs that fail loudly if invoked:

  sl0ppij --version        -&gt; sl0ppij 0.45.0+sl0p1
  pi --version             -&gt; 0.84.1        (on the bundled bun)
  pir --help               -&gt; ok
  sl0ppij -s realtest      -&gt; session starts under a pty, config "Well defined",
                              fork pane titles render, both wasm plugins load,
                              no errors beyond the first-run permission cache

Artifact: sl0ppi-0.1.0-x86_64-linux.tar.gz, 83M, + .sha256.
</content>
</entry>
<entry>
<title>build: follow the rename through config, packaging and docs</title>
<updated>2026-08-12T18:03:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T18:03:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=04ce010651a73a99d57e8b1c30bcc4f1346c9c52'/>
<id>urn:sha1:04ce010651a73a99d57e8b1c30bcc4f1346c9c52</id>
<content type='text'>
  config/config.kdl   plugin path -&gt; ~/.config/sl0ppij/plugins, builtin plugins
                      -&gt; the sl0ppij: URL scheme. Verified to load under the
                      renamed binary ("[CONFIG FILE]: Well defined.").
  install-config.sh   syncs to ~/.config/sl0ppij
  install.sh          a stock zellij on the target is no longer a warning, it is
                      just a fact: the two coexist
  stage.sh            drops the "renaming at package time is a fig leaf" note
  Makefile            builds `-p zellij` (package) and ships `sl0ppij` (binary);
                      `make check` now also gates the root package, using the
                      prebuilt asset plugins so it does not need debug wasm
  docs/               rename.md records what was done, what was deliberately
                      left (plugin-internal strings live in prebuilt wasm), and
                      the verification actually performed
</content>
</entry>
<entry>
<title>config: snapshot the live environment as source of truth</title>
<updated>2026-08-12T17:35:04Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T17:35:04Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=cd2e81d50569271f69d7f3c99aaf8a0295087f33'/>
<id>urn:sha1:cd2e81d50569271f69d7f3c99aaf8a0295087f33</id>
<content type='text'>
The most opinionated part of sl0ppi was never in any repo: it lived in
~/.config/zellij as dotfiles, versioned by ~20 hand-made `config.kdl.pre-*`
backups. Imported verbatim so the packaging work has something to install.

Deliberately not imported:
  - config.kdl.pre-* / layouts/*.pre-* / themes/*.pre-* backups (that is what
    git is for now)
  - plugins/pane-finder.wasm (a build output of sl0ppij/sl0p/zj-pane-finder)
  - plugins/zjstatus.wasm (a third-party blob no longer referenced by
    config.kdl -- dropped as dead weight)

The snapshot is faithful, so it still hardcodes development-machine paths;
config/README.md lists what the rename work has to parameterise.
</content>
</entry>
</feed>
