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 configsubcommands, 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,
ghauthentication 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:
qorCtrl+cquits.?opens a help overlay listing every binding of the current view.rrefreshes 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:
| View | Needs | Without it |
|---|---|---|
| Git, Worktrees | nothing extra | — |
| GitHub | GitHub CLI (gh) installed and authenticated (gh auth login) | the view shows a notice |
| Projects | gh with the project token scope — run gh auth refresh -s project | the view shows a notice explaining the missing scope |
| Docker | docker CLI and a running daemon | the view shows a notice |
| Procs | nothing 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. $EDITOR—eopens 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
| Key | Action |
|---|---|
1 | Switch to Git View |
2 | Switch to GitHub View |
3 | Switch to Files View |
4 | Switch to Docker View |
5 | Switch to Procs View |
6 | Switch to Worktrees View |
7 | Switch 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/kmove,gg/Gjump to top / bottom,Ctrl+d/Ctrl+uscroll half a page.Tab/Shift+Tabcycle the panes of the view.- Selecting an item in a list updates its detail / preview pane immediately;
iorEntermoves focus into it,horEsccomes back. /searches within the focused pane,n/Nstep through matches (case-insensitive).rrefreshes,?shows help,q/Ctrl+cquits.
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
(
Aadded,Ddeleted,Mmodified,Rrenamed,?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
| Key | Action |
|---|---|
Tab / Shift+Tab | Cycle panes: Files → Branches → Reflog → GitLog → Diff |
h / l | Move between adjacent upper panes (Files, Branches, Reflog) |
i | Jump from upper pane to main pane (GitLog / Diff) |
Esc | Return from main pane to previous upper pane |
And within a pane:
| Key | Action |
|---|---|
j / k | Scroll down / up |
h / l | Scroll left / right (in Diff view) |
gg | Jump to top |
G | Jump to bottom |
Ctrl+d / Ctrl+u | Half 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.
| Key | Action |
|---|---|
j / k | Navigate branches (git log preview updates) |
Enter | Action menu (switch / delete / set as diff base) |
/ | Search branches |
Esc | Clear 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.
| Key | Action |
|---|---|
j / k | Navigate commits |
Ctrl+d / Ctrl+u | Half page scroll |
g / G | Top / Bottom |
y | Copy commit hash |
Y | Copy commit URL |
o | Open in GitHub |
/ | Search commits |
Esc | Clear 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.
| Key | Action |
|---|---|
j / k | Navigate entries |
Ctrl+d / Ctrl+u | Half page scroll |
g / G | Top / Bottom |
Enter | Set as diff base |
/ | Search reflog |
Esc | Clear 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.
| Key | Action |
|---|---|
i | Enter Normal mode |
v | Visual mode (character) |
V | Visual-Line mode |
Esc | Back to Scroll mode |
Yank (copy)
In Normal / Visual mode, yanks go to the system clipboard:
| Key | Action |
|---|---|
yy | Yank line |
yw / ye / yb | Yank word / end of word / word back |
y$ / y0 | Yank to end / start of line |
y (in Visual) | Yank selection |
Text objects are also supported: iw, aw, i", a", i(, a(, i{, a{
Search
| Key | Action |
|---|---|
/ | Start search |
n | Next match |
N | Previous match |
Search works in all panes (DiffView, FileTree, CommitLog, Reflog) and is case-insensitive.
Constraints
- The action menu’s
git switchandgit branch -dare 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
| Key | Action |
|---|---|
h / l | Switch between the Issues, Pull Requests and Workflow Runs columns |
Tab / Shift+Tab | Cycle through the page’s panes — the three columns and the detail (inside a detail view: its sub-panes) |
j / k | Navigate list (the detail follows the selection) |
i / Enter | Open detail view |
o | Open in browser (issue, PR, run or the selected job) |
y | Copy the item URL (built locally, no API request) |
Esc | Back 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 N | Search: #number / title, workflow / branch / event, or in a run detail the job and step names / log lines |
Ctrl+d / Ctrl+u | Half page scroll (detail view) |
g / G | Top / Bottom |
r | Refresh data (in a detail view: only that item; a run re-fetches its jobs and log) |
Constraints
- Everything goes through the
ghCLI, 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:
eopens the selected file in your external editor ($EDITOR).oopens the selected file or directory with the OS default application (open/xdg-open/explorer).Oprompts for an application name and opens the entry with it (open -a <app>on macOS).
Key bindings
| Key | Action |
|---|---|
j / k | Move selection (preview follows) |
l / → / Enter | Enter directory / focus preview |
h / ← / Backspace | Parent directory |
i | Focus 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 N | Search file names |
e | Open selected file in external editor |
o | Open selected file or directory with the OS default app (open / xdg-open / explorer) |
O | Open selected entry with an app you name (open -a <app> on macOS) |
m | Toggle Markdown rendering in the preview |
r | Re-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
$EDITORor 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
| Key | Action |
|---|---|
j / k | Move selection (detail and logs follow) |
i / Enter | Focus the detail pane |
l (containers) | Focus the logs pane |
Tab / Shift+Tab | Cycle 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 N | Search container / image names, or log lines |
h / Esc (detail, logs) | Back to the list |
r | Re-fetch containers, images, detail and logs |
Constraints
- Requires the
dockerCLI and a running daemon; otherwise the view shows a notice (pressrafter 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
dockersubcommands.
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
| Key | Action |
|---|---|
j / k / Ctrl+d / Ctrl+u / g / G | Move in the process tree (detail follows) |
s | Cycle the sort: CPU → MEM → PID (shown in the pane title) |
c | Toggle the CPU graph: history ⇄ one bar per core |
Enter / i / l | Focus the detail pane |
/ n N | Search command lines (processes) or address / port / name (ports) |
Tab / Shift+Tab | Cycle 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 |
r | Refresh 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
| Key | Action |
|---|---|
Tab / Shift+Tab | Cycle panes: Worktrees → Stashes → Preview |
j / k | Move selection (preview follows) |
i / l / Enter | Focus 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 N | Search paths / branches (worktrees), messages / branches (stashes), or the diff |
r | Re-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
| Key | Action |
|---|---|
p / P | Next / 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 / V | Next / previous saved view of the project |
Space | Collapse / expand the selected swimlane |
+ / - | Zoom the roadmap time scale in / out |
o | Open the project / item in the browser |
y | Copy the project / item URL |
j / k / Ctrl+d / Ctrl+u (detail) | Scroll |
h / Esc (detail) | Back to the board |
Tab / Shift+Tab | Cycle panes: Board → Detail |
/ n N | Search item titles / numbers across columns |
r | Re-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 projectneeds theprojecttoken scope. When it is missing the view shows a notice instead of the panes: rungh auth refresh -s project, then pressr.- 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 leftwhen 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:
--config <path>command-line flag$VIG_CONFIGenvironment variable$XDG_CONFIG_HOME/vig/config.kdl, or~/.config/vig/config.kdlifXDG_CONFIG_HOMEis unset — on every OS, including macOS (vig deliberately does not use~/Library/Application Support;~/.config/vigis 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.kdlis your own file: it loads silently, and the status bar showsloaded .vig.kdlonce at startup. - A tracked
.vig.kdlis repo-provided: a trust dialog appears before the app is built (the answer decides which pages and keybindings even exist).yloads it and remembers the decision,nignores it and remembers,vshows the file so you can decide, andEscignores 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.kdlnever prevents vig from starting: you get builtin + user, plus anignored .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.kdlthat containsrepo-configitself (even"on") is rejected.
The vig config subcommands
| Command | What it does |
|---|---|
vig config path | One line per layer — builtin / user / repo-local — with its path and status (loaded, not found, ignored (…), pending trust decision). |
vig config dump | Print the built-in default config. This is the complete schema, commented — the best starting point for your own file. |
vig config themes | List the available syntax highlighting themes; * marks the active one. |
vig config trust | List 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:
| Class | Nodes | Rule |
|---|---|---|
| Replace wholesale | theme, icons, image-preview, procs-refresh-interval, procs-history, github-poll-interval, projects-board, pages, repo-config | Your node replaces the default node entirely. |
| Merge per key | app { }, 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 page | page "…" { layout { } }, tabs, bind | If 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
pageslist 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 otherfile_treekey 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
layoutfor 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 invig config dumpand edit. The same applies totabs(the pane cycle order) andbind(the select→detail wiring): one userbindline replaces all of the page’s defaultbindlines.
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:
| Preset | Expands to |
|---|---|
nav | j/Down → Nav.MoveDown, k/Up → Nav.MoveUp, Ctrl+d → Nav.HalfPageDown, Ctrl+u → Nav.HalfPageUp, g → Nav.JumpTop, G → Nav.JumpBottom |
search | / → Search.Start, n → Search.Next, N → Search.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
neven thoughpreset "search"provides it. - Presets are appended, never replaced. When your keys merge into a
pane, a
presetline of yours is added alongside the existing ones. So if a pane somehow lackedsearch,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 asize=.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 contentn— ignore it, and rememberv— view the file first, then decideEsc— 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 dumpprints it). - Merge — what happens when your config states the node.
- Complete, loadable examples are shown as
kdlblocks — 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
| Node | Default | Merge 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-board | absent (all linked boards) | replaced |
pages | all seven pages | replaced wholesale |
repo-config | "on" | replaced (user config only) |
app | Ctrl+c quit, 1…7 page switch | merged per key |
page | see Pages and panes | per 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.
- Form —
theme "<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.
- Form —
icons "<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).
- Form —
image-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).
- Form —
markdown-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.
- Form —
procs-refresh-interval "<duration>"— a number withsorms("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.
- Form —
procs-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.
- Form —
github-poll-interval "<duration>"— a number withsorms, 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.
- Form —
projects-poll-interval "<duration>"— a number withsorms, 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.
- Form —
github-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:
- Form —
projects-board "<title>"orprojects-board <number> - Default — absent: every linked project is available and
p/Pcycle 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.
- Form —
pages "<name>" "<name>" ...— names fromgit,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 1 … 7
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.
- Form —
repo-config "on"orrepo-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.
- Form —
app { "<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.
| Action | Meaning |
|---|---|
"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".Ctrlis the only supported modifier; there are noAlt+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:
| Preset | Expands to |
|---|---|
nav | j/Down → Nav.MoveDown, k/Up → Nav.MoveUp, Ctrl+d → Nav.HalfPageDown, Ctrl+u → Nav.HalfPageUp, g → Nav.JumpTop, G → Nav.JumpBottom |
search | / → Search.Start, n → Search.Next, N → Search.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 takesize="...".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
placelines 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,
Tabcycling and focus skip it, andbindlines naming it are ignored. The built-in Projects page ships this way — see Pageprojects.
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 case —
then=on the slot plus atriggerschild:slot "main" size="min:3" then="git_log" default="diff_view" { triggers "branch_list" "reflog" "git_log" }Shows
git_logwhilebranch_list,reflogorgit_loghas focus, anddiff_viewotherwise (the Git view’s bottom area). -
Multi case —
whenchildren, each listing trigger panes and naming the pane to show; the firstwhenwhose 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
whennames itsthenpane 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.
- Form —
tabs "<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.
- Form —
bind select="<pane>" detail="<pane>", repeatable - Merge — one user
bindline replaces all of the page’s defaultbindlines.
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).
presetlines 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).
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
PrevTab / NextTab | h / l | move between the sidebar panes | |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle the tabs panes | |
OpenEditor | e | open the selected file in $EDITOR | |
file_tree (nav, search) | ToggleDir | Space | expand / collapse a directory |
ExpandOrOpen | Enter, Right | expand a directory / open a file’s diff | |
FocusDiff | i | focus the diff view | |
branch_list (nav, search) | OpenActionMenu | Enter | switch / safe-delete / set as diff base |
FocusLog | i | focus the git log | |
git_log (nav, search) | YankHash | y | copy the commit hash |
YankUrl | Y | copy the commit URL | |
OpenGitHub | o | open the commit on GitHub | |
FocusReflog | h | focus the reflog | |
reflog (nav, search) | SetDiffBase | Enter | diff the working tree against this entry |
FocusLog | i | focus the git log | |
diff_view (nav, search) | ScrollLeft / ScrollRight | h, Left / l, Right | horizontal scroll |
EnterNormalMode | i | vim-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).
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
PrevTab / NextTab | h / l | move between the columns | |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle columns and detail | |
issue_list, pr_list, run_list (nav, search) | OpenDetail | i, Enter | open the detail view |
SwitchTab | Tab (issues) / BackTab (PRs, runs) | column-local tab switch | |
OpenBrowser | o | open the item in the browser | |
CopyUrl | y | copy the item URL | |
issue_detail, pr_detail (nav) | FocusBody / FocusRight | h / l | body ↔ right-hand sub-panes |
CycleForward / CycleBackward | Tab / BackTab | cycle the sub-panes | |
ToggleWatch | w | watch mode: auto-refresh the open item | |
OpenItem | o | open in the browser | |
CopyUrl | y | copy the item URL | |
run_detail (nav, search) | FocusBody / FocusRight | h / l | Jobs ↔ Log sub-panes |
CycleForward / CycleBackward | Tab / BackTab | cycle the sub-panes | |
OpenLog | i, Enter | show the selected job’s log | |
NextFailed / PrevFailed | ] / [ | jump between failed steps | |
OpenItem | o | open the run / job in the browser | |
CopyUrl | y | copy 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.
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle dir_list and preview | |
OpenEditor | e | open the selected file in $EDITOR | |
OpenDefault | o | open with the OS default app | |
OpenWith | O | open with an app you name | |
dir_list (nav, search) | Enter | l, Right, Enter | enter directory / focus preview |
Parent | h, Left, Backspace | go to the parent directory | |
FocusPreview | i | focus the preview | |
preview (nav, search) | ScrollLeft / ScrollRight | h, Left / l, Right | scroll long lines horizontally |
Back | — | back to the file list (unbound by default: Esc / Tab get there) |
Page docker
Panes: containers, images, detail, logs — all placed.
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle the panes | |
containers (nav, search) | OpenDetail | i, Enter | focus the inspect summary |
FocusLogs | l | focus the log tail | |
images (nav, search) | OpenDetail | i, Enter | focus the inspect summary |
detail (nav) | Back | h | back to the list |
logs (nav, search) | Back | h | back to the list |
In logs, Nav.JumpBottom (G) also resumes following the tail.
Page procs
Panes: processes, ports, detail, graphs — all placed.
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle the panes | |
processes (nav, search) | FocusDetail | i, l, Enter | focus the process detail |
CycleSort | s | sort by CPU → MEM → PID | |
TogglePerCore | c | CPU graph: history ⇄ per-core bars | |
ports (nav, search) | JumpToProcess | Enter | jump to the owning process |
detail (nav) | Back | h, Left | back to the process list |
graphs | TogglePerCore | c | CPU graph: history ⇄ per-core bars |
Page worktrees
Panes: worktrees, stashes, preview — all placed.
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle the panes | |
worktrees, stashes (nav, search) | FocusPreview | i, l, Enter | focus the preview |
preview (nav, search) | ScrollLeft / ScrollRight | h, Left / l, Right | horizontal scroll |
EnterNormalMode | i | Normal mode in the stash diff | |
NextFile / PrevFile | ] / [ | next / previous file in a stash | |
Back | Backspace | back 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
placed — p / 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"
}
| Pane | Action | Default key | Meaning |
|---|---|---|---|
view | Quit / Help / Refresh | q / ? / r | page-wide |
NextProject / PrevProject | p / P | cycle the linked projects | |
CyclePaneForward / CyclePaneBackward | Tab / BackTab | cycle the panes | |
projects (nav, search) | OpenBoard | i, l, Enter | show the selected project’s board |
OpenBrowser | o | open the project in the browser | |
CopyUrl | y | copy the project URL | |
board (nav, search) | PrevColumn / NextColumn | h, Left / l, Right | move between columns (table mode: sort column) |
ToggleTable | t | board ⇄ table mode | |
CycleSort | s | cycle the sort column (table mode) | |
NextView / PrevView | v / V | cycle the project’s saved views | |
ToggleLane | Space | collapse / expand the selected swimlane | |
ZoomIn / ZoomOut | + / - | zoom the roadmap time scale | |
OpenDetail | i, Enter | focus the item detail | |
OpenBrowser | o | open the item in the browser | |
CopyUrl | y | copy the item URL | |
detail (nav) | Back | h, Left | back to the board |
OpenBrowser | o | open 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:columnand the parser’s message, e.g. for apage "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=/whencases, abindwithoutselect=/detail=. -
Value errors — an interval below its minimum or without a unit, a
procs-historyout of range, aprojects-boardwith the wrong argument shape, arepo-configother than"on"/"off". -
Cross-references — your
appbinding to a page not listed inpages, or to the removedactionspage.
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 asinvalid (…)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:
ghis 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 yourPATH.ghis not authenticated — thegherror is shown as-is. Rungh auth login, then pressrin 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:
| What | Where | Notes |
|---|---|---|
| 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 macOS | Cached 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.