Releases: cablehead/stacks2099
v0.5.0
v0.5.0
Highlights
This release makes stacks2099 scriptable. The HTTP API is now self-describing: the binary serves its own /api markdown docs, rendered as minijinja templates with idiomatic Nushell examples and an absolute binary path so the snippets run as-is. /api/state enumerates every clip tagged with its stack, every terminal, and each terminal's working directory (OSC 7). A new /api/events endpoint streams the live log frame feed as newline-delimited JSON.
The clip API grew up. /clip/move takes an absolute ?to= index and a ?stack= target for cross-stack moves; /clip/add accepts an optional ?view= to set display style at creation; /stack/new returns {id} for JSON callers; and /pty/label was renamed to /clip/label. eval now emits results as JSON (strings raw, structured values as JSON) rather than display text. Looking up a clip by an unknown id returns a clean 404 instead of a silent 204.
Terminals are now observable over HTTP: a live raw-output tee, GET /pty/snap for a plain-text buffer snapshot, and clickable wrapped terminal URLs. Resize re-emits the full grid so deltas stay in sync, and grid updates travel as HTML records framed in Nushell.
Navigation went vim-style. h/l alias k/j for clip navigation (niri-friendly), Shift+H/Shift+L alias clip move, x terminates a clip, and the layout toggle moved to w. The full command set works bare in navigate mode (except delete), the New-clip picker takes t/n letter shortcuts, and new clips land directly after the selected clip. Reorders animate with FLIP instead of a hard jump, and newly created or moved clips scroll into view.
Under the hood, the binary now builds on the published http-nu 0.17.1 crate, dropping the vendored engine, and unpacks its bundled app into the store directory rather than a per-user cache.
Raw commits
- docs: add Cate comparison (2026-06-07)
- refactor: build on the http-nu 0.17.1 crate, drop the vendored engine (2026-06-07)
- fix(api): by-id clip routes 404 on an unknown clip instead of silent 204 (2026-06-05)
- feat(ui): Shift+H/Shift+L alias clip move (K/J); move layout toggle to w (2026-06-05)
- fix(ui): seed rename modal from the cursored clip's DOM, not the lagging $label slot (2026-06-05)
- docs: flag the planned h/l rebind (direction-aware) as a breaking change (2026-06-05)
- feat(ui): h/l alias k/j for clip navigation (vim-style, niri-friendly) (2026-06-05)
- feat(ui): terminate clip on x (was d, leader-only); free bare l (layout -> Shift+L) (2026-06-05)
- feat(ui): make the full command set work bare in navigate mode (except delete) (2026-06-05)
- feat(ui): scroll a newly created clip into view once its pane mounts (2026-06-05)
- fix(ui): reveal moved clip via transform-immune offsets, not mid-FLIP rects (2026-06-05)
- feat(ui): scroll the moved clip into view after a reorder, without focusing it (2026-06-05)
- feat(ui): animate pane reorder with FLIP instead of a hard jump (2026-06-04)
- feat(ui): new clips land directly after the selected clip, not top/bottom (2026-06-04)
- feat(api): GET /api/events -- live log frame feed as newline-delimited JSON (2026-06-04)
- feat(api): richer /clip/move -- absolute ?to= index and cross-stack ?stack= (2026-06-04)
- feat(api): optional ?view= on /clip/add to set display style at creation (2026-06-04)
- feat(api): /stack/new returns {id} for Accept: application/json callers (2026-06-04)
- refactor(api): rename /pty/label to /clip/label (query params), drop pty-meta label mirror (2026-06-04)
- feat(api): enumerate every clip in /api/state, tagged with its stack (2026-06-04)
- docs: call out the HTTP/1.1 terminal connection limit and HTTP/2 fix (2026-06-04)
- docs: document the per-store app unpack mechanism (2026-06-04)
- feat: unpack the bundled app into the store dir, not a per-user cache (2026-06-04)
- feat(ui): t/n letter shortcuts in the New-clip picker (2026-06-04)
- docs: research note on OSC 52 clipboard in wezterm-term (2026-06-04)
- feat(eval): emit string results raw, structured results as JSON (2026-06-04)
- fix(ui): crop scru128 ids to the tail, not the head (2026-06-04)
- feat(eval)!: emit result as JSON instead of display text (2026-06-04)
- fix(clip): never orphan a clip from a bogus selectedStack (2026-06-04)
- feat(api): self-contained /api docs -- absolute binary path + eval -c framing (2026-06-04)
- feat(pty): report each terminal's cwd (OSC 7) in pty list and /api/state (2026-06-04)
- docs(api): render /api docs as minijinja templates; idiomatic Nushell examples (2026-06-04)
- feat(api): self-describing /api markdown docs that travel with the binary (2026-06-04)
- fix(pty): clean 404 for an unknown sid on the /pty GET routes (2026-06-04)
- feat(pty): pty snap + GET /pty/snap for a plain-text buffer snapshot (2026-06-04)
- feat(pty): live raw-output tee + list terminals in /api/state (2026-06-03)
- test(e2e): pass options to waitForFunction, not as the bound arg (2026-06-03)
- test(e2e): fix close-clip cursor-drift race and a waitForFunction timeout arg (2026-06-03)
- test(e2e): drive rename with real key events, not synthetic dispatch (2026-06-03)
- fix(pty): re-emit full grid on resize so deltas don't desync (2026-06-03)
- refactor(pty): emit HTML grid-update records, frame datastar in nushell (2026-06-03)
- feat(pty): make wrapped terminal URLs clickable (2026-06-03)
- chore: bump to v0.4.1-dev (2026-06-03)
v0.4.0
v0.4.0
The keymap is rebuilt around a mod+K leader, international keyboards type into terminals, and a clip's view is now separate from editing it. Each stack is its own page with a per-tab cursor.
Highlights
International keyboards type into the terminal. Dead-key and IME composition (macOS Option+e e -> an accented char, Compose, CJK) reach the pty via a focused hidden input. Composed glyphs (Danish |, ~, \) forward as themselves instead of becoming Meta sequences. You can still select grid text, and Cmd+C copies it.
Modifier arrows reach the terminal. Alt/Ctrl/Cmd + arrow (plus Home/End/PageUp/Down and Alt/Ctrl+Backspace) send the xterm modifier sequence, so word- and line-motion work in a focused TUI. They used to arrive as a plain arrow.
New keymap. In navigate mode j/k move between clips, Shift+J/Shift+K reorder, Enter focuses. Every other command is under mod+K: press it then a letter, or pause for the panel (a which-key list). Clip keys j/k/v/r/d/o/J/K; stack keys n/N/R/s/l/[/]/g. The Alt chords are gone.
Focusing a clip sends it your keystrokes -- a terminal's pty, or a text clip's source editor -- as if it were the only thing on the page. mod+Enter focuses and leaves.
A clip's view is separate from editing it. Markdown cycles raw <-> rendered with mod+K v, independent of focus. Focusing an editable clip opens its source over the current view; leaving returns to that view -- rendered stays rendered. Editing used to force raw.
Each stack is its own page (/stack/<id>) -- back/forward and deep links work. The clip cursor is per tab, so two tabs browse independently and selection no longer round-trips through the server.
Tighter chrome. The top bar is just the stack breadcrumb and theme; the actions and new-clip panels rise out of the status bar; panes sit flush; the three bars are one size.
Fix: the embedded shell builds the $nu constant before reading config, so env.nu/config.nu using $nu.config-path (including parse-time consts like NU_LIB_DIRS) no longer error.
Thanks to @kiil for testing, especially the international-keyboard handling.
Raw commits
- docs: scrub stale Alt-chord references in README Model + sessions.html comments (2026-06-03)
- feat(keys): stack nav in the mod+K leader; remove all Alt chords (2026-06-03)
- feat(clips): split view (style) from focus (editing) -- two independent axes (2026-06-03)
- fix(pty): encode modifiers on cursor/edit keys so Alt/Ctrl/Cmd+arrow reach the terminal (2026-06-03)
- fix(ui): remove doc padding + bottom modals rest on the status bar; share a .bar class (2026-06-02)
- fix(ui): new-clip picker rises from the status bar (replaces the actions modal slot); tighten footer padding + pane-head small to match the bars (2026-06-02)
- feat(ui): remove Sort/Layout/New from the top bar (they live under mod+K now) (2026-06-02)
- feat(keys): label the d action Delete, not Close (2026-06-02)
- feat(keys): mod+K global tier (new clip/stack, rename stack, sort, layout); Shift+J/K move in navigate; drop redundant Alt chords (2026-06-02)
- feat(keys): plain Enter focuses the selected clip; refresh README keys for the leader + j/k + IME (2026-06-02)
- feat(keys): bare j/k navigate in navigate mode; mod+K toggles the panel; 150ms hint (2026-06-02)
- feat(keys): mod+K modal owns the keyboard; row letters work in the panel (ADR 0008) (2026-06-02)
- refactor(serve): emit selector-only remove patch per datastar spec (2026-06-02)
- docs(adr): 0008 -- mod+K modal owns the keyboard continuously (panel is a delayed visual); any action ends ownership (2026-06-02)
- docs(adr): 0008 leader keymap -- client-resident, two-tier, derived per-clip from the cursored pane (2026-06-02)
- test(e2e): reap the --dev server by store path so it can't orphan (2026-06-02)
- feat(keys): mod+K leader -- mod+K then j/k/r/d/o runs a clip command (ADR 0007) (2026-06-02)
- docs: sweep for consistency after the client-owned-cursor MPA work (2026-06-02)
- docs(adr): 0007 mod+K leader for clip commands (panel is the which-key hint) (2026-06-02)
- refactor: derive the status-bar dims client-side (ADR 0006 c) (2026-06-02)
- refactor(projection): drop clipCursors per-stack cursor memory (ADR 0006 b) (2026-06-02)
- fix(sse): /nav stops echoing the cursor -- kills the click morph race (ADR 0006 slice 4) (2026-06-02)
- feat(stacks): switching stacks is navigation, not selection (ADR 0006 MPA slice 3) (2026-06-02)
- feat(sse): scope /sse to the URL's stack (ADR 0006 MPA slice 2) (2026-06-02)
- feat(serve): per-stack URLs -- / redirects to /stack/ (ADR 0006 MPA slice 1) (2026-06-02)
- test(serve): endpoint test for /clip/add + /clip/update via do $handler (2026-06-02)
- refactor(serve): drop redundant is-empty branch in add-clip/set-clip-body (2026-06-02)
- refactor(serve): route via http-nu router dispatch, not a raw match (2026-06-02)
- docs(adr): supersede 0006 steps 4-6 with per-stack MPA (2026-06-02)
- refactor(client): pick next clip on close, client-side (ADR 0006 step 3) (2026-06-02)
- refactor(client): name the pty session id 'pty', not the overloaded 'sid' (2026-06-02)
- refactor(sse): collapse selection onto one client signal $cursor (ADR 0006 step 2) (2026-06-02)
- ci: pin deno to 2.8.1, exclude vendored stellar.css from fmt (2026-06-02)
- refactor(sse): clips-sidebar highlight is reactive on $selectedSid (ADR 0006 step 1) (2026-06-02)
- ci: deno fmt --check covers the whole repo (scope in deno.json) (2026-06-02)
- docs(adr): 0006 client-owned cursor (per-tab; server keeps stack cursor memory) (2026-06-02)
- style: deno fmt all supported files; add deno.json (proseWrap preserve) (2026-06-02)
- refactor: remove the canvas pane (old experiment) (2026-06-01)
- fix(pty): don't refocus on Cmd/Ctrl shortcuts, so Cmd+C keeps the selection (2026-06-01)
- fix(pty): don't refocus the hidden input while text is selected (2026-06-01)
- feat(browser): record.mjs to capture a session to video (2026-06-01)
- feat(pty): refocus the hidden input on keypress so grid text stays selectable (2026-06-01)
- feat(pty): capture dead-key/IME composition via a focused hidden input (2026-06-01)
- fix(keys): a focused clip owns all keys but mod+Enter/mod+K (ADR 0005) (2026-06-01)
- fix(pty): forward Option/AltGr-composed characters literally, not as Meta (2026-06-01)
- docs(adr): resolve focus carve-out to leave-then-navigate (j/k navigate-only) (2026-06-01)
- docs(adr): 0005 mode-projected keymap (focused clip gets keys raw; supersedes 0004) (2026-06-01)
- docs(readme): link Stacks to stacks.cross.stream (2026-05-31)
- chore: gitignore .claude/scheduled_tasks.lock (2026-05-31)
- test(repl): regression test for the $nu constant; extract enter_interactive (2026-05-31)
- fix(repl): create $nu constant + set is_interactive before config eval (2026-05-31)
- docs(release): note auto-body from changelog; end with a terse shipped summary (2026-05-31)
- chore: drop binstall metadata (http-nu fork leftover; not on crates.io) (2026-05-31)
v0.3.0
v0.3.0
stacks2099 is the stacks tool-for-thought, rebuilt on cross.stream and Datastar. This release builds out the workspace around that core: layout, theming, a single top bar, and a server-rendered terminal.
Highlights
Horizontal, niri-inspired layout. Each stack can lay out as a horizontal scrollable strip instead of a column. Alt+L toggles it.
Themeable colors. Pick a terminal palette from the top bar. Themes are plain CSS variables, so adding one is a few lines.
One top bar. The top and status bars are merged into a single bar: the stack breadcrumb on the left, clip handles on the right.
-
New clip. A top-right dropdown; type to pick terminal or note, no mouse needed.
-
Clip actions (mod+K). Rename, close, move, or resize the focused clip from one panel, even over a running terminal.
-
Stack switcher. The breadcrumb opens a modal to jump between stacks or make a new one. Alt+\ opens it; Alt+[ and Alt+] cycle.
Manual clip ordering. Reorder clips and sort a stack with keyboard chords; the manual order holds even as clips update.
Docs. A new journey.md walks through how the server-rendered terminal came together. A stacks-session skill edits clips on a running session.
Server-rendered terminal. Terminals render as an HTML grid with per-line diff patching, so only the lines that change repaint. URLs are clickable, even when they wrap across lines. Wide characters line up. Apps that clear the screen redraw cleanly.
Raw commits
- docs(changelog): drop cursor-overlay jargon from the terminal highlight (2026-05-31)
- docs(readme): contextual theme shot next to a focused terminal (2026-05-31)
- docs(changelog): recapture screenshots in context, add stacks switcher (2026-05-31)
- test(browser): capture feature shots in context (focused/themed terminal) (2026-05-31)
- build: exclude changes/ from deno fmt so changelogs stay single-line (2026-05-31)
- feat(stacks): Alt+\ opens the switcher (+ e2e) (2026-05-31)
- docs(readme): refresh for v0.3.0 (install methods, stacks switcher, layout/theme, screenshots) (2026-05-31)
- feat(stacks): top-left switcher + breadcrumb + Alt+[ ] cycle (stack nav in niri) (2026-05-31)
- docs(changelog): size screenshots with img tags, focus panes before capture (2026-05-31)
- docs(changelog): scannable v0.3.0 highlights with feature screenshots (2026-05-31)
- test(browser): feature-shots.mjs for cropped per-feature screenshots (2026-05-31)
- chore: release v0.3.0 (2026-05-31)
- chore(release): generate and ship a per-version changelog (2026-05-31)
- feat(layout): widen niri panes to 100ch (2026-05-31)
- feat(theme): themable terminal palette via CSS vars + top-bar theme picker (2026-05-31)
- feat(ui): consolidate top and status bars with visible New and Actions handles (2026-05-31)
- fix(layout): resize pty after niri flip reflows, not before (stale width) (2026-05-30)
- feat(actions): Cmd-K opens clip actions even over a focused terminal (2026-05-30)
- fix(ci): restore deleted dagger.json so release-binaries can load the module (2026-05-30)
- feat(actions): bottom-right clip-actions panel (rename/close) via mod-K (2026-05-30)
- test(picker): e2e for keyboard quick-select and no key leak to terminal (2026-05-30)
- feat(picker): top-right new-clip dropdown with keyboard quick-select (2026-05-30)
- fix(pty): rescan hyperlinks after resize reflow (2026-05-29)
- docs(journey): reflow prose with deno fmt (2026-05-29)
- feat(pty): detect wrapped URLs by scanning logical lines on the live screen (2026-05-29)
- fix(notes): refresh panes live; protect in-flight edits via data-ignore-morph (2026-05-29)
- feat(pty): clickable links and fixed-width wide cells in the grid (2026-05-29)
- docs(journey): use nu consistently in diagrams, drop reedline (2026-05-29)
- chore: narrow .claude gitignore to settings.local.json (2026-05-29)
- fix(sessions): vertical scroll on niri panes (2026-05-29)
- docs(skills): add stacks-session skill for reading/editing clips on a running session (2026-05-29)
- docs(journey): drop the 'paint l, paint s' callback opener (2026-05-29)
- docs(journey): fix link citations, align byte/OSC examples with the DEC parser spec (2026-05-29)
- fix(sessions): focus the textarea for notes via data-render, not data-kind (2026-05-28)
- docs(journey): drop 'The flow is short' filler (2026-05-28)
- fix(serve): rename keeps terminal grid alive + seed rename modal from $label signal (2026-05-28)
- docs(journey): fix dangling 'one' (has neither); de-clunk the wezterm-term sentence (2026-05-28)
- docs(journey): link original experiments, note ghostty-web is wasm, add pre-ghostty-web framing (2026-05-28)
- docs(journey): open with what stacks2099 is; remove em-dash prose punctuation (2026-05-28)
- docs(journey): tighten lede, lead with the xterm.js contrast and no-state payoff (2026-05-28)
- fix(pty): handle primary <-> alternate screen flips with a full re-emit (2026-05-28)
- docs(journey): open with the xterm.js contrast to frame the result (2026-05-28)
- docs(journey): deno fmt; add journey.md to check.sh fmt gate (2026-05-28)
- docs(journey): link state-building framing to libghostty post (2026-05-28)
- docs(journey): link third-party tools to sources, credit Benny's prompt (2026-05-28)
- docs(journey): tighten tense and person consistency in narrative beats (2026-05-28)
- test(browser): catch the SSE-join blank-line bug at the DOM, not just the wire (2026-05-28)
- docs(journey): add grid-state section, link tmux source, consolidate terminology (2026-05-28)
- fix(pty): concat appended rows on one data: elements line so SSE join doesn't insert literal newlines between them (2026-05-28)
- feat(hud): break down rows shipped per patch into +added -removed ~changed (2026-05-28)
- docs(journey): wrap escape sequences in backticks so angle brackets render (2026-05-28)
- fix(pty): namespace row ids per pane so diff patches don't bleed across terminals (2026-05-28)
- docs(journey): clarify server-side wezterm-term, link Datastar Discord (2026-05-28)
- Update journey.md (2026-05-28)
- docs(journey): blog-style headings, fold ghostty swap into vt100 section (2026-05-28)
- feat(pty): per-row seqno diff stream + separate cursor overlay (2026-05-28)
- docs(journey): move flow diagram into Stage 1 (2026-05-28)
- docs: journey.md tracing progression to server-rendered terminal grid (2026-05-28)
- feat(terminal): morph HUD shows rows touched per server patch (2026-05-28)
- fix(serve): skip pane repaint on position-only clip.patch (2026-05-28)
- test(browser): row id stability across scrollback purge (2026-05-28)
- fix(terminal): subtract pane-head height from available rows (2026-05-28)
- feat(layout): niri collapses nav rails + terminals fill column height (2026-05-28)
- feat(layout): per-stack niri layout (scrollable strip) + Alt+L toggle (2026-05-28)
- perf(pty): stable row ids so scrollback purge skips full-grid morph (2026-05-28)
- test(browser): shoot --add to file the shot into the running stack (2026-05-28)
- test(browser): shoot.mjs to screenshot a running instance (2026-05-28)
- docs: README for clip ordering (auto/manual, move + sort chords) (2026-05-27)
- feat(order): client reorder + move/sort chords (phase 3c) (2026-05-27)
- feat(order): /clip/move + /stack/sort endpoints, docOrder signal (2026-05-27)
- feat(order): fractional position helper + sort by position (2026-05-26)
- docs: add CLAUDE.md (keep CI nu in sync with the bundled engine) (2026-05-26)
- ci: run nu tests on 0.112.1 to match the bundled engine (2026-05-26)
- chore: bump to v0.2.1-dev (2026-05-26)
v0.2.0
Full Changelog: v0.1.0...v0.2.0
v0.1.0
Full Changelog: https://github.com/cablehead/stacks2099/commits/v0.1.0




