No description
  • Rust 84.6%
  • Python 11.9%
  • Nix 3.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
natsukium a331c6e379
All checks were successful
ci / cargo fmt / clippy / test / deny (push) Successful in 23s
chore(tui): follow felis to the v0.1.0 release (#5)
Reviewed-on: ⁨#5⁩
2026-09-26 20:53:19 +09:00
.forgejo/workflows chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
docs Follow felis to 105b0899: the 1.0 wire reset, minor 10, and a daemon-free socket path (#1) 2026-09-07 21:29:39 +09:00
scripts chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
src chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
.gitignore build: add flake-parts dev environment 2026-06-20 20:56:37 +09:00
Cargo.lock chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
Cargo.toml chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
clippy.toml chore(tui): adopt felis core's lint, format, and deny policy 2026-06-21 10:27:07 +09:00
deny.toml chore(tui): adopt felis core's lint, format, and deny policy 2026-06-21 10:27:07 +09:00
flake.lock build: add flake-parts dev environment 2026-06-20 20:56:37 +09:00
flake.nix chore(tui): provide cargo-deny in the devShell 2026-06-21 10:27:13 +09:00
LICENSE chore(tui): relicense to Apache-2.0 to match the felis ecosystem 2026-06-21 22:50:44 +09:00
README.md chore(tui): follow felis to the v0.1.0 release (#5) 2026-09-26 20:53:19 +09:00
rustfmt.toml chore(tui): adopt felis core's lint, format, and deny policy 2026-06-21 10:27:07 +09:00

felis-tui

A felis-native terminal multiplexer and TUI client.

felis is a GPU terminal emulator with a daemon/client split. Its sessions already survive the window closing — the daemon is the persistence layer — and felis deliberately refuses tabs, splits, and panes, delegating layout to the window manager (felis principle 1).

That delegation assumes a first-class WM. On Windows, on macOS, and over a bare SSH login, you may not have one. felis-tui fills that gap: it attaches to the felis daemon from any host terminal and provides the persistence and the splits that felis itself won't — while losing as little of felis's fidelity as the host terminal allows.

Why not just use tmux?

A multiplexer running inside another terminal can only render what that host terminal supports — that part is unavoidable. The difference is where the fidelity is lost:

  • tmux/zellij/dvtm re-parse the PTY byte stream with their own, necessarily incomplete, VT parser. They lose information at the parse step and again at the render step.
  • felis-tui consumes felis's already-parsed, structured grid (ShadowScreen) over the felis protocol. There is no re-parse. The only fidelity loss is the final re-encode into the host terminal's capabilities — the theoretical minimum.

That is the whole bet, and it is recorded as a hard non-goal in docs/non-goals.md: felis-tui never re-parses a PTY byte stream. The day it wants to, it has become tmux and lost its reason to exist.

Status

Working multiplexer: each pane is its own felis session over its own connection, mirrored and composited into one host terminal. The host is tiled into a scrollable row of columns (the niri model), each column a vertical stack of panes. By default widths auto-fit the host so it behaves like a snug tiling WM (presets: even-horizontal, even-vertical, main-vertical, tiled); resize a column and it switches to free, scrollable widths, panning to keep the focused column in view. Keys travel as key events that the felis daemon encodes against the session's own keyboard modes (full Kitty keyboard protocol), and colour is down-mapped to the host (true-colour passed through, else nearest xterm-256).

On a Kitty-graphics-capable host, images are re-emitted as Kitty graphics clipped to their pane — the thing tmux/zellij can't do, because felis-tui has the placement geometry as structured data rather than a re-parsed byte stream. On other hosts images are dropped client-side ("the client owns pixels"). Behaviour and config live in docs/roadmap.md.

cargo run            # first pane re-attaches a parked session, or spawns a shell

Prefix is Ctrl-A, then:

key action
h new pane in a new column beside the focus
v new pane below the focus, in the same column
s swap layout — cycle the preset, re-arranging the panes
> / < grow / shrink the focused column (switches to free, scrollable widths)
f toggle snug auto-fit ↔ free (scrollable) widths
p / n focus the column to the left / right — scrolls it into view
k / j focus up / down within the current column
o focus the next pane (cycle)
x close (detach) the focused pane — its shell keeps running
d detach all and quit
Ctrl-A send a literal Ctrl-A to the focused pane

Detaching never kills a shell: the session lives in the daemon, so closing felis-tui (or a pane) parks it for the next attach.

Config

Optional, at ~/.config/felis-tui/config.toml (or $XDG_CONFIG_HOME/felis-tui/config.toml). The [keys] table overlays the defaults, so you only list what you change. Keys bind to a fixed action set — there is no scripting, by design (see docs/non-goals.md).

prefix = "C-a"          # the multiplexer prefix chord
layout = "even-horizontal"  # startup preset (see below)

[keys]                  # key -> action (overlays defaults)
h   = "split-horizontal"
v   = "split-vertical"
s   = "swap-layout"
">" = "grow-width"
"<" = "shrink-width"
f   = "toggle-fit"
o   = "focus-next"
p   = "focus-left"
n   = "focus-right"
k   = "focus-up"
j   = "focus-down"
x   = "close"
d   = "detach"
"C-a" = "literal-prefix"

[theme]
separator = 8           # ANSI-256 index for pane separators

Actions: split-horizontal (new column), split-vertical (new pane in the column), swap-layout, grow-width, shrink-width, toggle-fit, focus-next, focus-left, focus-right, focus-up, focus-down, close, detach, literal-prefix. Key chords are a single character with an optional C- (Ctrl) or M- (Alt) prefix.

Layout presets (for layout and what swap-layout cycles through): even-horizontal, even-vertical, main-vertical, tiled.

Not yet

  • Pixel-level cropping of images that overflow a pane (today an overflowing or natural-size placement is skipped rather than cropped), scrollback-anchored placements, animation, and placeholders on incapable hosts.
  • Query-based graphics detection (today it is env markers).
  • Smooth scrolling: a partial "sliver" of the next column at the edge, and per-pane horizontal sub-scroll for a column wider than the host (today scrolling is whole-column).
  • Moving panes/columns (not just focusing them), and per-pane height resize within a column (today resize is column width only).
  • Scrollback viewport, copy mode, search.