The cleanest way to navigate a stacked-diff workflow isn’t with typed commands — it’s with a TUI. gt tui opens an interactive terminal interface to your stacks: branches on the left, PR status on the right, vim keys to navigate, single letters to act. Once you’ve used it for an afternoon, typed commands start to feel like the long way round.

This article is a guided tour.

Open it

In any repo with gt repo init already done:

gt tui

You’ll see something like this:

┌─ STACK ────────────────────────┐  ┌─ PR / MR ─────────────────────────┐
│ ● main                         │  │  feature/auth-base                 │
│ ├─ ● feature/auth-base        │  │  PR #41 · open · 2 approvals       │
│ │  ├─ ● feature/auth-oauth    │  │  Required: lint ✓  test ✓  deploy ⏳│
│ │  │  └─ ◉ feature/auth-2fa   │  │                                    │
│ │  └─ ○ feature/auth-recovery │  │  Recent activity                   │
│ │                              │  │  · @alice approved 2h ago          │
│ ├─ ● feature/db-migration     │  │  · @bob commented 5h ago           │
│ └─ ● feature/router-cleanup   │  │  · github-actions deploy started   │
│                                │  │                                    │
└────────────────────────────────┘  └────────────────────────────────────┘
[ status: clean · 3 stacks · 9 branches · branch: feature/auth-2fa ]

That’s the core layout. Stacks on the left (a tree, with the current branch marked ). On the right, details about whatever branch the cursor is on: PR status, required checks, recent activity, reviewer state.

The status line at the bottom shows your overall state (working tree clean, the current branch, total counts).

The whole TUI is keyboard-driven; the mouse works too but most people land on the keys.

KeyAction
j / kMove down / up
h / lCollapse / expand a stack node
g / GJump to top / bottom of the tree
TabMove focus between left and right panes
/Filter by branch name (fuzzy match)
?Open the keybindings cheat sheet
qQuit

The vim keys are the muscle-memory default; arrow keys also work.

Acting on branches

Pressing letters performs actions on the current branch:

KeyAction
cCreate a new branch on top of the current one
sSubmit (or update) the PR for this branch
SSubmit the entire stack
rRestack on top of the parent’s latest
RRestack the whole stack
mModify (amend) the current branch’s tip
nSync with origin (fetch + restack + prune merged)
dDelete a branch (with confirmation if it has unmerged work)
LLand — merge this branch’s PR
EnterOpen the branch’s PR in your browser

Each action runs inside a non-blocking task with a progress bar. You can keep navigating while a long-running submit happens in the background.

Inspecting diffs

Pressing D opens an inline diff viewer for the current branch (diff against its parent):

┌─ DIFF: feature/auth-base ↔ main ──────────────────────────────────────┐
│ src/auth/schema.sql                                                    │
│ +  CREATE TABLE auth_session (                                         │
│ +      id UUID PRIMARY KEY,                                            │
│ +      user_id UUID REFERENCES users(id),                              │
│ +      ...                                                             │
│ src/auth/middleware.rs                                                 │
│ +  pub async fn auth_middleware(req: Request) -> Result<Response> {    │
│ ...                                                                    │
└────────────────────────────────────────────────────────────────────────┘

The diff viewer supports syntax highlighting via Tree-sitter, hunk-by-hunk navigation, and word-level diff (toggle with w). It’s not meant to replace your editor — but for “is this branch what I expect it to be?” it’s faster than opening the PR in a browser.

Esc returns to the stack view.

Conflict resolution

When gt_restack (R from the TUI) hits a conflict, the TUI drops you into a conflict-resolution mode:

┌─ CONFLICT ────────────────────────────────────────────────────────────┐
│ feature/auth-oauth · 1 file with conflicts                            │
│                                                                       │
│ ▸ src/auth/middleware.rs                                              │
│                                                                       │
│ Press e to open in $EDITOR, n to skip this file,                      │
│        a to accept ours, b to accept theirs                           │
│                                                                       │
│ When done: c to continue · q to abort                                  │
└────────────────────────────────────────────────────────────────────────┘

In practice you press e, your editor opens the file, you resolve, save, close — and the TUI detects the resolution and offers c (continue). For simple conflicts (whitespace, renames, formatting), a and b cover most cases.

This is much friendlier than the git mergetool workflow.

Search and filter

For repos with many stacks, / is your friend. Type a few characters and the tree filters live:

/auth
↳ ● feature/auth-base
   ├─ ● feature/auth-oauth
   │  └─ ◉ feature/auth-2fa
   └─ ○ feature/auth-recovery

Esc clears the filter; Enter jumps to the selected branch and exits filter mode.

Multi-repo mode

If you keep multiple repos in ~/code/, you can launch the TUI in multi-repo mode:

gt tui ~/code/*

A repo picker appears at the top; r cycles between repos. This is convenient for “I have stacks in three services, show me the current state.”

Persisted state

The TUI remembers its state between launches: the last branch you were on, expanded/collapsed nodes, the focused pane. It uses STKD_TUI_STATE_PATH (default ~/.local/state/stkd/tui.json).

Delete the file to reset.

Customising

A few common tweaks. All go in ~/.config/stkd/config.toml:

[tui]
# Show PR check details inline on the tree
show_check_status = true

# Open editor on conflict instead of dropping into resolution mode
conflict_strategy = "editor"  # "editor" | "interactive"

# Refresh provider state on this interval (seconds)
refresh_interval = 30

[tui.keys]
# Override defaults
submit_stack = "S"      # default
delete_branch = "d"     # default

[tui.colors]
# Theme — accepts "auto", "light", "dark", or a path to a custom .toml
theme = "auto"

Custom themes are a small TOML mapping ANSI color names to CSS-style hex; see gt tui --print-default-theme for a starting point.

Why this matters

The point of the TUI isn’t to replace the CLI — typed commands compose better in scripts, in MCP, and in muscle memory once you have it. The point is that there’s a smooth on-ramp for new users and a fast browser for experienced ones.

Specifically:

  • New users get a visible tree of branches and PRs from the first second. They don’t need to memorise gt log flags.
  • Experienced users save half a dozen keystrokes per action. S (submit stack) is faster than typing gt submit --stack.
  • Code reviewers can gt tui in someone else’s repo to see the structure of a stack before reading the diff.
  • AI agents can describe what they’re about to do, and a human reviewer can confirm the state of the world by looking at the TUI rather than running a series of git commands.

What’s coming

A few features in flight:

  • Inline PR comment threads — read and reply to PR comments without leaving the TUI.
  • CI log tailing — when a check fails, peek the relevant log in a pane.
  • Multi-author stacks — visualise which branches are owned by whom.

Track the GitHub project board for ETAs.

Try it

brew install neul-labs/tap/stkd
cd ~/code/your-repo
gt repo init
gt tui

The whole cheat sheet fits on one screen (?). Spend a few minutes navigating — it sticks fast.

More