aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-16 19:13:28 +0200
committeruser <user@clank>2026-08-16 19:13:28 +0200
commitc58d873b409c7a166a64ceabf178692b0b4467c2 (patch)
tree45ed8190996bfa1c7b59309c1b07d50f5dadae19 /docs
parentsl0ppty: the prompt speaks config, not a dialect of it (diff)
downloadslosh-c58d873b409c7a166a64ceabf178692b0b4467c2.tar.gz
slosh-c58d873b409c7a166a64ceabf178692b0b4467c2.tar.xz
slosh-c58d873b409c7a166a64ceabf178692b0b4467c2.zip
sl0ppty: a pane can change tabs
sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2}' sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}' sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":0,"name":"logs"}' The mechanism only. There is no key and no gesture for it yet -- that is the next conversation, and guessing at it now would mean building the wrong one twice. **The pane keeps running.** Measured, not assumed: `pid=3062557` before the move and `pid=3062557` after, in a different tab, with its scrollback intact and still answering what it is sent. That is the whole reason to move a pane rather than open one where you wanted it and close this one. It is tree surgery and it reuses the surgery that was already here. `place_beside` is the second half of `split_node_with` -- everything after "make the leaf" -- lifted out so that a leaf which already exists somewhere else can be grafted the same way, flat sibling or new split, on either side. It takes the tab rather than `cur(a)` now, because replacing a root has to land on the right one and `cur(a)` was only right while the sole caller was splitting the pane you were looking at. `detach_leaf` is `close_leaf` without the funeral. Three decisions worth naming: **The destination is a tab id, not an index.** A tab whose last pane leaves has nothing to be, so it is removed -- and every index after it moves. An id survives that; an index quietly means a different tab. The move re-finds the destination by id *after* the removal for exactly this reason, and there is a test that empties a tab and checks the pane landed where it was sent. **A move drops what the old tab thought about the pane** -- a zoom naming it, and its minimised flag. Both are the tab's opinion and not the pane's: carried across, the first zooms a pane that has left and the second files the arrival in a strip nobody asked for. **It does not follow the pane.** Which tab you are looking at is a question about intent, and this is the mechanism; `move-pane` answers with the tab id so a caller can select it if that is what it meant. "Into a tab of its own" is its own call rather than new-tab-then-move, because a pane that is the only thing in its tab would otherwise have its tab removed from under the new one, and the order those two happen in is precisely the bug. Verified: `tests/test_move_pane.py`, 25 checks -- the pane still running and still talking afterwards, placement beside and under the destination's focus, focus landing on the arrival, the emptied tab, a tab of its own, every refusal (no such pane, no such tab, already there, already alone), the dropped zoom and minimise, and after three moves a layout with no overlaps that dumps and rebuilds as itself.
Diffstat (limited to 'docs')
-rw-r--r--docs/layouts.md20
-rw-r--r--docs/scripting.md1
2 files changed, 21 insertions, 0 deletions
diff --git a/docs/layouts.md b/docs/layouts.md
index 36029ab..7166751 100644
--- a/docs/layouts.md
+++ b/docs/layouts.md
@@ -64,6 +64,26 @@ sl0ppty -s work cmd '{"cmd":"apply-layout","kdl":"layout { tab { pane } }","repl
Without `replace`, the tabs the file describes are added to what is already
there.
+## Moving a pane between tabs
+
+```bash
+sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2}' # beside that tab's focus
+sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":2,"dir":"rows"}' # under it instead
+sl0ppty cmd '{"cmd":"move-pane","id":3,"tab":0,"name":"logs"}' # into a tab of its own
+```
+
+The pane keeps running: same pty, same scrollback, same process — a move is tree
+surgery, not a new pane and a funeral. The destination is a tab **id** rather than
+an index, because a tab whose last pane leaves is removed and every index after it
+shifts; an id survives that.
+
+Two things a move deliberately drops, both of them the old tab's opinion rather
+than the pane's: a zoom that named it, and its minimised flag. Carried across, the
+first would zoom a pane that has left and the second would file the arrival in a
+strip nobody asked for. Which tab you are *looking* at does not change.
+
+There is no key for this yet — the mechanism first, the presentation after.
+
## Purposes
A pane or tab can carry a `purpose=` label — `agent:main`, `logs`, `db` — for
diff --git a/docs/scripting.md b/docs/scripting.md
index de7e712..b311a3e 100644
--- a/docs/scripting.md
+++ b/docs/scripting.md
@@ -31,6 +31,7 @@ Panes and tabs are addressed by **id**, so a background tab is scriptable.
| `close` `rerun` | `id`, or 0 for the focused pane |
| `clear-shaders` | `id`, or 0 for the focused pane; answers `cleared:0\|1` — the way back from a pane that painted itself unreadable |
| `new-tab` `select-tab` `close-tab` `move-tab` `set-name` | tabs, by `id` or `index` |
+| `move-pane` | `id` of the pane, `tab` id to move it into (`0` for a tab of its own, with an optional `name`), `dir:"cols"\|"rows"`. The pane keeps running — same pty, same scrollback |
| `set-purpose` | `target:"pane"\|"tab"`, `id`, `purpose` |
| `dump-layout` `apply-layout` | see [layouts](layouts.md) |
| `notify` | put a line in the session's status area |