aboutsummaryrefslogtreecommitdiffstats
path: root/src (follow)
Commit message (Collapse)AuthorAge
...
* sl0ptty: M6 — the config file, and a READMEsl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | D2, delivered: a hand-rolled KDL subset (kdl.c, 300 lines, no dependency) with arguments, key=value properties, children, // and /* */ comments, and ; terminators for single-line nodes. config/config.kdl documents every setting by being the defaults. Geometry, theme, the prefix and every binding now come from that file instead of being compiled in. Bindings are a table of (key, mods) -> action, so an action can be rebound, unbound with "none", or given a different prefix entirely; caps and num lock are masked out so they cannot stop a binding working. The property that matters is fail-open, applied to the mux itself rather than only to the CLI wrapper: a missing config is silent, a broken one writes the line number to stderr and keeps the compiled-in defaults, and a bad binding does not discard the rest of the file. A live session can reload, and a reload that fails to parse keeps the configuration that works instead of leaving you with half a theme. min_pane is configuration too, so the point at which a layout collapses is a preference rather than my taste — tested by making the same split collapse or not on the same terminal size. 14 parser cases as a C test, 24 config checks through the harness, and a README that shows what the thing actually looks like. 250 checks green.
* sl0ptty: M5 — responsive collapse, status bar, pane findersl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | D6 as promised: layout(tree, rect) recomputed every frame, and a split that cannot give every child a floor (24 cols / 6 rows) collapses *itself* into a stack — one expanded child, every sibling a one-row header. No swap layouts, no remembered position, no synthesized anything. Three properties fall out of the design rather than being coded: - the expanded child is the one holding focus, so focusing a collapsed pane expands it: from the finder, by clicking its header, or by walking into it; - a hidden pane is never resized, so its program does not reflow off-screen; - a narrow/wide cycle returns the exact rects it started with, and a pane added afterwards is not stacked. That last one is the fork's 'panes stacked after a responsive collapse/expand cycle' bug, asserted directly. It cannot regress here without the layout function growing memory it does not have. A collapsed header shows the pane's title and its OSC 5577 status, so a stacked agent is still legible — the monitored strip, without a 'rail' mode. Below the size where headers fit, a node shows only the focused subtree: at 12x4 you get one pane instead of rects that do not fit on the screen. Every size from 200x60 down to 12x4 is asserted for overlap and containment. Status bar: tabs, a +tab button, live pane count, and the held prefix shown as a mode. The button is '+tab', not '+', because the frame already has a '+' for splitting — two verbs sharing a glyph is a UI bug the fork shipped, noticed, and wrote down; the collision showed up here as two hit-list tests failing. Pane finder (C-a f): substring filter over title, purpose and tab name across every tab, keyboard and mouse, painted last so its rows win the hit list. It is how you reach a pane the screen has collapsed away, which is tested. Deferred to M6: drag-to-reorder, which wants the same drag-state machine as pane resizing and should be built with it. 213 checks green (186 headless + 27 live).
* sl0ptty: M4 — OSC 5577 pane status bars, buttons and click reportssl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The payoff milestone: sl0ppi's pi extensions work here unmodified. printf '\033]5577;1;status;building 3/7\033\\' printf '\033]5577;1;buttons;approve:Approve;cancel:Cancel\033\\' ╰ building 3/7 ──────────────────────[Approve]─[Cancel]─╯ libghostty-vt's UNKNOWN_SEQUENCE reports APC only, so the sequences are picked up by a scanner (osc5577.c) reading the same bytes the terminal does — lib-vt discards an OSC it does not know, so nothing is drawn and no pane output has to be buffered. The scanner is a byte-at-a-time state machine, so a sequence split across reads still lands. Buttons are budgeted from the right before the status text gets any columns, and each registers its hit as it is painted — the split button's rule, for the split button's reason. A press sends the click report to that pane's stdin. In-band purposes arrive here too, and by construction can never be declared: a pane printing one is asking, not telling. A pane cannot relabel a purpose a layout declared, which is now tested end to end rather than only at the API. One real bug, found by the hostile-input tests: an over-long button id was truncated into a *valid* one, so a pane could mint a 40-character id that collides with the 32-character id its program already trusts. Ids are now unescaped into a large buffer and validated at full length. Acceptance test replays answer-picker's exact byte patterns, including `buttons` with no payload (drop buttons, keep status) and a click report that matches the extension's own CLICK_RE. Four test bugs on the way, each worth remembering: printf eats %3B as a format specifier; the driver's unescaper reads \0 as NUL so \033 became NUL+'33'; a trailing ST inside a double-quoted shell string escapes the quote (BEL avoids it); and readline eats a typed ESC as a meta prefix, so tests that drive a pane must not use an interactive shell. 187 checks green (160 headless + 27 live).
* sl0ptty: M3 — tabs, purposes, and the JSON control APIsl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Tabs are layout trees, one per tab, and panes in every tab keep running: fds, pumping and reaping span all of them, only the current one is composed. A one-row strip shows them (M5 makes it the real status bar) and each label registers a hit, so clicking a tab switches to it through the same mechanism as every other click. C-a c new tab C-a n / C-a p cycle C-a 1..9 select Purposes on panes and tabs with D8's trust model implemented as a lock: declared (from a layout or an operator) outranks in-band, cannot be overridden, and everything is sanitised to [A-Za-z0-9_.:/-] on ingest — 'project:foo bar/../$(rm -rf)' becomes 'project:foobar/../rm-rf'. The in-band path arrives with the OSC scanner in M4 and always passes declared:false; the refusal is already tested. The control API is now JSON in, JSON out: {"cmd":"new-tab","name":"agent","purpose":"project:x.deadbeef"} {"ok":true,"id":2} with panes/tabs/snapshot/send/raw/resize/split/focus/close/select-tab/ set-name/set-purpose/alive/quit. Panes are addressed by id, not by 'the focused one', so a background tab can be scripted. The bare-verb form stays as a harness alias and runs the same code, so the tests cannot drift from the API. Adds a small JSON reader (jsonval.c) — we could only emit until now. 129 checks green.
* sl0ptty: M2 — server/client split, detach/reattach, control socketsl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The session now outlives its client, which is the point: agents keep running while you are gone. The server owns the panes, the layout and the renderer; the client is a terminal in raw mode and a socket, forwarding bytes and painting what it is sent. It does not even decode input, so the decoder can change without redeploying clients. sl0ptty attach to 'main', creating it if nobody is home sl0ptty -s NAME a named session sl0ptty ls live sessions sl0ptty cmd LINE one control command C-a d detach Connections are not clients. A connection becomes the display client only by sending HELLO; control commands and liveness probes are just connections and never displace anybody. This was found the hard way, one hour after the server first ran: 'sl0ptty ls' kicked the attached client off, because ls connects to see whether a socket answers. That is precisely the fault that gave the zellij fork a 20-25% CLI failure rate, rebuilt and then fixed structurally. The regression runs 20 x (ls + control) against a live client and demands 0 failures and a client that can still type. cmd.c now holds the command vocabulary once, shared by the headless driver and the control socket, so a script written against one works against the other — 'sl0ptty cmd "snapshot text"' against a detached session prints its screen. tests/test_session.py: 18 checks over create, split, probe-resistance, detach, survival, drive-while-detached, reattach, displacement and cleanup. 123 checks green overall.
* sl0ptty: M1 — layout tree, splits, focus, frames, and the hit list in usesl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | app_t owns the tree, focus, the prefix and what a key means; the interactive client and the headless driver are now both thin front ends over it, so there is exactly one implementation of every command. Layout is the pure function D6 promised: layout(tree, rect) recomputed every frame, remainder spread across children so nothing drifts, and splitting in a direction that already exists grows that split rather than nesting — three splits give three equal columns, not 1/2 + 1/4 + 1/4. Chrome: rounded frames, aspect-corrected gap, centred titles. The split button is budgeted BEFORE the title and hit-tested from the rect it was drawn in, which is the fix the fork needed four attempts to land. Every painted element registers (rect, action) as it is painted; a click is a lookup. Mouse into a pane is translated to pane-local coordinates. Three bugs the harness caught, none of which unit tests would have: - one click on the split button split twice: press *and* release both fired. Chrome now acts on press; forwarding to a pane still gets every event. - callbacks were registered as &fn, but the option is pointer-typed, so the stored 'callback' was a stack address. Any OSC 2 or DSR jumped into a dead frame. Now a hard segfault turned into two regression tests. - a keystroke that changed nothing still emitted a cursor hide/show pair. The cursor is hidden lazily now, so an idle frame costs zero bytes. 96 checks green in 6s (40 unit, 23 screen, 33 layout), plus 9 live.
* sl0ptty: M0.5 — the headless driver and the harness everything rests onsl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | --script runs the whole compositor with no tty: commands in (send, raw, settle, resize, snapshot, alive), composited screen out as JSON — rows of text, style runs, cursor, and the hit list. Styles are emitted as runs rather than per-cell objects, because a test wants 'columns 0..5 of row 0 are bold red', not 1920 objects. The hit list is empty until M1 paints something clickable, but the type, the JSON, and hit_test() exist now: one geometry, established before there is anything to get wrong. Also fixes two host-policy defaults found by the cursor never appearing. lib-vt is a VT library, not a terminal, so it starts with DECTCEM (mode 25) off and grapheme clustering (2027) off. Both are now set via MODE_DEFAULT, so they survive RIS. tests/harness.py + tests/test_screen.py: 19 checks over composition, styling, wide chars/clusters, scrollback, resize and the full input round trip — including the kitty/legacy re-encoding matrix, which no longer needs a pty. make test is now 2.4s for 59 checks; make test-live keeps the four that genuinely need a tty.
* sl0ptty: input decoder + per-pane re-encodesl0p2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | The half libghostty-vt does not do: decode what the outer terminal sends us into semantic events, then re-encode each event for the target pane against that pane's own negotiated modes. Decoder (src/input.c): legacy keys and modifiers, SS3, tilde keys, the kitty keyboard protocol (CSI u, shifted/base layout sub-params, press/repeat/release, associated text), SGR mouse incl. drag and wheel, bracketed paste, focus events, alt-prefix, UTF-8, and partial sequences retained across reads. Re-encoding (pane_send_key/mouse/paste) drives lib-vt's key and mouse encoders via setopt_from_terminal, so a pane gets exactly what it negotiated. The mouse encoder wants surface pixels; we declare a 1x1 pixel cell so cell coordinates are exact rather than approximated. The prefix key is handled on decoded events, not bytes, so it cannot be confused by a byte that merely looks like C-a inside a UTF-8 or escape sequence. Tests: 40 table-driven decoder cases (tests/input_test.c) + a live check that drives the same client into a legacy pane and a kitty/mouse pane and asserts each receives its own encoding (tests/live_input.py). 59 checks, all green.
* sl0ptty: M0 — libghostty-vt vendored, one pane, compositor, diff renderersl0p2026-08-13
Design contract in DESIGN.md. libghostty-vt vendored fresh from ghostty main@bedb6f2 (zig 0.16), built to a 14MB static lib. M0: pty spawn (no libutil), a lib-vt terminal per pane, a screen cell buffer, a diff emitter that repaints only what changed, raw mode, SIGWINCH, and a ~120Hz frame cap that coalesces pty reads. Also the seed of the headless harness: --headless dumps the composited screen without a tty, so composition is testable in milliseconds. tests/live_m0.py covers what headless cannot: raw mode, the diff emitter, resize propagation, and the quit path. 6/6 green.