Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

vig User Guide

vig is a read-only TUI cockpit for a repository and everything working around it: git (side-by-side diffs, log, reflog), GitHub issues / pull requests / Actions runs / Projects boards, a file browser, Docker containers, running processes, and worktrees / stashes — with vim-style keybindings throughout. It is built for keeping an eye on busy repositories, including ones where AI agents do the work.

Safe by design — vig only performs read operations and safe git commands (git switch, git branch -d). Destructive operations like merge, rebase, force delete, or push are intentionally excluded. vig helps you inspect your repository, not mutate it.

What’s in this guide

  • Getting Started — installation, your first run, a tour of the seven views, and what vig needs from your environment.
  • Views — one chapter per view: what it shows, every key binding, and each view’s constraints.
  • Configuration Basics — where config files live, the three layers (builtin → user → repo-local), the vig config subcommands, just enough KDL, and the merge rules.
  • Config Recipes — worked, copy-pasteable examples: themes, keybindings, tabs, layouts, slots, board pinning, per-repository config, polling. Every example is CI-verified.
  • Config Reference — the complete reference: every node with its form, default, merge rule and errors, and every page’s panes and actions.
  • Troubleshooting / FAQ — config errors, gh authentication and scopes, rate limits, fonts, image previews, and where vig keeps its files.

日本語版

This guide is also available in Japanese: vig ユーザーガイド (in-repo: docs/guide/ja).

Getting Started

vig runs entirely in your terminal. Install it, cd into a Git repository, and run vig — no configuration is required to get a working setup.

Installation

Homebrew

brew install td72/tap/vig

Pre-built binaries

Download a pre-built binary from the GitHub Releases page:

# Linux x86_64
curl -sL https://github.com/td72/vig/releases/latest/download/vig-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.local/bin vig

# Linux aarch64
curl -sL https://github.com/td72/vig/releases/latest/download/vig-aarch64-unknown-linux-gnu.tar.gz | tar xz -C ~/.local/bin vig

# macOS Apple Silicon
curl -sL https://github.com/td72/vig/releases/latest/download/vig-aarch64-apple-darwin.tar.gz | tar xz -C ~/.local/bin vig

crates.io

cargo install vig

Build from source

Requires: Rust toolchain, libgit2, libssl, pkg-config

cargo install --path .

First run

Run vig inside a Git repository:

cd your-repo
vig

You land in the Git view: the changed files on the left, branches and the reflog next to them, and the side-by-side diff filling the rest of the screen. The header lists the views as numbered tabs (1:Git, 2:GitHub, …); press the number to switch. The status bar at the bottom shows the current mode and the most useful keys for the focused pane.

A few things worth knowing on day one:

  • q or Ctrl+c quits.
  • ? opens a help overlay listing every binding of the current view.
  • r refreshes the current view; the Git view also refreshes automatically when files change on disk.
  • vig works without any configuration. When you want to change something, a single KDL file does it (--config <path>, $VIG_CONFIG, or ~/.config/vig/config.kdl) — see the configuration chapters.

A tour of the seven views

vig ships with seven views. Each gets its own chapter in Views; this is the thirty-second version.

1 — Git

The heart of vig: a side-by-side diff of your working directory with syntax highlighting, a file tree with status indicators, a branch selector with a git log preview, and the reflog. Compare against any branch or reflog entry, yank text with vim motions, and open files in $EDITOR.

2 — GitHub

Issues, pull requests (body, comments, reviews, CI status) and Actions workflow runs (jobs, steps, job logs) — browsed read-only through the gh CLI, with bodies rendered as Markdown.

3 — Files

A yazi-like three-column file browser rooted at the repository: parent directory, current directory, and a preview with syntax highlighting — images included, drawn at full resolution in terminals with a graphics protocol.

4 — Docker

Containers grouped by compose project, images, an inspect summary and a live log tail — read-only, via the docker CLI.

5 — Procs

A process tree with CPU and memory, listening ports and their owners, btop-style system history graphs, and a per-process detail with CPU / RSS sparklines. Inspect-only: vig never sends a signal.

6 — Worktrees

Your worktrees and stashes at a glance, with the HEAD commit or the stash’s patch shown in the same side-by-side diff view as the Git view.

7 — Projects

The GitHub Projects (v2) boards linked to the repository: kanban columns by Status, a sortable table mode, and an item detail with every project field.

Getting help inside vig

Press ? in any view to open the help overlay. It lists every key binding of the current view — including your own rebindings, since it is generated from the active configuration. Press ? or Esc to close it.

Keeping vig up to date

vig update

vig update downloads the latest release from GitHub, verifies its signature, and replaces the current binary. It is meant for installs from the pre-built release binaries; if you installed through Homebrew or cargo, prefer brew upgrade vig or cargo install vig so your package manager stays in charge.

Requirements

vig itself only needs a Git repository to run in. Some views use external tools when present:

ViewNeedsWithout it
Git, Worktreesnothing extra
GitHubGitHub CLI (gh) installed and authenticated (gh auth login)the view shows a notice
Projectsgh with the project token scope — run gh auth refresh -s projectthe view shows a notice explaining the missing scope
Dockerdocker CLI and a running daemonthe view shows a notice
Procsnothing extra (lsof on macOS / ss on Linux for ports)port info may be empty

Two smaller notes:

  • Nerd Font — the Files view shows file-type icons that need a Nerd Font. If your terminal font is not one, put icons "none" in your config.
  • $EDITORe opens the selected file in your external editor.

Views

vig is organized as a row of views — tabs in the header, numbered 1:Git through 7:Projects. Each view is a self-contained page with its own panes and key bindings; the following chapters cover them one by one.

Read-only by design

Before the keys, the philosophy — because it shapes every view:

vig inspects; it does not mutate. The only write operations in the entire program are the two safe git commands offered by the branch action menu: git switch and git branch -d (safe delete, which git refuses if the branch is unmerged). Everything else — GitHub, Docker, processes, worktrees, stashes, project boards — is strictly read-only:

  • The GitHub view never reruns, cancels, comments, or edits anything.
  • The Docker view never starts, stops, or removes containers or images, and never displays environment variables.
  • The Procs view never sends a signal, and never reads or displays environment variables.
  • The Worktrees view never applies, drops, or prunes anything.
  • The Projects view never adds, moves, edits, or deletes items.

You can leave vig open all day and it will not change your system. Destructive operations (merge, rebase, force delete, push) are not “hidden behind a setting” — they do not exist in the code.

Switching views

KeyAction
1Switch to Git View
2Switch to GitHub View
3Switch to Files View
4Switch to Docker View
5Switch to Procs View
6Switch to Worktrees View
7Switch to Projects View

The numbers are the position of the view in the header, which follows the pages list in the configuration — trim or reorder that list and the numbers follow.

Conventions shared by every view

The views deliberately feel the same:

  • j / k move, gg / G jump to top / bottom, Ctrl+d / Ctrl+u scroll half a page.
  • Tab / Shift+Tab cycle the panes of the view.
  • Selecting an item in a list updates its detail / preview pane immediately; i or Enter moves focus into it, h or Esc comes back.
  • / searches within the focused pane, n / N step through matches (case-insensitive).
  • r refreshes, ? shows help, q / Ctrl+c quits.

The key tables in these chapters show the default bindings, taken from the built-in configuration (assets/default.kdl), which is the source of truth. The README carries a condensed summary and may omit minor keys. Every key can be rebound or unbound; see the configuration chapters.

Git View

The heart of vig: a side-by-side diff of your working directory against a base of your choosing. Four panes across the top and main area:

  • Files — the changed files as a tree, with status indicators (A added, D deleted, M modified, R renamed, ? untracked).
  • Branches — every local branch, with a git log preview of the selected one in the main area.
  • Reflog — the repository’s reflog entries.
  • Diff / Git Log — the main area: the side-by-side diff with syntax highlighting, or the log preview while you browse branches or the reflog.

The view watches the working directory and refreshes the diff automatically when files change; r refreshes by hand. e opens the selected file in $EDITOR.

Moving between panes

KeyAction
Tab / Shift+TabCycle panes: Files → Branches → Reflog → GitLog → Diff
h / lMove between adjacent upper panes (Files, Branches, Reflog)
iJump from upper pane to main pane (GitLog / Diff)
EscReturn from main pane to previous upper pane

And within a pane:

KeyAction
j / kScroll down / up
h / lScroll left / right (in Diff view)
ggJump to top
GJump to bottom
Ctrl+d / Ctrl+uHalf page down / up

Branch list

Selecting a branch previews its git log in the main area. Enter opens the action menu — the only place in vig that writes to the repository, and it offers exactly three things: switch to the branch (git switch), delete it safely (git branch -d; git refuses if it is unmerged — vig never uses -D), or set it as the diff base so the diff pane compares your working directory against it.

KeyAction
j / kNavigate branches (git log preview updates)
EnterAction menu (switch / delete / set as diff base)
/Search branches
EscClear search / Reset comparison to HEAD

Git log

The log preview in the main area is itself navigable: walk the commits, yank a hash, or jump to the commit on GitHub.

KeyAction
j / kNavigate commits
Ctrl+d / Ctrl+uHalf page scroll
g / GTop / Bottom
yCopy commit hash
YCopy commit URL
oOpen in GitHub
/Search commits
EscClear search / Back to Branch List

Reflog

The reflog pane makes “what did I just do?” diffable: select any reflog entry and Enter sets it as the diff base, so the main pane shows what changed since that state.

KeyAction
j / kNavigate entries
Ctrl+d / Ctrl+uHalf page scroll
g / GTop / Bottom
EnterSet as diff base
/Search reflog
EscClear search / Back to Branches

The diff pane: modes

The diff pane starts in Scroll mode, where j / k / h / l scroll. Press i for Normal mode, which gives you a character cursor and vim motions — from there v and V start selections, exactly like in a vim buffer.

KeyAction
iEnter Normal mode
vVisual mode (character)
VVisual-Line mode
EscBack to Scroll mode

Yank (copy)

In Normal / Visual mode, yanks go to the system clipboard:

KeyAction
yyYank line
yw / ye / ybYank word / end of word / word back
y$ / y0Yank to end / start of line
y (in Visual)Yank selection

Text objects are also supported: iw, aw, i", a", i(, a(, i{, a{

KeyAction
/Start search
nNext match
NPrevious match

Search works in all panes (DiffView, FileTree, CommitLog, Reflog) and is case-insensitive.

Constraints

  • The action menu’s git switch and git branch -d are the only write operations in all of vig. There is no staging, committing, merging, rebasing, pushing, or force-deleting — by design, not by omission.
  • The diff always has your working directory on one side; the base (HEAD, a branch, or a reflog entry) is what you choose on the other.

GitHub View

Browse GitHub Issues, Pull Requests and Actions workflow runs directly within vig — three columns across the top, with a detail view for each. Requires the GitHub CLI (gh) to be installed and authenticated; without it the view shows a notice instead of the panes.

Bodies and comments are rendered as Markdown (headings, lists, task lists, code, tables narrowed to fit the pane width where possible). Sub-issues are listed under their parent issue as a tree, and PRs in a GitHub Stack (as created by gh stack) are nested bottom-to-top under the PR they build on.

Issues and Pull Requests

j / k walk the list while the detail follows; i or Enter moves into the detail view, where h / l switch between the body and the right-hand sub-panes (comments, reviews, CI status for a PR). o opens the item in the browser. In an issue or PR detail, w toggles watch mode: vig re-fetches the open item about every 10 seconds so a conversation or CI status you are waiting on stays current.

The issue and PR lists also keep themselves current without spending API points: every github-poll-interval vig sends one conditional request for the repository’s most recently updated issue or PR (a 304 Not Modified answer is free), and only when that answer changes does it re-fetch both lists and the open detail. The status bar shows the lists’ age (lists 12s ago) and a brief ↻ updated after such a refresh. The check follows github-auto-refresh, the idle slow-down and the low-quota throttle like every other poll.

Workflow Runs

The third column lists the latest 50 workflow runs (gh run list) with their status, workflow, run number, branch, event, duration (elapsed while running) and age; while any run is queued or in progress the list refreshes every 5 seconds.

Selecting a run fills the detail area with its jobs and their steps nested underneath (failed steps in red) in the Jobs sub-pane; Enter on a job or step loads that job’s log into the Log sub-pane, with step boundaries and ##[group] markers rendered as section lines. Logs of jobs that are still running are polled every 5 seconds and followed like a tail — ] and [ jump between failed steps, and G jumps to the end and resumes following.

Key bindings

KeyAction
h / lSwitch between the Issues, Pull Requests and Workflow Runs columns
Tab / Shift+TabCycle through the page’s panes — the three columns and the detail (inside a detail view: its sub-panes)
j / kNavigate list (the detail follows the selection)
i / EnterOpen detail view
oOpen in browser (issue, PR, run or the selected job)
yCopy the item URL (built locally, no API request)
EscBack to list
h / l (detail)Body ↔ right-hand sub-panes; for a run: Jobs ↔ Log
w (issue / PR detail)Toggle watch mode (auto-refresh the open item)
i / Enter (run detail, Jobs)Show the job’s log (a step row scrolls to that step)
] / [ (run detail)Next / previous failed step in the log
G (run detail, Log)Jump to the end and resume following
/ n NSearch: #number / title, workflow / branch / event, or in a run detail the job and step names / log lines
Ctrl+d / Ctrl+uHalf page scroll (detail view)
g / GTop / Bottom
rRefresh data (in a detail view: only that item; a run re-fetches its jobs and log)

Constraints

  • Everything goes through the gh CLI, so its authentication and rate limits apply.
  • Nothing in this view reruns, cancels or deletes anything — no commenting, no editing, no merging. It reads.

Files View

A read-only file browser rooted at the repository, laid out like yazi: the left column shows the parent directory, the middle the current one, and the right a preview of the selected entry — syntax-highlighted text, or a listing for directories. .git internals are hidden and symlinks are marked. Entries get Nerd Font icons by file type; if your terminal font is not a Nerd Font, put icons "none" in your config.

Image previews

Images (PNG / JPEG / GIF / WebP) are previewed in the pane, with their format, dimensions, size and the renderer in use on the first line. In terminals with a graphics protocol (Kitty, WezTerm, Ghostty, iTerm2, or Sixel-capable ones such as foot) the image is drawn at full resolution; elsewhere it falls back to unicode half-blocks. image-preview "halfblocks" in the config skips the terminal detection and "none" shows only the metadata. Images over 20 MB are not decoded.

Markdown previews

Markdown files (.md / .markdown, by extension) are rendered in the preview: headings, emphasis, lists, task lists, code and GFM tables, with tables fitted to the pane width and reflowed on resize. A YAML front matter block at the top is kept verbatim in a dim style. m toggles between the rendered form and the raw highlighted text (the pane title shows markdown / raw); the markdown-preview config node picks the default.

Reading long files

Inside the preview, h / l (or / ) scroll long lines horizontally — nothing wraps, and the line number gutter stays put. / searches the file contents (case-insensitive) over what is shown: the rendered text in Markdown mode, the raw lines otherwise; matches are highlighted, n / N step through them and the view scrolls both ways so the current match is visible. Esc clears the search, then returns to the file list. Image previews have nothing to search.

Opening files outside vig

Beyond previewing, the Files view can hand a file to another program — this is the only view with an “open with” concept:

  • e opens the selected file in your external editor ($EDITOR).
  • o opens the selected file or directory with the OS default application (open / xdg-open / explorer).
  • O prompts for an application name and opens the entry with it (open -a <app> on macOS).

Key bindings

KeyAction
j / kMove selection (preview follows)
l / / EnterEnter directory / focus preview
h / / BackspaceParent directory
iFocus preview
j / k / Ctrl+d / Ctrl+u (preview)Scroll
h / l (preview)Scroll long lines horizontally
/ n N (preview)Search the file contents
Esc (preview)Clear the search / back to the file list
/ n NSearch file names
eOpen selected file in external editor
oOpen selected file or directory with the OS default app (open / xdg-open / explorer)
OOpen selected entry with an app you name (open -a <app> on macOS)
mToggle Markdown rendering in the preview
rRe-read the current directory

Constraints

  • The browser is rooted at the repository — it does not wander above the repository root.
  • Strictly read-only: no create, rename, delete, copy, or move. Handing a file to $EDITOR or the OS opener is as far as it goes.

Docker View

A read-only view of the local Docker daemon, built on the docker CLI’s JSON output (docker ps, docker images, docker inspect, docker logs). If docker is not installed or the daemon is not running, the view shows a notice instead of the panes.

Containers are grouped under their compose project (running ones first), the detail pane shows an inspect summary for the selected container or image, and the logs pane tails the selected container (--tail 200, then --since appends every second while following). The lists refresh every 5 seconds.

Key bindings

KeyAction
j / kMove selection (detail and logs follow)
i / EnterFocus the detail pane
l (containers)Focus the logs pane
Tab / Shift+TabCycle panes: Containers → Images → Detail → Logs
j / k / Ctrl+d / Ctrl+u (detail, logs)Scroll (scrolling the logs pauses following)
G (logs)Jump to the end and resume following
/ n NSearch container / image names, or log lines
h / Esc (detail, logs)Back to the list
rRe-fetch containers, images, detail and logs

Constraints

  • Requires the docker CLI and a running daemon; otherwise the view shows a notice (press r after starting the daemon).
  • Environment variables are never displayed — the inspect summary omits them deliberately.
  • Nothing in this view starts, stops, restarts, or removes containers or images. It only runs inspecting docker subcommands.

Procs View

A read-only view of what is running on your machine: the processes as a tree by parent pid with CPU % and resident memory, the listening TCP / UDP ports with the process that owns each one, and a detail of the selected process (pid, ppid, user, state, uptime, CPU / memory, full command line, cwd, executable, children, listening ports). Values that need privileges you do not have are shown as (no access).

Processes come from sysinfo; ports from lsof on macOS and ss on Linux. Both are re-read every 2 seconds while the view is shown (procs-refresh-interval in the config) and on r.

The System graphs

The System pane on top graphs the machine totals as btop-style filled area charts: the global CPU % (with the recent peak) and the used memory over the last procs-history samples (120 by default — 4 minutes at the 2 s interval), plus a Swp line when swap is present. Every sample column is colored by its load — green below 50 %, yellow from 50 %, red from 80 % — and the percentage labels carry the same color.

c swaps the CPU chart for one small gauge per core in the same gradient. The charts fill from the right until the buffer is full, sample only while the view is shown, and always cover the whole machine — they draw numbers only, never per-process data. The detail pane adds the same history for the selected process: a colored CPU % area chart and a resident-memory chart under the CPU / MEM fields.

Key bindings

KeyAction
j / k / Ctrl+d / Ctrl+u / g / GMove in the process tree (detail follows)
sCycle the sort: CPU → MEM → PID (shown in the pane title)
cToggle the CPU graph: history ⇄ one bar per core
Enter / i / lFocus the detail pane
/ n NSearch command lines (processes) or address / port / name (ports)
Tab / Shift+TabCycle panes: Processes → Ports → Detail → System
Enter (ports)Jump to the process that owns the port
j / k / Ctrl+d / Ctrl+u (detail)Scroll
h / Esc (detail)Back to the process list
rRefresh now

Constraints

  • The view only inspects — it never sends a signal. There is no kill, no renice, no stop.
  • Environment variables are never read or displayed.
  • Values that require privileges you lack show as (no access); port ownership may be incomplete without them.
  • Sampling pauses while another view is shown, so the graphs only cover time spent on this view.

Worktrees View

A read-only overview of the repository’s worktrees and stashes. The top-left pane lists the worktrees (git worktree list) with their path — relative to the main worktree where possible — the checked-out branch (or a detached HEAD), and flags such as [main], [locked], [prunable] or [bare]; the worktree vig is running in is marked with *. The bottom-left pane lists the stashes (stash@{n}, message, the branch they were made on and how long ago).

The preview pane

The preview on the right follows the selection:

  • For a worktree it shows the HEAD commit (hash, author, date, subject) and its changed files.
  • For a stash it shows the stash’s patch, including untracked files it carries, in the same side-by-side diff view as the Git view — syntax highlighting, search, and Normal / Visual mode with yank all work there. [ / ] step through the files of a multi-file stash.

Key bindings

KeyAction
Tab / Shift+TabCycle panes: Worktrees → Stashes → Preview
j / kMove selection (preview follows)
i / l / EnterFocus the preview
j / k / Ctrl+d / Ctrl+u (preview)Scroll
h / l (preview)Scroll the diff horizontally
[ / ] (preview)Previous / next file in a multi-file stash
i (preview)Normal mode in the stash diff (v / V / y as in the Git view)
Esc / Backspace (preview)Back to the list
/ n NSearch paths / branches (worktrees), messages / branches (stashes), or the diff
rRe-read worktrees and stashes

Constraints

  • Nothing is ever applied, dropped, added, removed, locked, or pruned from this view — it lists and previews, full stop. Managing worktrees and stashes stays in your shell.

Projects View

A read-only board for the GitHub Projects (v2) linked to the current repository (gh repo view --json projectsV2), with the board itself fetched over GraphQL (a few points per board).

The board takes the full width and the first linked project shows up right away: one column per Status option in GitHub’s order, plus a No status column for items without one. Cards show the item type ( issue, pull request, draft), number, title and assignees; a card whose item lives in another repository carries a dimmed owner/repo prefix before its number.

Several linked projects, and pinning one

With several linked projects the header reads Board: <title> (i/n) and p / P cycle through them; with none the board explains how to link one (the repository’s Projects tab or gh project link). A top-level projects-board config node pins the page to one board, by title or project number — see projects-board in the Config Reference.

Table mode and the detail pane

t switches to a table with one row per item and the project’s fields (Status, Priority, Estimate, Iteration, dates, custom text / number fields) as sortable columns — h / l and s pick the sort column.

The detail pane lists every field value of the selected item, then the issue / PR body and comments as in the GitHub view (drafts show their body).

The optional projects list pane

A projects list pane also exists but is not placed by the built-in layout. Placing it in your config gets a selectable list of the linked projects back — see the recipe for the layout to paste.

Saved views

The project’s saved views (ProjectV2.views, fetched over GraphQL — gh project does not expose them) are read together with the board. The header shows the current view’s name and layout (Board: vig demo board · Sprint [board] (2/3)), and v / V cycle through them. A project without saved views — or a views fetch that fails — falls back to the fixed Status kanban. A Table view renders as the view defines it: its visible fields become the columns (in the view’s order, behind a # column), its sort is the initial sort — descending sorts marked — and its grouping renders one bold header row per group, No <field> last.

A Board view follows the view too: the columns come from its column field (verticalGroupByFields — any single-select or iteration field, in option order plus No <field>; Status when unset), its sort orders the cards inside each column, and its horizontal grouping renders swimlanes — one band per value with its own header line, Space collapses / expands the selected lane and j / k cross between lanes at a column’s edge. A Roadmap view renders a timeline: item rows on the left, a time scale on the right with one bar per item, a yellow today marker and shaded iteration bands. Spans come from the project’s date fields (a name containing start / begin is the span start, target / end / due / finish the end; a single date field is a point) or, for items without dates, from the iteration field’s start and duration. + / - zoom between month, week and day scales, h / l scroll the timeline, and t drops into the table and back. Items without a span are listed without a bar.

A view’s filter (status:Todo -label:bug assignee:@me is:issue no:milestone …) is evaluated locally against the items already fetched — no extra API call — before grouping and sorting, in every layout. Supported: free-text title words, field:value with , lists and quoted values, - negation, is:issue|pr|draft, no: / has:, assignee: (@me is the signed-in login), label:, milestone:, repo:. Ranges (>, ..), wildcards and is:open|closed cannot be evaluated: the status bar says ⚠ filter: unsupported "…" and those tokens are ignored. The status bar also counts what the filter hid ((3 filtered out)).

Key bindings

KeyAction
p / PNext / previous linked project
h / l, / (board)Previous / next column (table mode: sort column)
j / k (board)Move between cards in a column (table mode: rows)
t (board)Toggle table mode
s (board, table mode)Cycle the sort column
Enter / i (board)Focus the detail
v / VNext / previous saved view of the project
SpaceCollapse / expand the selected swimlane
+ / -Zoom the roadmap time scale in / out
oOpen the project / item in the browser
yCopy the project / item URL
j / k / Ctrl+d / Ctrl+u (detail)Scroll
h / Esc (detail)Back to the board
Tab / Shift+TabCycle panes: Board → Detail
/ n NSearch item titles / numbers across columns
rRe-read the linked projects, the board and the shown item

Auto-refresh

While the page is shown, vig asks GitHub every projects-poll-interval (30 seconds by default) whether the board changed. The probe reads only the project’s updatedAt — one GraphQL point; moving a card or editing a field bumps it — and re-fetches the board only when it moved, keeping the selection, the view and the sort. The status bar shows the board’s age (board 12s ago) and a brief ↻ updated after such a refresh. Coming back to the page after five minutes still re-fetches a stale board. Both follow github-auto-refresh, the idle slow-down and the low-quota throttle.

Constraints

  • gh project needs the project token scope. When it is missing the view shows a notice instead of the panes: run gh auth refresh -s project, then press r.
  • Boards are fetched with two GraphQL requests — the fields, saved views and item count, then the items in pages sized to that count — asking only for what the page renders, which costs about a point per hundred item × field pairs (a small board: ~2 points). Past 500 items the status bar says (truncated).
  • The header warns ⚠ api N left when fewer than 1,500 GraphQL points remain of the account’s 5,000/hour, and automatic re-fetches slow down or stop on their own — see Troubleshooting.
  • Nothing in this view adds, moves, edits or deletes anything.

Configuration Basics

vig starts with a complete built-in configuration — you never have to write a config file. When you want to change something (the theme, a key, the tabs, a layout), a single KDL file describes the change and vig merges it on top of the defaults. You only write the parts you want different.

This chapter covers where config files live, how the layers stack, the vig config subcommands, just enough KDL syntax to read and write the file, and the merge rules. The next chapter, Config Recipes, is a cookbook of worked examples — every snippet there is a complete config you can paste and run.

The three layers

vig builds its effective configuration from up to three layers, each merged on top of the previous one:

3. repo-local   .vig.kdl at the worktree root   (personal, per-repository)
2. user         ~/.config/vig/config.kdl        (yours, for every repository)
1. builtin      the embedded defaults           (always present)

Run vig config path at any time to see all three layers, their paths, and whether each one was found and loaded.

1 — builtin

The defaults are compiled into the binary. They are a complete KDL config themselves — the same assets/default.kdl that vig config dump prints — so everything that can be configured is visible in one place, with comments.

2 — user

Your own config file. vig looks for it in this order:

  1. --config <path> command-line flag
  2. $VIG_CONFIG environment variable
  3. $XDG_CONFIG_HOME/vig/config.kdl, or ~/.config/vig/config.kdl if XDG_CONFIG_HOME is unset — on every OS, including macOS (vig deliberately does not use ~/Library/Application Support; ~/.config/vig is what users of zellij / helix / etc. expect everywhere)

A missing file at the default location simply means “use the defaults”, but a path given explicitly via --config or $VIG_CONFIG must exist — vig refuses to start otherwise, so a typo in the path cannot silently give you the wrong config. Note that --config / $VIG_CONFIG replace the user layer; they do not add a fourth one.

3 — repo-local (.vig.kdl)

vig also reads a personal .vig.kdl from the root of the current worktree and merges it on top of your user config, so one repository can get its own theme, pages, or keybindings. It uses the exact same schema as the user config and is meant to be gitignored — it is your file, not the project’s. The per-repository recipes show what typically goes in it.

Because a cloned repository may ship a committed .vig.kdl, trust is decided by git tracking:

  • An untracked .vig.kdl is your own file: it loads silently, and the status bar shows loaded .vig.kdl once at startup.
  • A tracked .vig.kdl is repo-provided: a trust dialog appears before the app is built (the answer decides which pages and keybindings even exist). y loads it and remembers the decision, n ignores it and remembers, v shows the file so you can decide, and Esc ignores it this one time without remembering anything.

Decisions are stored in $XDG_STATE_HOME/vig/trust.json (~/.local/state/vig/trust.json), keyed by the worktree path and a hash of the file content — when the file changes (say, after a pull), the old decision no longer applies and vig asks again. vig config trust lists the remembered decisions; vig config trust --forget <path> drops one.

Two more properties of this layer, both deliberate:

  • It degrades instead of aborting. An error in .vig.kdl never prevents vig from starting: you get builtin + user, plus an ignored .vig.kdl: <reason> note in the status bar (and one line on stderr).
  • It cannot control its own switch. Putting repo-config "off" in your user config disables the layer entirely — no loading, no dialog. Only the user config’s value counts; a .vig.kdl that contains repo-config itself (even "on") is rejected.

The vig config subcommands

CommandWhat it does
vig config pathOne line per layer — builtin / user / repo-local — with its path and status (loaded, not found, ignored (…), pending trust decision).
vig config dumpPrint the built-in default config. This is the complete schema, commented — the best starting point for your own file.
vig config themesList the available syntax highlighting themes; * marks the active one.
vig config trustList the remembered .vig.kdl trust decisions (worktree, decision, date).
vig config trust --forget <path>Forget the decision for one worktree, so the dialog asks again.

All of them respect --config / $VIG_CONFIG, so vig --config ./try.kdl config path tells you what that file would do.

Copy the dump, then trim

The comfortable way to write your first config:

mkdir -p ~/.config/vig
vig config dump > ~/.config/vig/config.kdl

Now open the file, change what you want changed — and then delete everything you did not change. Your file is a partial override: whatever it does not mention keeps its default. Trimming matters for a second reason too: a full copy of the dump freezes every default at today’s values, so when a future vig release improves a default binding or layout, your untouched-but-copied version would silently override it. A trimmed config states exactly your opinions, and nothing else.

A trimmed file often ends up this small:

theme "Solarized (dark)"
icons "none"
pages "git" "github" "worktrees"

vig validates the file at startup, so you can iterate quickly: edit, run vig, read the error if any, repeat. To experiment without touching your real config, point at a scratch file: vig --config ./try.kdl.

Just enough KDL

The config is a KDL document. You need five ideas to read and write it:

  • A node is a name followed by arguments: theme "Solarized (dark)".
  • Arguments are strings in double quotes; a node can take several: pages "git" "files" "worktrees". (One exception takes a bare integer: projects-board 2.)
  • A property is a named value on a node: split direction="horizontal".
  • A node can have children in { … }, nested to any depth.
  • Comments: // to end of line, /* … */ for a span, and the KDL-specific slashdash /- which comments out the entire node that follows it — children and all.

All five in six lines:

theme "Solarized (dark)"        // node with one string argument
/- icons "none"                 // slashdash: this node is ignored
page "git" {                    // children block
    layout {
        split direction="horizontal" {      // property
            place "file_tree" size="30"
            place "diff_view" size="min:20"
        }
    }
}

(That example really loads — it also happens to replace the Git view’s layout with just the file tree and the diff, which the layout recipes explain.)

How merging works

Your file merges into the defaults node by node, and different nodes merge differently. There are three classes:

ClassNodesRule
Replace wholesaletheme, icons, image-preview, procs-refresh-interval, procs-history, github-poll-interval, projects-board, pages, repo-configYour node replaces the default node entirely.
Merge per keyapp { }, page "…" { pane "…" { keys { } } }Each key you mention replaces the default binding for that key; keys you do not mention keep their default. preset lines are appended.
Replace wholesale, per pagepage "…" { layout { } }, tabs, bindIf your page block contains a layout, it replaces that page’s whole layout. Same for tabs, and for the set of bind lines.

The consequences, spelled out:

  • Top-level values are all-or-nothing — which is natural, since each is a single value. Your pages list replaces the default list wholesale: pages you leave out are disabled, not merely moved.
  • Key blocks are additive. Writing page "git" { pane "file_tree" { keys { "o" "ExpandOrOpen" } } } adds one binding; every other file_tree key keeps its default. Binding a key to the special action "None" removes it. This is why a keybinding tweak is two lines, not a restatement of forty defaults.
  • Layouts are not additive. If you write a layout for a page, you are writing the whole layout of that page — there is no way to nudge one pane’s size without restating the tree. Start from the corresponding block in vig config dump and edit. The same applies to tabs (the pane cycle order) and bind (the select→detail wiring): one user bind line replaces all of the page’s default bind lines.

Page and pane names are fixed — you can rearrange, resize, and rebind them, but not invent new ones. The valid pages are git, github, files, docker, procs, worktrees, and projects; run vig config dump to see each page’s panes.

The repo-local .vig.kdl merges with exactly the same rules, one layer later: builtin → user → repo-local, repo-local winning.

Errors are loud

Any problem in the user config — a syntax error, an unknown node, a bad theme name, a layout that places a pane twice or places nothing — stops vig from starting and prints a message naming the file (and line:column for syntax errors). vig never silently falls back to the defaults when a config file is present, so a typo cannot go unnoticed:

theem "Solarized (dark)"
// → invalid config file ~/.config/vig/config.kdl:
//   unknown top-level block "theem" (expected `theme`, `icons`, ...)

The one exception is the repo-local layer, which degrades instead of aborting, as described above.

Config Recipes

A cookbook for the parts of the config people actually change. Each recipe is a problem, a complete config you can paste into ~/.config/vig/config.kdl, and what changes on screen. Every kdl block on this page is loaded by vig’s test suite exactly the way a user config is — a broken example cannot ship.

If you have not read Configuration Basics, the one-line summary: your file is a partial override of the defaults — key blocks merge per key, layouts replace wholesale.

Appearance

Change the syntax highlighting theme

The diff colors don’t fit my terminal.

vig config themes    # list the choices; `*` marks the active one
theme "Solarized (dark)"

The diff view (Git and Worktrees) and the Files preview re-color immediately on next start. Only foreground colors come from the theme, so the light themes (InspiredGitHub, Solarized (light), base16-ocean.light) are readable mainly on a light terminal background.

Turn off file icons

The Files view shows boxes / garbage instead of icons.

Those are Nerd Font glyphs and your terminal font doesn’t have them. Either install a Nerd Font, or:

icons "none"

The Files view shows plain names.

Tame image previews

Image previews look wrong over SSH / in my terminal.

By default ("auto") the Files view probes the terminal for a graphics protocol (Kitty, iTerm2, Sixel) and falls back to unicode halfblocks. Two overrides:

image-preview "halfblocks"   // skip detection, always use halfblocks
image-preview "none"         // no image rendering at all

Keybindings

Rebind or add a key

I want o to open things in the Git file tree, like in my file manager.

page "git" {
    pane "file_tree" {
        keys {
            "o" "ExpandOrOpen"
        }
    }
}

Keys merge per key: this adds one binding (or overrides o if it had one) and leaves every other file_tree key at its default. The help overlay (?) picks it up automatically — it is generated from the active config.

A key is written as a string: a single character ("j", "G", "/"), a named key ("Enter", "Esc", "Tab", "BackTab", "Space", "Backspace", "Delete", "Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown"), or a Ctrl+ combination ("Ctrl+d"). Action names are per pane — vig config dump shows every pane with its defaults, and the Config Reference will list them all.

Global keys live in the app block and work on every page:

app {
    "q" "Quit"            // quit from anywhere, not only from a pane
    "Ctrl+g" "page:git"   // jump to the Git view
}

app actions are "Quit" and "page:<name>" — switching to a page works by name, so the binding keeps working when you reorder the tabs.

Remove a binding

Space toggling directories keeps surprising me.

Bind the key to the reserved action "None":

page "git" {
    pane "file_tree" {
        keys {
            "Space" "None"
        }
    }
}

The key does nothing in that pane anymore and disappears from the help overlay. This works for preset-provided keys too — "n" "None" in a pane removes the search-next key there.

What presets are

In vig config dump you’ll see preset "nav" and preset "search" inside almost every pane’s keys block. A preset is a named bundle of standard bindings, expanded in place:

PresetExpands to
navj/DownNav.MoveDown, k/UpNav.MoveUp, Ctrl+dNav.HalfPageDown, Ctrl+uNav.HalfPageUp, gNav.JumpTop, GNav.JumpBottom
search/Search.Start, nSearch.Next, NSearch.Prev

Two rules govern them:

  • Explicit beats preset. Presets expand first; an explicit binding in the same pane — the default config’s or yours — wins for that key. That is how the recipe above could unbind n even though preset "search" provides it.
  • Presets are appended, never replaced. When your keys merge into a pane, a preset line of yours is added alongside the existing ones. So if a pane somehow lacked search, preset "search" in your config adds the three search keys in one line.

Tabs

Trim or reorder the tabs

I only use the Git, Files and Worktrees views.

pages "git" "files" "worktrees"

The header becomes 1:Git 2:Files 3:Worktrees. The pages list replaces the default list wholesale: the position in the list is the tab’s number, and pages you leave out are disabled entirely — not started, no tab, no background polling.

Reordering works the same way:

pages "github" "git" "files" "docker" "procs" "worktrees" "projects"

Number keys are bindings onto pages, by name — after either config, the built-in page:git binding still reaches the Git view from its new position. Built-in keys of disabled pages are silently dropped; but a binding in your own app block to a page you did not list is an error, because it can never work:

pages "git" "files"
app {
    "d" "page:docker"    // → error: page "docker" is not listed in `pages`
}

Layouts

Reading a layout tree

Every page’s arrangement is a tree of three elements inside layout { }:

  • split direction="horizontal" { … } lays its children side by side; direction="vertical" stacks them. Each child may take a size=.
  • place "<pane>" shows a pane.
  • slot "<name>" … { … } is one area that shows different panes at different times — covered below.

Sizes are "30" (exactly 30 cells), "40%", or "min:20" (at least 20 cells, grab the leftovers). Omitted means min:0. Here is the default Git view layout, annotated:

page "git" {
    layout {
        split direction="vertical" {                    // two rows
            split direction="horizontal" size="40%" {   // top row: 40% tall, three columns
                place "file_tree" size="30"             //   exactly 30 cells wide
                place "branch_list" size="35%"          //   35% of the width
                place "reflog" size="min:20"            //   the rest, at least 20
            }
            slot "main" size="min:3" then="git_log" default="diff_view" {
                triggers "branch_list" "reflog" "git_log"
            }                                           // bottom row: log or diff
        }
    }
}

That block is a valid config — restating a page’s default layout changes nothing, and is exactly how every layout edit starts: copy the page’s layout from vig config dump, then adjust. A layout you write replaces the page’s whole layout; there is no partial layout merge.

Two constraints, both enforced at startup: a layout may place each pane at most once, and it must place at least one pane.

Widen a pane

The Files preview is too narrow.

Copy the Files layout from the dump and shift the numbers:

page "files" {
    layout {
        split direction="horizontal" {
            place "parent_dir" size="15%"   // default: 20%
            place "dir_list" size="25%"     // default: 30%
            place "preview" size="min:20"   // takes what the others freed
        }
    }
}

The preview now gets ~60% of the width instead of ~50%.

Leave a pane out

I never look at the reflog; give its space to the branches.

A pane your layout does not mention becomes inactive: it gets no area, Tab cycling and focus skip it, and bind lines naming it are ignored. You don’t have to touch tabs or keys — they adapt.

page "git" {
    layout {
        split direction="vertical" {
            split direction="horizontal" size="40%" {
                place "file_tree" size="30"
                place "branch_list" size="min:20"     // reflog's space is yours
            }
            slot "main" size="min:3" then="git_log" default="diff_view" {
                triggers "branch_list" "git_log"
            }
        }
    }
}

Bring the Projects list pane back

I want to see all linked project boards as a list, not cycle with p.

The Projects page ships with an intentionally unplaced pane: projects, the list of boards linked to the repository. The default layout shows only the board and the item detail (p / P cycle between boards). Place the list and it comes alive — including its built-in bind select="projects" detail="board", which starts applying on its own:

page "projects" {
    layout {
        split direction="horizontal" {
            place "projects" size="22%"
            split direction="vertical" size="min:30" {
                place "board" size="60%"
                place "detail" size="min:5"
            }
        }
    }
    tabs "projects" "board" "detail"
}

Selecting a project in the list loads its board on the right; Enter moves into it, Esc from the board returns to the list. (This very layout sits as a comment in assets/default.kdl.)

Slots: one area, several panes

What are slot, when and then?

A slot is a layout area that shows different panes depending on where your focus is. The GitHub view’s detail area is the worked example — one bottom area, three possible occupants:

page "github" {
    layout {
        split direction="vertical" {
            split direction="horizontal" size="40%" {
                place "issue_list" size="33%"
                place "pr_list" size="34%"
                place "run_list" size="33%"
            }
            slot "detail" size="min:3" default="issue_detail" {
                when "pr_list" "pr_detail" then="pr_detail"
                when "run_list" "run_detail" then="run_detail"
            }
        }
    }
}

Reading the slot: each when lists trigger panes and names the pane to show (then=). The first when whose trigger list contains the focused pane wins; when none matches, default= shows. So: focus on the PR column (or inside the PR detail itself — that’s why pr_detail is its own trigger) → the area shows pr_detail; focus on the runs column → run_detail; anywhere else, issue_detail. Note that each when names its then pane among its own triggers — otherwise moving focus into the detail would switch the area away from it.

There is also a single-case shorthand — then= on the slot itself plus a triggers child — which the Git view uses: show git_log while branch_list, reflog or git_log has focus, diff_view otherwise (see Reading a layout tree above). Both forms can be combined in one slot; the slot’s name ("detail", "main") is just a label.

A variation to make the slot yours — you live in PRs, so make pr_detail the resting state:

page "github" {
    layout {
        split direction="vertical" {
            split direction="horizontal" size="40%" {
                place "issue_list" size="33%"
                place "pr_list" size="34%"
                place "run_list" size="33%"
            }
            slot "detail" size="min:3" default="pr_detail" {
                when "issue_list" "issue_detail" then="issue_detail"
                when "run_list" "run_detail" then="run_detail"
            }
        }
    }
}

For pane-placement purposes a slot counts each pane it can show as placed once — so no other place may show pr_detail again, and the at-most-once rule applies across the whole tree.

Projects

Pin one board

My repository links five boards; I only ever look at one.

By title (matched case-insensitively against the linked projects):

projects-board "Roadmap"

Or by project number:

projects-board 2

The Projects page shows only that board. p / P stop cycling (they show board pinned by config (projects-board) in the status bar) and the header drops the (i/n) counter. If no linked project matches, the board pane says so, naming your pin. Note the number form is the one place the config takes a bare integer instead of a quoted string.

Per-repository config

A .vig.kdl for one repository

This one repo needs different tabs and a pinned board — but only this repo.

Put a .vig.kdl at the worktree root (and gitignore it — it is personal):

// .vig.kdl — this repository only
pages "git" "github" "projects"
projects-board "Roadmap"
github-poll-interval "10s"

It merges on top of your user config with the same rules (builtin → user → repo-local, repo-local wins). Anything that is only true for one repository belongs here: its pinned board, a trimmed page list, a busier or calmer poll interval, a theme that matches that project’s terminal profile. Preferences that follow you — your keybindings, your icons — belong in the user config.

Errors here never stop vig: a broken .vig.kdl is reported in the status bar (ignored .vig.kdl: …) and vig starts with builtin + user.

The trust dialog

If a .vig.kdl is tracked by git, it came with the repository, and vig asks before loading it — a config decides which pages and keybindings exist, so it is not loaded silently. The dialog appears before the UI starts:

  • y — load it, and remember that answer for this exact file content
  • n — ignore it, and remember
  • v — view the file first, then decide
  • Esc — ignore it this one time; ask again next start

Remembered decisions are keyed by worktree and content hash, so a changed file (after a pull, say) asks again. Manage them from the CLI:

vig config trust                     # list remembered decisions
vig config trust --forget ~/src/foo  # ask again next time in that worktree

Your own untracked .vig.kdl never triggers the dialog — it loads silently with a loaded .vig.kdl note in the status bar.

Turn the repo layer off

I never want a repository influencing my vig.

In your user config:

repo-config "off"

No .vig.kdl is loaded and no dialog ever appears. Only the user config’s value counts — a .vig.kdl cannot contain repo-config at all, so a repository can never flip the switch back.

Polling and history

Calm down (or speed up) GitHub polling

vig polls too often while I watch a running job.

github-poll-interval "10s"

This is how often the GitHub view polls while something is active — the Workflow Runs column with a run in progress, a PR’s checks in watch mode (w), a running job’s log. Default "5s", minimum "2s" (so a config cannot burn through your API quota); polling pauses entirely while another view is shown. Rate-limit handling is built in on top: when GitHub rejects a request, vig backs off exponentially and shows the reset time in the status bar, regardless of this setting.

Procs sampling rate and history depth

I want smoother graphs and a longer history.

procs-refresh-interval "1s"
procs-history "600"

procs-refresh-interval is how often the Procs view re-reads processes and ports while it is shown (default "2s", minimum "250ms", also "1.5s" / "500ms" style values; sampling pauses on other views).

procs-history is how many samples the history graphs keep — the system CPU / memory charts and the per-process sparklines. One sample lands per refresh, so the two settings multiply: the example keeps 600 × 1s = 10 minutes of history. Default "120" (4 minutes at "2s"); allowed range "10" to "10000".

Config Reference

The complete reference for vig’s KDL configuration: every node the config file accepts, with its form, default, merge rule and errors. It is organized for lookup — one section per top-level node, then the page block elements, then every page with its panes and actions.

If you are reading about the config for the first time, start with Configuration Basics (locations, layers, merge model) and Config Recipes (worked examples). This chapter assumes those and aims for completeness.

Conventions used below:

  • Form — what the node looks like. All values are quoted strings unless noted; the one exception is projects-board 2 (a bare integer).
  • Default — the built-in value, as shipped in assets/default.kdl (vig config dump prints it).
  • Merge — what happens when your config states the node.
  • Complete, loadable examples are shown as kdl blocks — vig’s test suite loads each one exactly the way a user config is loaded. Fragments and error demonstrations are marked as ignored and annotated with the error they produce.

Top level at a glance

NodeDefaultMerge rule
theme"base16-eighties.dark"replaced
icons"nerd"replaced
image-preview"auto"replaced
markdown-preview"render"replaced
procs-refresh-interval"2s"replaced
procs-history"120"replaced
github-poll-interval"5s"replaced
github-auto-refresh"on"replaced
projects-poll-interval"30s"replaced
projects-boardabsent (all linked boards)replaced
pagesall seven pagesreplaced wholesale
repo-config"on"replaced (user config only)
appCtrl+c quit, 17 page switchmerged per key
pagesee Pages and panesper element, see below

A config stating every top-level node (each at its default here, so this loads and changes nothing):

theme "base16-eighties.dark"
icons "nerd"
image-preview "auto"
markdown-preview "render"
procs-refresh-interval "2s"
procs-history "120"
github-poll-interval "5s"
github-auto-refresh "on"
projects-poll-interval "30s"
pages "git" "github" "files" "docker" "procs" "worktrees" "projects"
repo-config "on"
app {
    "Ctrl+c" "Quit"
}

Anything else at the top level is an error:

colors "red"
// → unknown top-level block "colors" (expected `theme`, `icons`,
//   `image-preview`, `markdown-preview`, `procs-refresh-interval`,
//   `procs-history`, `github-poll-interval`, `github-auto-refresh`,
//   `projects-poll-interval`, `projects-board`, `pages`, `repo-config`,
//   `app`, or `page`)

Top-level nodes

theme

The syntax highlighting theme used by the diff views (Git and Worktrees) and the Files preview.

  • Formtheme "<name>"
  • Default"base16-eighties.dark"
  • Merge — replaces the default.

Only the themes bundled with syntect are available; run vig config themes to list them (* marks the active one): InspiredGitHub, Solarized (dark), Solarized (light), base16-eighties.dark, base16-mocha.dark, base16-ocean.dark, base16-ocean.light. Only foreground colors are taken from the theme, so the light themes are readable mainly on a light terminal background.

theme "Solarized (dark)"
theme "Solarised (dark)"
// → unknown theme "Solarised (dark)"; available: InspiredGitHub, ...

icons

File-type icons in the Files view.

  • Formicons "<mode>""nerd" or "none"
  • Default"nerd"
  • Merge — replaces the default.

"nerd" shows Nerd Font glyphs by file type and needs a Nerd Font in your terminal; "none" shows plain names (recipe).

icons "none"

image-preview

How the Files view renders image previews (PNG / JPEG / GIF / WebP).

  • Formimage-preview "<mode>""auto", "halfblocks" or "none"
  • Default"auto"
  • Merge — replaces the default.

"auto" probes the terminal for a graphics protocol (Kitty, iTerm2, Sixel) and falls back to unicode halfblocks; "halfblocks" skips the detection and always uses halfblocks; "none" renders no image at all (the preview still shows the image’s metadata line).

image-preview "halfblocks"

markdown-preview

How the Files view previews Markdown files (.md / .markdown).

  • Formmarkdown-preview "<mode>""render" or "raw"
  • Default"render"
  • Merge — replaces the default.

"render" shows the rendered form (headings, emphasis, lists, code, GFM tables fitted to the pane width); "raw" shows the plain syntax-highlighted text. m toggles between the two for the session either way.

markdown-preview "raw"

procs-refresh-interval

How often the Procs view re-reads the process list and the listening ports while it is shown. Sampling pauses on the other views.

  • Formprocs-refresh-interval "<duration>" — a number with s or ms ("2s", "1.5s", "500ms"), quoted; at least "250ms"
  • Default"2s"
  • Merge — replaces the default.
procs-refresh-interval "5s"
procs-refresh-interval "100ms"
// → bad procs-refresh-interval "100ms"; expected a duration such as
//   "2s" or "500ms" (at least 250ms)

procs-history

How many samples the Procs view’s history graphs keep — the system CPU / memory area charts in the graphs pane and the per-process history charts in the detail pane. One sample is taken per refresh interval, so this and procs-refresh-interval multiply into a time span.

  • Formprocs-history "<n>" — a quoted number between "10" and "10000"
  • Default"120" (4 minutes of history at the default "2s")
  • Merge — replaces the default.
procs-history "300"
procs-history "5"
// → bad procs-history "5"; expected a sample count between 10 and 10000

github-poll-interval

How often the GitHub page polls while something is active — the Workflow Runs column while a run is queued or in progress, a PR’s checks in watch mode (w), and the log of a running job — and how often it checks the issue / PR lists for changes (one conditional request whose 304 answer costs no API points; the lists are re-fetched only when it changes). Polling pauses while another page is shown.

  • Formgithub-poll-interval "<duration>" — a number with s or ms, quoted; at least "2s", so a config cannot burn through the API quota
  • Default"5s"
  • Merge — replaces the default.

Rate-limit handling is built in on top of this setting: when GitHub rejects a request as rate-limited, the page suspends all its polling with an exponential backoff (30s, 60s, … capped at 10 minutes) and shows ⚠ GitHub rate limited (resets in Nm) in the status bar. The reset time comes from one gh api rate_limit call (that endpoint is not rate-limited). r retries immediately; a successful fetch clears the backoff. See Troubleshooting for the full story.

github-poll-interval "10s"

projects-poll-interval

How often the Projects page asks GitHub whether the shown board changed while the page is displayed. Each probe reads only the project’s updatedAt (one GraphQL point — item moves and field edits bump it) and the board is re-fetched only when it moved. Probing pauses while another page is shown.

  • Formprojects-poll-interval "<duration>" — a number with s or ms, quoted; at least "5s", since every probe costs a point
  • Default"30s" (120 points an hour with the page open, 20 when idle)
  • Merge — replaces the default.

The probe follows github-auto-refresh and its idle / low-quota scaling; a rate-limited answer pauses it with the same backoff as the GitHub page.

projects-poll-interval "60s"
projects-poll-interval "2s"
// → bad projects-poll-interval "2s"; expected a duration such as "30s" or "60s" (at least 5000ms)

github-auto-refresh

Whether vig refreshes GitHub data on its own: the GitHub page’s polling (runs, watch mode, a running job’s log, the issue / PR list change check) and the Projects page’s stale re-fetch when the page is shown again plus its board change probe.

  • Formgithub-auto-refresh "<mode>""on" or "off"
  • Default"on"
  • Merge — replaces the default.

"off" leaves only the manual r refresh — for shared tokens, CI terminals or a machine running many vig instances. Even when on, automatic refreshes back off by themselves: after 10 minutes without a key press every interval is ×6 (idle in the header), under 20 % of the hourly GraphQL quota ×2, and under 5 % they stop (refresh stopped). The header also shows ⚠ api N left below 1,500 points.

github-auto-refresh "off"

projects-board

Pins the Projects page to one board. The single argument is either a board title (a string, matched case-insensitively against the projects linked to the repository) or a project number — the config’s one bare integer:

  • Formprojects-board "<title>" or projects-board <number>
  • Default — absent: every linked project is available and p / P cycle through them
  • Merge — replaces the default.
projects-board "Roadmap"
projects-board 2

When set, the page shows only that board: p / P no longer cycle (pressing them shows board pinned by config (projects-board) in the status bar) and the header shows the title without the (i/n) counter. When no linked project matches the pin, the board pane shows a notice naming it.

projects-board "Roadmap" 2
// → bad projects-board (one argument required); expected exactly one
//   argument, a board title (`projects-board "Roadmap"`) or a project
//   number (`projects-board 2`)

pages

Which pages are enabled, in tab order. The position in the list is the page’s slot — the number shown in the header (1:Git, 2:GitHub, …) and the position Tab cycling reaches.

  • Formpages "<name>" "<name>" ... — names from git, github, files, docker, procs, worktrees, projects; at least one, no repeats
  • Default — all seven, in that order
  • Merge — replaces the default list wholesale.

Pages you leave out are disabled — not started, no tab, no background polling:

pages "git" "files" "worktrees"

gives a three-tab vig with 1:Git 2:Files 3:Worktrees.

Keys are bindings onto pages, not slots: app { "<key>" "page:<name>" } keeps addressing a page by name wherever it sits, so the built-in 17 still switch to the same pages after reordering. Built-in keys of disabled pages are dropped; a binding in your app { } block to a page that is not listed in pages is an error (see app).

pages "git" "filez"
// → pages: unknown page "filez"; expected one of: git, github, files,
//   docker, procs, worktrees, projects

pages "git" "git"
// → pages: page "git" listed twice

pages
// → pages must list at least one page

The actions page of v0.7.0 was folded into the github page (its Workflow Runs column) in v0.8.0. A config that still lists it is rejected with a message saying so:

pages "git" "actions"
// → pages: page "actions" was folded into the "github" page (v0.8.0);
//   remove it from pages / app bindings

repo-config

Whether the repository-local .vig.kdl layer (Configuration Basics) is read at all.

  • Formrepo-config "on" or repo-config "off"
  • Default"on"
  • Merge — replaces the default; only the user config’s value counts.

With "off" the .vig.kdl file is never loaded and the trust dialog never appears. The switch is read before the repo layer is merged, so a .vig.kdl cannot turn itself on or off — a .vig.kdl that contains repo-config at all (even "on") is rejected: repo-config can only be set in the user config, not in .vig.kdl.

repo-config "off"

app

Global key bindings that work on every page.

  • Formapp { "<key>" "<action>" ... }
  • Default"Ctrl+c" "Quit" and "1""7" bound to the seven pages by name
  • Merge — merged per key: a key you set replaces the default binding for that key; keys you do not mention keep theirs.
ActionMeaning
"Quit"Quit vig
"page:<name>"Switch to that page — page:git, page:github, page:files, page:docker, page:procs, page:worktrees, page:projects. The page must be listed in pages.
"None"Remove the binding for that key
app {
    "q" "Quit"            // quit from anywhere, not only from a pane
    "Ctrl+g" "page:git"   // jump to the Git view
    "7" "None"            // unbind the built-in Projects switch
}

A page: binding of your own naming a page that exists but is not enabled is an error (built-in bindings of disabled pages are silently dropped instead):

pages "git" "files"
app {
    "d" "page:docker"
    // → app block: "d" "page:docker": page "docker" is not listed in
    //   `pages` (git, files)
}

Keys

A key is written as a string, in one of three forms:

  • A single character"j", "G", "/", "]". Case matters: "g" and "G" are different keys.
  • A named key"Enter", "Esc", "Tab", "BackTab", "Space", "Backspace", "Delete", "Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown". A few aliases are accepted: "Return" / "CR" for Enter, "Escape" for Esc, "S-Tab" for BackTab, "BS" for Backspace, "Del" for Delete.
  • A Ctrl+ combination"Ctrl+d", "Ctrl+u", "Ctrl+c". Ctrl is the only supported modifier; there are no Alt+ or function keys.

Binding a key to the reserved action "None" removes it — in app { } and in any pane’s keys { } block alike.

Presets

A preset is a named bundle of standard bindings, expanded in place inside a pane’s keys { } block. Two exist:

PresetExpands to
navj/DownNav.MoveDown, k/UpNav.MoveUp, Ctrl+dNav.HalfPageDown, Ctrl+uNav.HalfPageUp, gNav.JumpTop, GNav.JumpBottom
search/Search.Start, nSearch.Next, NSearch.Prev

The Nav.* and Search.* actions can also be bound individually, in any pane that has the corresponding preset in its defaults. Two rules govern presets (why): presets expand first and explicit bindings in the same pane win for the same key; and when your keys merge into a pane, preset lines are appended, never replaced.

page "git" {
    pane "diff_view" {
        keys {
            "J" "Nav.HalfPageDown"   // bind a preset action explicitly
            "n" "None"               // remove a preset-provided binding
        }
    }
}

The page block

page "<name>" {
    layout { <split | place | slot> }     // replaced wholesale
    tabs "<pane>" "<pane>" ...            // replaced wholesale
    bind select="<pane>" detail="<pane>"  // all bind lines replaced together
    pane "<name>" { keys { ... } }        // keys merged per key
}

Page names and pane names are fixed — you can rearrange, resize and rebind them, but not add new ones. Every block is optional; a page block only changes what it states.

layout

Exactly one root element, of three kinds, nested to any depth:

  • split direction="horizontal" { <children> } — lays its children side by side; direction="vertical" stacks them. Each child may take size="...".
  • place "<pane>" — shows a pane.
  • slot "<name>" ... — one area that shows different panes depending on focus; see Slots below.

Sizes"30" (exactly 30 cells), "40%" (percentage), "min:20" (at least 20 cells, take the leftovers). Omitted means "min:0".

Merge — a layout you write replaces the page’s whole layout; there is no partial layout merge. Start from the page’s block in vig config dump and edit (recipes).

Rules, both checked at startup:

  • Each pane may be placed at most once — counting place lines and, for a slot, each distinct pane the slot can show.
  • At least one pane must be placed.
  • A pane the layout leaves out is inactive: it gets no area, Tab cycling and focus skip it, and bind lines naming it are ignored. The built-in Projects page ships this way — see Page projects.
page "git" {
    layout {
        split direction="vertical" {
            place "diff_view"
            place "diff_view"
        }
    }
}
// → page "git": layout places pane "diff_view" more than once

page "git" { layout { } }
// → page "git" layout is empty

Slots

A slot is a layout area that shows different panes at different times: whichever case matches the currently focused pane wins. Two forms, which can be combined in one slot:

  • Single casethen= on the slot plus a triggers child:

    slot "main" size="min:3" then="git_log" default="diff_view" {
        triggers "branch_list" "reflog" "git_log"
    }
    

    Shows git_log while branch_list, reflog or git_log has focus, and diff_view otherwise (the Git view’s bottom area).

  • Multi casewhen children, each listing trigger panes and naming the pane to show; the first when whose triggers include the focused pane wins, default= shows when none does:

    slot "detail" size="min:3" default="issue_detail" {
        when "pr_list" "pr_detail" then="pr_detail"
        when "run_list" "run_detail" then="run_detail"
    }
    

    The GitHub view’s detail area. Note each when names its then pane among its own triggers, so moving focus into the detail does not switch the area away from it.

The slot’s name ("main", "detail") is just a label. For the at-most-once placement rule, a slot counts each distinct pane it can show as placed once. A worked walk-through is in the slot recipes.

tabs

The panes cycled by Tab / BackTab, in order.

  • Formtabs "<pane>" "<pane>" ...
  • Merge — replaced wholesale when present.

Panes the layout does not place are skipped, so the default tabs stays valid under your layout — you only need to restate tabs to change the cycle order or to include a pane the default order lacks.

bind

Which detail pane a selection pane drives — e.g. selecting a file in file_tree loads it into diff_view.

  • Formbind select="<pane>" detail="<pane>", repeatable
  • Merge — one user bind line replaces all of the page’s default bind lines.

A bind naming an unplaced pane is ignored — and starts applying on its own once a layout places the pane (this is how the Projects page’s list pane comes alive; see Page projects).

pane and keys

page "git" {
    pane "file_tree" {
        keys {
            "o" "ExpandOrOpen"   // add or override a binding
            "Space" "None"       // remove one
        }
    }
}
  • Merge — per key, on top of the pane’s default keys (including expanded presets). preset lines are appended.

Each pane accepts its own set of actions, listed per page below. The pane named view is special: it is not a real pane but the holder of the page-wide keys (quit, help, refresh, tab and pane cycling), and it can never be placed in a layout. The help overlay (?) is generated from the merged keymap, so it always reflects your bindings.

Pages and panes

For each page: its panes, each pane’s bindable actions, and the built-in key for each action. Nav.* and Search.* (see Presets) are additionally available in every pane whose defaults include the corresponding preset — below, panes with nav and search presets are marked. Esc is an action of every interactive pane (leave the pane / clear the search). Run vig config dump to see every default binding in its KDL form.

Page git

Panes: file_tree, branch_list, git_log, reflog, diff_view — all placed by the default layout (git_log and diff_view share the main slot).

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
PrevTab / NextTabh / lmove between the sidebar panes
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the tabs panes
OpenEditoreopen the selected file in $EDITOR
file_tree (nav, search)ToggleDirSpaceexpand / collapse a directory
ExpandOrOpenEnter, Rightexpand a directory / open a file’s diff
FocusDiffifocus the diff view
branch_list (nav, search)OpenActionMenuEnterswitch / safe-delete / set as diff base
FocusLogifocus the git log
git_log (nav, search)YankHashycopy the commit hash
YankUrlYcopy the commit URL
OpenGitHuboopen the commit on GitHub
FocusRefloghfocus the reflog
reflog (nav, search)SetDiffBaseEnterdiff the working tree against this entry
FocusLogifocus the git log
diff_view (nav, search)ScrollLeft / ScrollRighth, Left / l, Righthorizontal scroll
EnterNormalModeivim-style Normal mode (cursor, yank, visual)

Page github

Panes: issue_list, pr_list, run_list (the three columns) and issue_detail, pr_detail, run_detail (sharing the detail slot).

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
PrevTab / NextTabh / lmove between the columns
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle columns and detail
issue_list, pr_list, run_list (nav, search)OpenDetaili, Enteropen the detail view
SwitchTabTab (issues) / BackTab (PRs, runs)column-local tab switch
OpenBrowseroopen the item in the browser
CopyUrlycopy the item URL
issue_detail, pr_detail (nav)FocusBody / FocusRighth / lbody ↔ right-hand sub-panes
CycleForward / CycleBackwardTab / BackTabcycle the sub-panes
ToggleWatchwwatch mode: auto-refresh the open item
OpenItemoopen in the browser
CopyUrlycopy the item URL
run_detail (nav, search)FocusBody / FocusRighth / lJobs ↔ Log sub-panes
CycleForward / CycleBackwardTab / BackTabcycle the sub-panes
OpenLogi, Entershow the selected job’s log
NextFailed / PrevFailed] / [jump between failed steps
OpenItemoopen the run / job in the browser
CopyUrlycopy the run URL

In run_detail, Nav.JumpBottom (G) also resumes following a running job’s log.

Page files

Panes: parent_dir, dir_list, preview — all placed. parent_dir is display-only: it has a pane block with no keys and accepts none.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle dir_list and preview
OpenEditoreopen the selected file in $EDITOR
OpenDefaultoopen with the OS default app
OpenWithOopen with an app you name
dir_list (nav, search)Enterl, Right, Enterenter directory / focus preview
Parenth, Left, Backspacego to the parent directory
FocusPreviewifocus the preview
preview (nav, search)ScrollLeft / ScrollRighth, Left / l, Rightscroll long lines horizontally
Backback to the file list (unbound by default: Esc / Tab get there)

Page docker

Panes: containers, images, detail, logs — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
containers (nav, search)OpenDetaili, Enterfocus the inspect summary
FocusLogslfocus the log tail
images (nav, search)OpenDetaili, Enterfocus the inspect summary
detail (nav)Backhback to the list
logs (nav, search)Backhback to the list

In logs, Nav.JumpBottom (G) also resumes following the tail.

Page procs

Panes: processes, ports, detail, graphs — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
processes (nav, search)FocusDetaili, l, Enterfocus the process detail
CycleSortssort by CPU → MEM → PID
TogglePerCorecCPU graph: history ⇄ per-core bars
ports (nav, search)JumpToProcessEnterjump to the owning process
detail (nav)Backh, Leftback to the process list
graphsTogglePerCorecCPU graph: history ⇄ per-core bars

Page worktrees

Panes: worktrees, stashes, preview — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
worktrees, stashes (nav, search)FocusPreviewi, l, Enterfocus the preview
preview (nav, search)ScrollLeft / ScrollRighth, Left / l, Righthorizontal scroll
EnterNormalModeiNormal mode in the stash diff
NextFile / PrevFile] / [next / previous file in a stash
BackBackspaceback to the list

Page projects

Panes: projects, board, detail. The built-in layout places only board and detail; the projects list pane is defined but not placedp / P cycle the linked projects instead, and a top-level projects-board pins one board and disables them. To get the list back, place it — the built-in bind select="projects" detail="board" then applies on its own (recipe):

page "projects" {
    layout {
        split direction="horizontal" {
            place "projects" size="22%"
            split direction="vertical" size="min:30" {
                place "board" size="60%"
                place "detail" size="min:5"
            }
        }
    }
    tabs "projects" "board" "detail"
}
PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
NextProject / PrevProjectp / Pcycle the linked projects
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
projects (nav, search)OpenBoardi, l, Entershow the selected project’s board
OpenBrowseroopen the project in the browser
CopyUrlycopy the project URL
board (nav, search)PrevColumn / NextColumnh, Left / l, Rightmove between columns (table mode: sort column)
ToggleTabletboard ⇄ table mode
CycleSortscycle the sort column (table mode)
NextView / PrevViewv / Vcycle the project’s saved views
ToggleLaneSpacecollapse / expand the selected swimlane
ZoomIn / ZoomOut+ / -zoom the roadmap time scale
OpenDetaili, Enterfocus the item detail
OpenBrowseroopen the item in the browser
CopyUrlycopy the item URL
detail (nav)Backh, Leftback to the board
OpenBrowseroopen the item in the browser

On the board, Esc returns to the project list when it is placed.

Startup errors

Any problem in the user config stops vig from starting and prints a message naming the file — vig never silently falls back to the defaults when a config file is present. The categories:

  • Syntax errors — reported with file:line:column and the parser’s message, e.g. for a page "git" { left unclosed:

    Error: failed to parse config file /home/you/.config/vig/config.kdl
      /home/you/.config/vig/config.kdl:1:12: No closing '}' for child block
    
  • Unknown names — top-level blocks, pages, panes, themes, icon / image-preview modes, preset names, keys and actions are all validated; each error lists what was expected. Typos cannot go unnoticed.

  • Structural errors — a layout that places a pane twice or places nothing, a slot without then= / when cases, a bind without select= / detail=.

  • Value errors — an interval below its minimum or without a unit, a procs-history out of range, a projects-board with the wrong argument shape, a repo-config other than "on" / "off".

  • Cross-references — your app binding to a page not listed in pages, or to the removed actions page.

The repository-local .vig.kdl layer is the one exception: it degrades instead of aborting (ignored .vig.kdl: <reason> in the status bar). See Troubleshooting for reading the messages in practice.

Troubleshooting / FAQ

The problems people actually hit, with the exact messages vig shows and the way out of each. The fixes that are config changes link into the Config Reference.

vig refuses to start (config error)

vig validates the user config at startup and fails fast: any problem — a syntax error, an unknown node, a bad value — stops vig with a message naming the file, instead of silently falling back to the defaults. A real one:

Error: invalid config file /home/you/.config/vig/config.kdl

Caused by:
    unknown top-level block "theem" (expected `theme`, `icons`, `image-preview`,
    `procs-refresh-interval`, `procs-history`, `github-poll-interval`,
    `projects-board`, `pages`, `repo-config`, `app`, or `page`)

Reading it: the first line names the file; the cause names the thing vig did not accept and lists what it expected — here a typo of theme. Syntax errors additionally carry file:line:column. The Startup errors section lists every category.

Three tools for iterating quickly:

  • vig config path — shows each layer’s status; a broken user config shows as invalid (…) with the same message, without trying to start the TUI.
  • vig --config ./try.kdl — experiment on a scratch file without touching your real config.
  • vig config dump — the always-valid reference to copy correct forms from.

The repository-local .vig.kdl is the one layer that never blocks startup: when it is broken, vig starts with builtin + user and shows ignored .vig.kdl: <reason> in the status bar (plus one line on stderr).

The GitHub view shows an error instead of the panes

The GitHub, Projects (and their polling) go through the GitHub CLI (gh). Two usual causes:

  • gh is not installed — the status bar shows the launch error (e.g. gh not found: No such file or directory). Install the GitHub CLI so it is on your PATH.
  • gh is not authenticated — the gh error is shown as-is. Run gh auth login, then press r in the view to retry.

vig never reads your token itself; authentication is entirely gh’s.

Projects view: gh needs the project scope

The Projects view uses gh project …, which needs the project token scope — a scope gh auth login does not grant by default. When it is missing, the view shows a notice instead of the panes and the status bar says:

gh needs the project scope: run `gh auth refresh -s project`

Run exactly that:

gh auth refresh -s project

then press r in the view. (The GitHub view works without this scope; only Projects needs it.)

⚠ GitHub rate limited

When GitHub rejects a request as rate-limited, the GitHub page stops all its polling and backs off exponentially — 30s, 60s, … capped at 10 minutes — showing ⚠ GitHub rate limited (resets in Nm) in the status bar. The reset time comes from a single gh api rate_limit call (that endpoint is itself not rate-limited). r retries immediately; the first successful fetch clears the backoff.

If you hit this regularly, slow the polling down — vig polls only while something is active (a running workflow, watch mode, a running job’s log), at github-poll-interval (default "5s", minimum "2s"):

github-poll-interval "15s"

Remember the quota is shared with everything else using your token — other tools polling the same account count against the same limit — and every vig instance you have open. vig protects the quota by itself: after 10 minutes without a key press a forgotten instance polls six times less often (idle in the header), under 20 % of the hourly 5,000 GraphQL points every automatic interval doubles, under 5 % automatic refreshes stop (refresh stopped; r still works), and the header shows ⚠ api N left below 1,500 points. Instances on the same repository also share their disk cache, so a board another instance just fetched is read from disk rather than fetched again. To stop automatic refreshes altogether, set github-auto-refresh "off".

The Files view shows boxes / garbage instead of icons

Those are Nerd Font glyphs and your terminal font does not have them. Either install a Nerd Font, or turn icons off (icons):

icons "none"

Image previews look wrong (or: why is my image low-res?)

The Files view previews images at full resolution only in terminals with a graphics protocol — Kitty, WezTerm, Ghostty, iTerm2, or Sixel-capable ones such as foot. Elsewhere (and over most SSH / multiplexer setups) it falls back to unicode halfblocks, which are deliberately coarse. The preview’s first line names the renderer in use, so you can see which path you got.

If the auto-detection misbehaves in your terminal, override it (image-preview):

image-preview "halfblocks"   // skip detection, always use halfblocks
image-preview "none"         // no image rendering, metadata only

Images over 20 MB are not decoded.

The trust dialog keeps asking about .vig.kdl

The dialog appears for a git-tracked .vig.kdl (one that came with the repository), and the remembered answer is keyed by the worktree path and a hash of the file content — so a changed file (e.g. after a pull) asks again by design, and Esc never remembers anything (use y / n for that). Manage the memory from the CLI:

vig config trust                     # list remembered decisions
vig config trust --forget <path>     # ask again next time in that worktree

If you never want the repo-local layer at all, switch it off in your user config (repo-config) — no loading, no dialog:

repo-config "off"

Your own untracked .vig.kdl never triggers the dialog.

Where vig keeps its files (and how to clear them)

vig writes to three places, all safe to delete:

WhatWhereNotes
Config~/.config/vig/config.kdl (or $XDG_CONFIG_HOME/vig/config.kdl)Yours; vig only reads it.
GitHub disk cache<cache>/vig/v1/<owner>/<repo>/ where <cache> is ~/.cache ($XDG_CACHE_HOME) on Linux, ~/Library/Caches on macOSCached issue / PR lists and details, so the GitHub view has content the moment it opens. Deleting it costs one re-fetch.
Trust store$XDG_STATE_HOME/vig/trust.json (~/.local/state/vig/trust.json)The remembered .vig.kdl decisions. Prefer vig config trust --forget for single entries; deleting the file just re-asks everywhere.

vig stores no credentials anywhere — GitHub access goes through gh, which manages its own token.

FAQ

Can vig change my repository?

No, by design. vig performs read operations and exactly two safe git commands — git switch and git branch -d (the safe delete, which refuses unmerged branches) — both only from the branch action menu, after you confirm. No merge, rebase, force delete, push, stash mutation, container operation or process signal, ever.

A key does nothing / does the wrong thing — where do I look?

Press ?: the help overlay is generated from the merged config, so it shows exactly what is bound right now — your rebindings included. If a key is missing there, it was unbound ("None") or your layout made its pane inactive. vig config path tells you which layers are loaded; remember a repo-local .vig.kdl can rebind keys too.

Why is a pane missing from the screen?

A layout in your config (or in .vig.kdl) that leaves a pane out makes it inactive — no area, skipped by Tab. That is a feature (recipe), and the Projects page even ships one intentionally unplaced pane (Page projects). Restate the page’s layout with the pane placed to bring it back.

How do I update vig?

vig update downloads the latest release, verifies its signature and replaces the binary — meant for installs from the pre-built binaries. If you installed via Homebrew or cargo, use brew upgrade vig / cargo install vig instead.

Does vig work without gh / Docker?

Yes. Views degrade independently: without gh the GitHub and Projects views show a notice; without a Docker daemon the Docker view does; the Git, Files, Procs and Worktrees views never need them. Disable unused views entirely with pages.