The application will use the Git CLI as its source of truth instead of reimplementing Git. It will remain a normal VT application in every terminal; kterm's OSC extensions add rounded tabs, buttons, and scrollbars when the terminal advertises support.
Diffs are rendered by kgit rather than delegated to a pager. The built-in renderer combines unified and intraline change presentation with tree-sitter syntax highlighting, and can hide whitespace-only changes while reviewing. The UI adopts the terminal's configured palette by default. Catppuccin Mocha and six dark/light Everforest contrast variants are available as explicit fixed themes matching kvim and Kterm.
Product principles
- Start quickly and remain responsive in large repositories.
- Make common actions keyboard-first without hiding their Git meaning.
- Use safe, non-interactive Git subprocesses with cancellable background reads.
- Make syntax-aware diffs readable without losing exact Git patch semantics.
- Render a complete fallback UI before adding terminal-specific decoration.
- Express all colors through semantic theme roles.
- Keep domain, process, input, rendering, and terminal concerns behind clear boundaries without adding abstractions that only rename or forward calls.
- Give keyboard and mouse workflows equivalent actions where mouse interaction is useful.
- Protect established workflows with focused automated tests as features evolve.
- Keep the repository model, Git execution, application state, and rendering separate.
- Replace kvim's Git explorer rather than becoming coupled to kvim.
Initial experience
The workspace has six top-level routes:
- Changes pairs the changed-path tree with its file diff.
- History pairs the commit graph with commit details and diffs.
- Branches, Tags, Remotes, and Stashes each expose a repository collection with a derived detail view.
Wide terminals show each route as a master/detail split. Narrow terminals show one level at a time: l opens detail and Esc or h returns to the route's master list. Enter opens Changes and History details, while repository collection routes keep their specific activation (for example, switching a branch or fetching a remote). 1 through 6, Tab/Shift-Tab, and gt/gT switch routes; [/] switch repository collection routes. Destructive or history-rewriting operations require an explicit confirmation that displays the Git command and affected scope.
A Nerd Font is expected for file, folder, and semantic status icons.
The detailed design is in docs/architecture.md. The dependency-ordered phases, deliverables, and completion gates are in docs/implementation-plan.md.
Related projects
..\kvimcontains the existing Git explorer whose useful process, parsing, and safety patterns inform this design...\ktermdefines the optional OSC protocols used for enhanced chrome and pane navigation.
kgit must not depend on either project's internal crates. Shared behavior should be captured by kgit-owned interfaces and tests.
Development
The repository uses the stable Rust toolchain with Rustfmt and Clippy. Run the same checks used by CI from the repository root:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
Windows MSVC release builds use a statically linked CRT through .cargo/config.toml; CI verifies that kgit.exe does not import VCRUNTIME140.dll.
Basic startup commands are:
cargo run -- --help
cargo run -- --version
cargo run -- --repo .
For input or layout diagnostics, set KGIT_LOG to an output file before launching. The log records raw mouse coordinates and event kinds, semantic hit target categories, divider capture transitions, rendered pane geometry, and reducer clamping without recording selected paths or ref names:
$env:KGIT_LOG = "$PWD\kgit-diagnostics.log"
cargo run --release -- --repo .
Remove-Item Env:KGIT_LOG
Select-String -Path .\kgit-diagnostics.log `
-Pattern "terminal input|mouse event|preview divider|viewport layout"The general diagnostic stream can include repository paths and Git failure messages, so review the file before sharing it.
kgit --repo <path> is the stable integration contract for terminals and editors. The path may be a repository root, a nested worktree directory, a linked worktree, a bare repository, or an existing directory outside Git. See installation and release packages, migration and rollback, the kvim parity matrix, and Phase 9 stress evidence.
The Changes pane discovers the enclosing worktree from the current directory or --repo, then shows independent index/worktree/conflict badges from porcelain v2 status. Use j/k, gg/G, and Ctrl-u/d to navigate, h/l or Enter to collapse/expand directories, / plus n/N to search, and r to refresh. Press s to stage paths with worktree changes or unstage index-only paths. Directory actions expand to the exact changed paths currently displayed and are passed to Git as bounded literal pathspec batches.
Use v for a keyboard range and Space to toggle the current item. Shift-click extends a range, Ctrl-click toggles an item, and left-drag extends the same selection. These batch selections are available only for changed paths and exact actionable hunks. Scrollbar tracks page; scrollbar thumbs use pointer capture while dragging. Vim motions provide the equivalent keyboard scrolling.
The route detail view renders bounded diffs without an external pager. Use d to open it or switch index/worktree when both exist, i for unified/intraline presentation, o for patch/whole-file scope, w for exact/ignore-all-space review, z to toggle wrapped lines, and [/] for hunks in the visible file. Whole-file scope fills unchanged ranges from the complete new-side file while retaining removed rows at their hunk anchors; deleted files use the old side and opens at the first available hunk. Press s on an exact worktree hunk to stage it or on an exact index hunk to unstage it. kgit verifies baseline, index, and worktree identities, runs git apply --check, and then applies the generated patch. Whitespace-filtered or stale hunks are rejected and trigger an exact refresh.
The preview uses concise file and change labels instead of raw diff --git, index, path-marker, and @@ syntax. Old/new line-number columns distinguish removed and added rows without repeating - and + signs in the content gutter.
Press D on exact tracked worktree hunks to discard only those hunks, on tracked worktree paths to discard the files, or on an entirely untracked path selection to clean it. The modal lists the operation and exact path/hunk scope; y/Enter confirms and n/Esc cancels. Hunk discard verifies the same baseline, index, and worktree identity immediately before a non-mutating patch check and reverse-applies the generated patch to the worktree only. The index is not changed. Whitespace-filtered, stale, non-actionable, untracked, and conflicted hunks are rejected. Empty untracked directories are not listed by Git and are not removed. Unresolved conflicts cannot be discarded as hunks or through the tracked-discard flow; stage a resolved file explicitly. Bare repositories, paths outside a repository, and repository access failures never permit mutation. Mutations are serialized per repository and are not cancellable after queueing, avoiding partial interruption between bounded Git operations.
The current repository snapshot derives staged counts and commit eligibility. Press c to open the internal commit composer in Insert mode on its title field, with a separate wrapped body-description input. The composer provides Normal/Insert editing with i/a/A/I/o/O, h/j/k/l, w/b, 0/$, gg/G, x, D, and dd. Use Tab or Shift-Tab to switch inputs. Submit with Enter in Normal mode or Ctrl-Enter in Insert mode; q/Esc cancels from Normal mode before anything is queued. Empty messages are rejected. kgit passes the message through a securely created temporary file to git commit --file; repository hooks run normally, and bounded hook stderr is shown when a hook rejects the commit.
Press A for the distinct amend flow. kgit first confirms the exact current HEAD and that local history will be rewritten, then opens the composer. Detached HEAD commits are supported. Merge, rebase, cherry-pick, revert, and bisect metadata appears in Context. Only actions reported for the current state are offered: C continues, X aborts with confirmation, and S skips with confirmation. Unmerged rows show available base/ours/theirs stages. Resolve conflicts in an external editor, stage the result with s, refresh, and then use the available continue action; kgit does not embed a merge editor.
The History route loads Arc-backed commit summaries in bounded pages and keeps commit selection stable by full object ID. Full author/committer/body details load only for the selected commit. At most 2,000 summaries are retained; deeper history is reported as capped instead of growing without bound. Decorations, parents, roots, and merge lane transitions are shown deterministically. Vim motions, counts, gg/G, H/M/L, Ctrl-u/d, /, n/N, mouse row selection, the wheel, and the scrollbar all work on history. More commits load automatically near the end without duplicate rows. Press Enter to focus the selected commit diff. Press v, move to a second commit, and review that commit range through the same built-in diff viewport, syntax highlighting, whitespace toggle, search, and theme paths used for worktree diffs.
Branches, Tags, Remotes, and Stashes are direct routes. Use [/] to switch between them and / to search the active inventory. Branch rows show the current branch, upstream, and ahead/behind counts. On Branches, use a to create at the displayed HEAD object ID and x to request confirmed deletion. Creation uses --no-track, never forces, and fails atomically if the name already exists. Deletion first rejects symbolic local refs, branches checked out by any worktree, and tips not merged into current HEAD, then uses one git update-ref --stdin transaction to verify the checked HEAD OID and no-dereference delete the full ref at the displayed branch OID. It does not remove branch.* configuration because post-delete cleanup could race with a newly recreated branch of the same name.
Enter performs an exact named switch only with Git 2.46 or newer. kgit checks that capability, rejects symbolic local refs and branches checked out by another worktree, then switches detached to the displayed object ID. It atomically verifies the branch still has that ID and symbolically attaches HEAD with an update-ref --stdin transaction. If the branch moves between phases, kgit reports a safe partial outcome and leaves HEAD detached at the displayed commit. Older Git is refused without changing the worktree. Branch rename is intentionally unavailable because Git cannot atomically preserve its full rename/config/reflog semantics against the displayed object ID. Tags are browse-only; Enter previews the tagged commit.
Worktree occupancy is checked again as late as practical, including after the detached switch phase. Git cannot include every worktree's HEAD in the shared ref transaction, so a separate Git process can still race this best-effort check. Expected-OID and raw-ref updates remain atomic.
On Stashes, a creates a stash (including untracked files), Enter opens its details and diff, and e applies its full stash commit object ID. Use l or the disclosure glyph to expand a stash into its changed-file tree, and h to collapse it. Navigate the expanded folder/file rows with j/k; selecting a path scopes Preview to that path. p is a separately labeled safe apply-by-OID action that also retains the stash; it is not pop. Drop is unavailable. Each stash row keeps its selector for display and uses a globally unique duplicate-aware reflog fingerprint for selection, while apply never resolves a potentially moved selector.
The Remotes route shows redacted fetch/push URLs and configured-upstream ahead/behind state. Use f to fetch, p to pull only the configured upstream, and P to push. (p retains its stash-apply meaning while the Stashes tab is focused.) These synchronization commands are listed first in Help, and p/P remain visible in the bottom command bar (f is also shown there on Remotes). Ambiguous fetches open an explicit remote chooser.
P always opens a destination chooser for the current named branch. It preselects the configured upstream and lists bounded, locally cached remote-tracking branches plus a same-name creation target for each remote. Cached destinations are derived through each remote's configured fetch refspecs; ambiguous or non-branch mappings are omitted. Use Vim motions and counts, / with n/N, or the mouse to select a destination. Choosing a branch other than the configured upstream is a one-off push and does not rewrite branch configuration. When no upstream exists, choosing the same-name destination sets it after the explicit choice.
Background synchronization is bounded and never prompts for credentials. Synchronization remains disabled until remote discovery succeeds for the current repository generation and branch; refreshes and discovery failures discard the old remote/upstream inventory. Background SSH also receives OpenSSH batch mode so it cannot bypass Git's no-prompt policy through a TTY. Fetch progress is parsed from Git's progress stream, and Esc cancels a running fetch without reporting success. Pull and push are not cancellable after they start; process failures that cannot prove the result are reported as uncertain and trigger a refresh. Push uses porcelain output and explains rejected updates. Immediately before any background or interactive network Git, kgit revalidates the exact named, detached, or unborn HEAD identity. Normal and force-with-lease pushes also pin their source to that request's full commit OID; same-name upstream setup retains Git's branch-based --set-upstream behavior after the same identity check. All stored synchronization diagnostics redact URL userinfo, queries, and fragments.
After a non-fast-forward rejection, F opens a separate confirmation for --force-with-lease=<remote-ref>:<expected-remote-oid> for the exact selected destination; plain --force is never used. A fetched tracking OID is required. If background Git reports that credentials or explicit SSH host verification are required, I first rebuilds the exact request from the current generation, HEAD, and remote inventory, refusing a stale retry. It then explicitly releases the TUI terminal to Git and restores raw mode, alternate screen, cursor, and mouse capture afterward. Interactive Git stderr is continuously drained, bounded, and URL-sanitized before terminal display or storage; direct controlling-terminal prompts remain available. Interactive push porcelain and failure effects retain the same rejection, uncertainty, and refresh semantics as background synchronization.
Configuration and repository trust
kgit loads configuration in this order:
- built-in defaults;
- the platform user file (
kgit/config.toml, orKGIT_CONFIGwhen set); .kgit.tomlat the canonical repository root, only when that exact canonical root is allowlisted by the user file.
Missing and invalid user configuration is shown in the status line and command palette. An invalid or unreadable user file is never overwritten by a trust action. An untrusted repository file is ignored in full and produces a visible source-aware notice. Press t or click Trust to atomically add only the displayed canonical root to [trust].repositories, then reload both layers. Parent paths, glob patterns, manually listed symlink aliases, and local [trust] tables never confer trust. Repository config must be a regular, non-symlink .kgit.toml file.
Preview controls changed with i, o, w, and z are written atomically to the user-only [preview] table and restored on the next launch. An invalid or unreadable user file is never overwritten; kgit keeps the in-session toggle and shows the persistence failure instead.
[appearance]
theme = "terminal" # default; fixed themes are listed below
color-mode = "auto" # true-color, ansi-256, ansi-16, or no-color
# The compatibility spelling `true-color = "auto|always|never"` is also accepted.
[input]
mouse-capture = true
seamless-pane-controls = true
[preview]
layout = "unified" # unified or intraline
scope = "patch" # patch or whole-file
whitespace = "exact" # exact or ignore-all-space
word-wrap = false
[keymap]
"za" = "repository.refresh"
"<C-S-l>" = "pane.resize-right"
[trust]
repositories = ["C:\\exact\\canonical\\repository"]Keymap values are stable action names rather than Rust enum names or shell commands. The supported groups are motion.*, pane.*, context.*, operation.*, selection.*, mutation.*, sync.*, diff.*, repository.refresh, search.*, palette.open, clipboard.copy, and app.*. Diff controls include diff.toggle-layout, diff.toggle-whole-file, diff.toggle-whitespace, and diff.toggle-word-wrap. Sequences may contain bare characters and tokens such as <Esc>, <Space>, <C-h>, <C-S-l>, and <PageDown>. Unknown actions, malformed keys, normalized duplicates, ambiguous prefixes, and sequences longer than eight keys reject the complete source with its path and invalid value shown. Esc is reserved for cancel/step-back and cannot be overridden or used as a sequence prefix. Configured <S-Tab> is normalized to the runtime BackTab representation.
terminal is the default theme. It uses terminal default/reset foreground and background colors plus the terminal's ANSI palette entries 0-15 for semantic accents, diffs, history, syntax, and icons. color-mode still controls color enablement. In Kterm true-color sessions, kgit also accepts the strict, bounded KTERM_PALETTE v1 export (1: followed by FG, BG, CURSOR, ACCENT, SURFACE, and ANSI0-ANSI15 as exactly 21 uppercase six-digit RGB fields). It uses those concrete anchors for subdued theme-aware diff, selection, status, command surfaces, and contrast-safe inactive frames. It chooses foregrounds by computed contrast, supporting both light and dark terminal themes. Malformed or unsupported exports are ignored. ANSI-256, ANSI-16, explicit no-color, and non-Kterm fallback behavior remain unchanged; NO_COLOR always wins.
catppuccin-mocha preserves the previous fixed RGB palette exactly and adapts it deterministically for true-color, ANSI-256, ANSI-16, and no-color output.
Everforest is available through six stable names: everforest-dark-hard, everforest-dark-medium, everforest-dark-soft, everforest-light-hard, everforest-light-medium, and everforest-light-soft. Each uses the corresponding fixed upstream foreground, accent, surface, semantic-tint, status-line, and ANSI palette. True-color uses those values directly; ANSI-256, ANSI-16, and no-color use the same deterministic degradation paths as Catppuccin. KTERM_PALETTE is consumed only by the terminal theme and never changes a fixed theme.
Kterm grid-shape records require concrete RGB values, so shapes remain enabled for fixed themes and for true-color terminal sessions with a valid KTERM_PALETTE; otherwise they are omitted. The complete VT interface remains visible.
Everforest palette data is derived from Everforest by sainnhe at commit 85a86eb62409e3ec88713bff3d1b9d7374e112e4, Copyright (c) 2019 sainnhe, and is used under the MIT License. See the complete local third-party notice; the pinned upstream license is also available.
Command palette, mouse, and kterm
Press ? for the contextual command palette. j/k selects and Enter runs a command; every command is clipped to one clickable row, including narrow screens. Right-click opens the same palette for the pointed pane. Hover updates only the status hint. Click, wheel, Shift-wheel, Shift/Ctrl selection, row drag, scrollbar track click, and captured thumb drag all resolve through the same typed actions as keyboard input. Mouse input waits for any worker-triggered frame so it never resolves against the preceding hit map.
Ctrl-h/j/k/l navigates kgit's visible pane grid. At an edge it asks kterm to continue when KTERM_PANE_NAVIGATION=1. Ctrl-Shift-h/j/k/l moves kgit's nearest bounded divider, then asks kterm to continue when KTERM_PANE_RESIZE=1. Claims occur only on the alternate screen and are balanced across suspend, interactive child handoff, panic, and exit. Before claiming, kgit capability-checks and pushes Kitty keyboard disambiguation (CSI > 1 u), then releases the claim and pops that stack entry in reverse order. On Windows, Crossterm's event backend cannot decode progressive keyboard input, so kgit leaves seamless pane controls unclaimed. Kterm's ConPTY transport continues to provide native key events independently.
In the wide layout, the master pane starts at the smallest width that fits its content, capped at half the workspace. Drag the divider to resize Preview, or use Shift-Left/Right. The Ctrl-Shift-h/j/k/l bindings remain available for seamless kterm pane resizing.
When kterm advertises KTERM_GRID_SHAPES=1, kgit draws its complete VT frame first and then adds bounded rounded tabs, buttons, focused surfaces, and scrollbar thumbs. It also publishes the repository directory, explicit y clipboard copies, and operation progress only to detected kterm sessions. Tiny or zero-sized shape targets are omitted. OSC 7 percent-encodes path bytes (including semicolons), while Windows OSC 9;9 is validated and bounded independently; unsupported metadata is omitted with a nonfatal status diagnostic. Shapes and progress are cleared on lifecycle transitions, and progress is cleared again after each terminal reacquire before its cache is reset. Generic terminals retain the full text controls, borders, scrollbars, status, and command palette.
The initial tree-sitter language inventory and its exact kvim parity source are documented in docs/syntax-language-parity.md.
Set KGIT_LOG to a file path to capture startup diagnostics. Diagnostics are disabled otherwise, so they never write into the terminal frame.