aboutsummaryrefslogtreecommitdiffstats
path: root/docs (follow)
Commit message (Collapse)AuthorAge
* release: 0.1.9, with a bun that runs without AVX2HEADmainuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 <baseline>/bun --version -> 1.3.14 qemu-x86_64 -cpu Nehalem <default>/bun --version -> 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.
* sl0p: keep a templated tab's purpose, so runtime tabs get a status baruser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* cli: carry pane templates into the generated layoutuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | `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.
* sl0p(bar): draw the bar as a framed box, and a single line when collapseduser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* sl0p(bar): a new-tab button, and margins that match the pane gapuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* sl0p: the plugins we ship do not ask for permissionuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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: <config dir>/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.
* sl0p: make the split button work on stacked panes toouser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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/.
* build: cut the edit loop from 52s to 5s with more codegen unitsuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | zellij-server is one enormous crate and `fastdev` was building it in 16 codegen units, which is the granularity the incremental cache can skip work at: a one-line change re-optimised a sixteenth of the crate. Splitting the crates we actually edit into 256 units makes that a sliver instead. Measured on the same one-line change to pane_boundaries_frame.rs, `git checkout` of the file to a linked binary: codegen-units = 16 52s codegen-units = 256 6s, 4s on a second run + opt-level = 1 5s (rejected: not worth a slower binary) Ruled out on the way: linking the 69MB binary is ~2s, and rust-lld did not beat the default linker on it, so the linker was never the problem. Root crate codegen plus link is ~2s of the total. Costs one 80s rebuild when switching profiles, and the fastdev binary keeps opt-level 3. Verified with the full server/utils suites and make check-live.
* sl0p: make the split button visible, and clickable where it is drawnuser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* docs: handoveruser2026-08-13
| | | | | | 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.
* docs: record the responsive fix and how to re-verify ituser2026-08-13
| | | | | | The tab harness cannot reproduce this (no rendering means panes_to_hide is always empty), so the verification is a scripted pty that resizes mid-session. Written down so it can be re-run rather than re-derived.
* docs: record the responsive stacking bug with a deterministic reprouser2026-08-13
| | | | | | | | | | | | A pane added after a narrow->wide responsive cycle is absorbed into a stack, because the tab stays pinned to the synthesized sl0p:responsive-expand layout, which was generated for the pane count at synthesis time. Creating the session wide and adding a pane is correct; creating it narrow, widening, then adding is not. apply_responsive_layout only runs on resize, which is why resizing appears to fix it. Includes the instrumented step table and the geometry that proves the stack, so the next session can start from the mechanism rather than the symptom.
* cli: create sessions by attaching, to dodge the phantom-client buguser2026-08-13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Reported symptom: a pane titled "MY FOCUS AND:" in a fresh sl0ppi session. That is zellij's multi-user indicator. A session created with `attach --create-background` keeps one client in its connected set forever, so `connected_clients.len() > 1` and every pane frame renders its multi-user form with one anonymous colour block. sl0ppi created every session that way, so every sl0ppi session looked like someone else was in it. Isolated by comparison rather than by reading: created with attach --create-background marker on the FIRST attach, and every one after created by an ordinary attach that dies no marker, ever Ruled out by measurement: not the CLI-action clients (15 actions with no attach leaves nothing behind), and not the creating client exiting too early (making it wait for a server ack -- 94ms, server answered -- changed nothing; that experiment was reverted rather than shipped with a comment claiming a fix it did not deliver). The client-id change did not cause this either: it only made it visible, because recycled ids used to hand the phantom's id straight to the next client that attached. So: when `sl0ppi` has a terminal it now creates the session by ATTACHING to it (`--layout X attach --create NAME`) instead of creating it in the background and attaching second. That is the path with no leak, and it is one process instead of two. Headless `up` keeps the background path, where no one is looking at a pane frame. This is a workaround. The fork bug is recorded in docs/roadmap.md with what has been ruled out and where to instrument next, and it still affects anyone calling `zellij attach -b` directly. Verified on the test box under a real pty: no marker on creation or on any later attach, both project tabs present, agents still staged (purpose='agent:main' held=true cmd='pi').
* docs: record the flakiness as fixed, with the measurementsuser2026-08-13
| | | | | Both causes are now understood and closed, so the known-issue section becomes a record of what it was and how it was proven fixed.
* docs: locate the remaining action flakiness in assert_socketuser2026-08-13
| | | | | | | Records the exact function and the two defects in it (judging liveness on the first message received, and deleting a live socket on a transient ConnectionRefused), plus what the resize fix did and did not solve. The handshake change is deliberately left unmade rather than rushed.
* release: 0.1.1, and stop the CLI carrying a second versionuser2026-08-12
| | | | | | | | | | | | | | Published 0.1.1 with the dead-session and flaky-read fixes. Also removes the `sl0ppi-cli` line from MANIFEST and the version field from cli/package.json: the CLI ships as part of the product, so a version of its own only drifts -- a 0.1.1 release shipped a MANIFEST claiming sl0ppi-cli 0.1.0. Records the terminal-level flakiness as a known issue with the measurements, so it is a tracked defect rather than folklore: list-tabs/list-panes fail ~60% of the time against a detached live session on a 2-vCPU box, ~25% with a client attached, and the action client blames "no active session" for what is really a transient enumeration failure.
* release: publish sl0ppi.sl0p.foo, and a script to keep it currentuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Phase 5, settled as build scripts rather than CI: make release tests, then a fresh static-musl tarball make publish render the site, upload, and verify against the live URL publish.sh is deliberately dumb -- rsync, then ask the live site whether it agrees (status codes plus a re-downloaded checksum) -- but it has two guard rails that matter: - it refuses to republish a version whose bytes differ from what is already live. /releases/ is cached for a year, so the same URL serving two different tarballs would be undebuggable. Bump VERSION instead. - artifacts upload before latest.json, so the pointer never names a file that is not downloadable yet. site/install.sh is POSIX sh and short on purpose, because people are being asked to pipe it into a shell. It reads latest.json, downloads the versioned artifact, VERIFIES ITS SHA256, and only then unpacks and runs the bundled installer. Refusal was tested for real by serving a doctored latest.json: it stops with a checksum mismatch and installs nothing. Verified end to end from the public URL, in a throwaway HOME with node, npm, bun, cargo and rustc replaced by poison stubs: curl -fsSL https://sl0ppi.sl0p.foo/install.sh | sh sl0ppij --version -> 0.45.0+sl0p1 pi --version -> 0.84.1 sl0ppi doctor -> all good The vhost itself is committed in the sl0pday repo, where that box's nginx config lives.
* build: ship the sl0ppi CLI in the tarballuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The artifact was stale twice over: it predated the protobuf fix and did not contain the CLI at all. Both fixed, and the whole chain re-verified from the tarball rather than assumed. The CLI ships as a single 31K file (`bun build --target=bun`) run by the bundled bun, rather than a source tree or a second `--compile`d binary that would embed another 90M copy of bun. `install.sh` generates the wrapper and pins SL0PPI_BIN to the sl0ppij it just installed, so the orchestrator can never end up driving some other zellij that happens to come first on PATH. Verified in a throwaway HOME with node, npm, bun, cargo, rustc and python3 all replaced by poison stubs that fail loudly if invoked: sl0ppij --version -> 0.45.0+sl0p1 (static musl, no libc dependency) pi --version -> 0.84.1 (bundled bun) sl0ppi doctor -> all good sl0ppi init x2 -> two projects scaffolded sl0ppi up -> session "dev", 2 tabs, agents suspended sl0ppi ls -> agent:suspended, 1 pane each sl0ppi up (again) -> "up to date" And the promise the whole thing exists for, end to end: attach a client, press Enter in the suspended agent pane, and pi starts on the bundled runtime -- while the other project's agent stays suspended, because only the focused pane got the key. `make test` now runs the CLI suite alongside the Rust ones, so the two halves cannot drift apart unnoticed.
* cli: init and doctor, and stop one bad project breaking a rootuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Slices 3 and 5 of docs/cli-design.md. `init` writes .sl0ppi/layout.kdl. The template is mostly comments, because the purpose vocabulary and the start_suspended habit are the two things nobody can guess, and the point of use is the cheapest place to explain them. A test asserts the template passes doctor's own fragment check -- shipping people a starting point that fails our validation would be embarrassing. `doctor` splits into gathering facts (IO) and judging them (pure), so the rules are testable. It is honest about its limits: sl0ppij has no parse-only mode, so fragments get a structural check (empty, unbalanced braces, a whole `layout {}` pasted in, a `tab` node sl0ppi owns) rather than a claim of validity. It also reports the known packaging hole -- pir needs python3 and fzf, neither of which is in the tarball. Driving them for real turned up a worse bug than either command: a single broken fragment took down the entire root On session creation every tab shares one layout file, so one unbalanced brace made the whole `up` fail and no project opened at all. Fragments are now checked before use on BOTH paths, broken ones are skipped with the reason, and the rest open. `up` also registers the root even when some projects failed, since forgetting it would orphan the tabs that did open. Two smaller fixes from the same session: - falling open with no tty used to spawn a bare sl0ppij, which silently CREATES a session; that is where a handful of randomly-named junk sessions came from during testing. It now reports and exits. - a rename leftover: `setup --check` still described "the configuration of zellij". Test isolation lesson worth recording: sessions live in the runtime dir (/run/user/1000/sl0ppij), not TMPDIR, so setting TMPDIR alone does not isolate them -- an earlier test session called `root` was silently reused by a later test. SL0PPIJ_SOCKET_DIR is what actually isolates. 88 tests green.
* docs(cli): record the fork gap the design turned upuser2026-08-12
| | | | | | Runtime new-tab dropped purposes until 5873374; the design depends on it, so the dependency is written down rather than assumed. Also notes the single-line KDL terminator trap, which cost a debugging round.
* docs: agree the sl0ppi CLI designuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | One root dir = one sl0ppij session, one project = one tab, panes from the project's .sl0ppi/layout.kdl fragment. Verified against the fork before designing on top of it, rather than assuming: - `action new-tab --layout|--layout-string --name --cwd` exists and returns the new tab id - `tab name= purpose=` is reported by `action list-tabs --json` - purposes accept [A-Za-z0-9_.:/-] up to 64 chars, so "project:foo.a1b2c3" survives the sanitiser verbatim - layout-declared purposes outrank in-band ones and survive resurrection, so tab identity cannot be spoofed from inside a pane and is stable across a session restart - `start_suspended` exists, which is what keeps `up` on a twelve-project root from becoming twelve running servers Load-bearing property: `up` diffs discovered projects against list-tabs and creates only what is missing, so running it repeatedly is a no-op. First thing the tests will assert. Entry point fails open: any error warns and execs plain sl0ppij with the original args. Today's incident is the argument -- when the environment breaks, the terminal is what you debug it with, so the orchestrator must never stand between the user and their multiplexer. Also records two problems found while designing: tab overflow (twelve projects vs a status bar showing ~six, so `open` wants to be a picker) and that the shipped `pir` needs python3 + fzf, neither of which is in the tarball.
* build: ship a static musl binary instead of a glibc-2.39 oneuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | The gnu release build required GLIBC_2.39 (Jan 2024), so the "install it on another machine" artifact would have failed on Debian 12, Ubuntu 22.04 and RHEL 9. That is the whole premise of this repo, so it is a defect, not a nit. make build -> cargo zigbuild --target x86_64-unknown-linux-musl result -> ELF, statically linked, 0 GLIBC_ symbols, 51M make build-gnu kept for fast local use A musl build needs a musl C toolchain: isahc/curl pulls libz-sys and libnghttp2-sys unconditionally (confirmed: --no-default-features does not drop them). cargo-zigbuild + the ziglang python module were already installed here, so this needs no sudo, no docker daemon and no system packages. stage.sh now refuses to be quiet about it: it reports "statically linked" or warns loudly that the artifact inherits the build box's libc. Re-verified from the tarball in a throwaway HOME with node/npm/bun/cargo replaced by poison stubs: static binary, pi 0.84.1 on the bundled bun, both wasm plugins installed, session starts under a pty with no errors. Remaining libc floor is bun's own glibc 2.17 (2012); bun publishes a musl build if Alpine ever matters.
* docs: record the target-dir symlink coupling and refresh stale provenanceuser2026-08-12
| | | | | | | 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 && make build plugins'.
* build: ship sl0pstatus, and install every plugin the package carriesuser2026-08-12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 -> sl0ppij 0.45.0+sl0p1 pi --version -> 0.84.1 (on the bundled bun) pir --help -> ok sl0ppij -s realtest -> 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.
* build: follow the rename through config, packaging and docsuser2026-08-12
| | | | | | | | | | | | | | | | config/config.kdl plugin path -> ~/.config/sl0ppij/plugins, builtin plugins -> 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
* build: scaffold versioning, docs and the packaging pipelineuser2026-08-12
Adds the layer that makes this a distributable product rather than three directories: VERSION one sl0ppi version for the whole monorepo FORKS.md what each component is based on, and how to rebase the fork Makefile build / vendor / stage / package, plus dev-box helpers packaging/ fetch pinned payloads, assemble an install tree, install it docs/ architecture, the rename plan, the roadmap The self-contained runtime question is settled and verified: pi upstream builds its own release binary with `bun build --compile`, and a vendored bun runs the vendored pi dist unmodified, so the tarball carries bun + pi and needs no system node, npm or rust on the target machine. packaging/install.sh does a per-user install under ~/.local, never clobbers an existing config (writes .new alongside, or backs up under --force-config), and warns loudly when a stock zellij is present -- because until docs/rename.md is implemented the binary is still zellij underneath and would collide with it.