<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/cli/test/layout.test.ts, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/cli/test/layout.test.ts?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/cli/test/layout.test.ts?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-13T20:07:45Z</updated>
<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>cli: keep the status bar, so other projects are visible</title>
<updated>2026-08-13T11:06:44Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-13T11:06:44Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=6d9e46274c0d7613441338e10fea6972e4d335dd'/>
<id>urn:sha1:6d9e46274c0d7613441338e10fea6972e4d335dd</id>
<content type='text'>
Reported: only one tab for proj_a, where was proj_b? Both tabs existed --
list-tabs showed proj_b at position 1 the whole time -- but there was nothing on
screen to say so, because a generated session had NO tab bar and NO status bar.

sl0ppi passes its own `--layout` when creating a session, which replaces the
user's default layout, and the shipped config gets its chrome from
`layouts/default.kdl`'s `default_tab_template` (the zjstatus pane, which is what
draws the tab names). So a root with five projects looked exactly like a root
with one.

The obvious fix is to emit that `default_tab_template` into the generated
layout. It does not work: a templated tab comes back with `purpose=None`,
because the fork hangs a tab's purpose on the tab's root layout and templating
replaces it. Identity is load-bearing here -- with no purposes `sl0ppi ls` sees
no project tabs and `up` would happily create a second set of them. Caught by
checking list-tabs after the change rather than trusting the tab bar appearing.

So the template is INLINED instead: its body is taken, the project's panes are
substituted for its `children` node, and the result becomes the tab's contents.
The `tab` node keeps its own name/purpose/cwd, and the status bar comes along.
A template with no `children` node is skipped rather than mangled.

The extractor brace-matches by hand but respects strings and comments: a real
zjstatus template is full of `{index}` and `{name}` inside quoted format
strings, and counting those would truncate the block and emit a layout that does
not parse. It reads the live config, so a user's own default.kdl is honoured.

Verified on the test box: tab bar reads "1 proj_a  2 proj_b", tab purposes are
project:proj_a / project:proj_b, `sl0ppi ls` sees both, `up` still says "up to
date", and no phantom-client marker.
</content>
</entry>
<entry>
<title>cli: up, ls, open, close</title>
<updated>2026-08-12T20:24:55Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T20:24:55Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=2cd247828d863cdc779c7178c988152835096ca2'/>
<id>urn:sha1:2cd247828d863cdc779c7178c988152835096ca2</id>
<content type='text'>
Slice 2 of docs/cli-design.md. The commands read the world (discovery +
list-tabs), compute a plan, then apply it -- never mutating while walking, which
is how `up` stays idempotent.

  sl0ppij.ts   process wrapper; every piece of state comes back out of
               list-tabs/list-panes, so there is nothing to desync
  layout.ts    composes the tab node around a project's fragment
  root.ts      root resolution, session naming, the root registry
  workspace.ts up / ls / open / close
  index.ts     dispatch + the fail-open wrapper

Four things this turned up that reading the code would not have:

  - `attach --create-background` fails with "There is no active session!" when
    the caller is itself inside a session -- which sl0ppi almost always is,
    being run from the user's daily-driver terminal. Session creation now runs
    with the SL0PPIJ*/ZELLIJ* markers stripped: we are creating a new detached
    session, not nesting.

  - falling open by passing our own argv to sl0ppij just produces "unrecognized
    subcommand: up" and still leaves the user without a terminal. Our verbs and
    flags are stripped first, so what is handed over is something sl0ppij
    actually understands.

  - attaching panics in the client when stdin is not a tty, so `up` from a
    script or CI step died. It now reports the session is ready and exits.

  - a start_suspended pane reports `is_held`, not `exited`. Conflating them made
    `ls` claim every agent was running when none had started. Plugin panes (the
    status bar, the link handler) also inflated pane counts and are now excluded
    as chrome.

Index conventions were verified against a live session rather than inferred,
because guessing closes the wrong tab: list-tabs `position` and
`close-tab --tab-id` are 0-based, `go-to-tab` is 1-based, and closing renumbers
every tab after it, so batched closes run in descending position order.

Verified end to end against a real session (three projects, one shipping a
custom fragment): up creates the session with all tabs and their purposes; up
again reports "up to date" with tabs unchanged; ls reports panes and
agent:suspended correctly; open creates-and-focuses and resolves unique
prefixes; close removes a tab; deleting a project auto-closes its tab; and with
the root itself missing, nothing is closed at all.

64 tests green.
</content>
</entry>
</feed>
