Living document. Updated at the end of every phase and whenever an issue is found or closed. The milestones themselves are defined in SPEC.md section 8; this file tracks where we are, what was decided, and what is open.
| Phase | State | Commit | Notes |
|---|---|---|---|
| 0 terminal plumbing | done | 1b2ceb2 | probe, raw mode, kitty graphics shm + direct, restore on exit and panic |
| 1 rendering | done | 90fe663 | rasterizer, framebuffer, egui demo, headless PNG |
| 2 input | done | 56f7a56 | parser with byte tests, stdin thread, egui mapping, --dump-input |
| 3 read-only git | done | bdad0e2 | git2 0.21.0, snapshot + graph + diff, worker thread, real UI |
| 4 writes | done | de672e1 | stage/unstage files and hunks, commit, amend, checkout, branches, stash, discard, fetch/pull/push via git CLI |
| 5 integration | done | c3694d3 | split.rs, agent.rs, skill/SKILL.md, install script, release workflow |
| post-v0.1 polish | done | 77e92c1 | v0.1.1 to v0.1.4: keyboard shortcuts, agent network ops, auto refresh, footer toolbar, branch picker, GitHub publish |
| review pass | done | 5d00e5f | 2026-09-04 audit: layout fixes for short and narrow panes, socket permissions, bounds checks; see "Review 2026-09-04" |
| feature parity pass | done | 9eb454f | 2026-09-05: line staging, diff search / context / whitespace, commit menu, history rewriting, merge / rebase state and conflicts, branch / remote / tag / stash operations, help; see "Feature audit 2026-09-05" |
| iced port | done | v0.4.0 | 2026-09-06: egui replaced by iced 0.14 on tiny-skia, pane grid, three-way conflict resolver, collapsible sidebar; see "iced branch" |
| editor and file tree | done | b968197 | 2026-09-05: built-in editor with syntax colors (ui/editor.rs, ui/highlight.rs), Shift+E external editor in a split with --editor / gitgui.editor, Shift+O cmux file preview, sidebar file tree (ui/tree.rs, lazy Command::ListDir), --open |
Release build, raster stage only, --headless-frame:
| Frame | Time |
|---|---|
| 1600x1000 scale 1 | 2 ms |
| 1600x1000 scale 2 | 2.7 ms |
| 3344x1870 scale 2 (full cmux pane on the dev Mac) | 10 ms |
On screen in cmux (includes shm copy, encode, dirty check): about 7 ms per frame at scale 2 in a half-width pane.
Git snapshot (status + 2000 commits + refs) on the worker thread:
| Repo | Time |
|---|---|
| gitgui (4 commits) | 6 ms |
| vllm (20k commits, capped at 2000) | 660 ms |
The UI never waits for it; the first frame shows "loading" until the snapshot arrives. Filesystem polling every 2 s only re-snapshots when a watched mtime changed.
-
Unstaging a hunk reverses the HEAD-to-index patch text (swap hunk ranges and +/- prefixes, keep the file headers) and applies it to the index.
-
Network ops run on the git worker thread through the git CLI with
GIT_TERMINAL_PROMPT=0; output streams into a log panel. A slow push blocks other git commands until it finishes, the UI stays responsive. -
commit.gpgsign=trueroutes commits throughgit commitso signing works. -
Manual testing on this machine: never inject mouse events while the user works;
cmux sendtargets a specific surface and is safe, screenshots are only meaningful when the gitgui workspace is in front. -
git2 0.21 returns
Resultfrom most string getters (shorthand,summary,Signature::name,StatusEntry::path); unwrap to empty strings. -
Rename detection: the status entry's
path()is the old path, the delta'snew_file()has the new one. -
The graph keeps the first parent in the commit's lane; merge curves are drawn on the row where lanes join, forks on the merge commit's row.
-
Log virtualization uses
ScrollArea::show_rows; 2000 rows cost nothing off screen. -
Shm probe rides in the capability batch as
a=q,t=s; both anOKreply and the object being unlinked are required (PROTOCOLS 4.2). -
DECRQM for mode 1016 decides between pixel and cell mouse coordinates, instead of guessing from coordinate ranges.
-
Ctrl acts as egui's
commandmodifier, because terminals never deliver Cmd. -
Ctrl+C always quits.
qquits only when no text field has keyboard focus. -
Copy goes out as OSC 52. There is no read path back from the clipboard; paste arrives through bracketed paste.
-
Frame pacing: the loop sleeps on the input channel until the repaint delay egui asked for. Idle CPU is zero unless a widget requests animation.
-
Editor: the galley for the highlighted text is cached on a hash of the buffer, recomputed inside the layouter because
TextEditlays out again after each edit in the same frame.Escapethat opens the unsaved-changes dialog is consumed so the dialog does not see it and close at once. -
Editor resolution for
Shift+E:--editor,git config gitgui.editor,$GITGUI_EDITOR,$VISUAL,$EDITOR,vi. GUI editors run detached, matched on the basename of the first word; everything else gets a split. -
File tree: directories are listed by the worker on demand (
ListDir), never walked eagerly; a snapshot re-lists the root and open folders..gitis skipped, ignored entries stay visible but dimmed.
- UI must be usable at the terminal's own text size: font size now derives
from the cell height (
cell_h / ppp * 0.76), override with--font-size. Loading the terminal's font family itself (Ghosttyfont-family, resolved through CoreText / fontconfig) is a follow-up; egui's bundled fonts are used until then. - A status bar at the bottom listing the available commands: present, and visible now that the scale bug is fixed.
- cmux panes resize and move between displays: SIGWINCH re-reads the grid
from the ioctl, re-queries the cell size in-band (
CSI 16 t), and when the cell height changes the scale and font size follow. Window resizes while a frame is in flight are coalesced by the identical-frame skip.
Full pass over the code after v0.1.4 (three review agents plus headless frames at 800x500, 550x350 and 1672x935 pt). Clippy and 108 tests green before and after.
Layout (all visible in --headless-frame at 1600x1000 scale 2 before the fix):
- Footer text (path, branch, counts) overlapped the Quit and Refresh buttons
in panes under about 900 pt. Cause: the trailing
right_to_leftlayout was allocated after the leading labels and simply overflowed to the left. Newui/row.rshelper lays out the trailing widgets first and clips the leading side to what is left; used by the footer, the diff header and the commit button row. The footer drops the path first, then truncates the counts, so the branch switcher always stays visible. - Commit box vanished below the footer in short detail panes. Cause: nested
allocate_ui_with_layoutgrows past the requested height when the lists overflow, andScrollAreahas a 64 pt minimum. The lists and the commit box now get hard rects (scope_builder+max_rect+ clip) and the scroll areas usemin_scrolled_height(0). During egui's panel sizing pass the detail pane reports a fixed 260 pt so the bottom panel does not balloon to the whole window. - Commit summaries wrapped to two lines and clipped inside a single row.
Now
layout_no_wrapwith the existing clip rect. - Hunk header text ran underneath the
Stage hunkbutton. Text is clipped to the space left of the button; in wrap mode the wrap width reserves the button too. - Empty "nothing staged" list took half of the list column while the
unstaged list showed one row.
list_scroll_heightsnow takes row counts; a list only gets what its rows need, the rest goes to the other list. - Toolbar wider than the footer in narrow panes (Quit wrapped off screen). Below 560 pt the fetch/pull/push/refresh buttons show icons only.
- Sidebar defaulted to 220 pt even in a 550 pt pane. Default is now 25 % of the width, clamped to 140..220.
- Amend checkbox label truncated to a stray dot in narrow columns; the label is dropped below 72 pt (hover text still explains it).
Robustness:
- Agent socket directory and socket file are now chmod 0700 / 0600. Before,
another local user could drive the repository through
$TMPDIR/gitgui. --size WxHrejects 0 and anything above 16384 instead of allocating a zero or multi-gigabyte framebuffer.- Texture atlas partial update with an x offset past the atlas width no longer indexes out of bounds.
- Commit list row drawing uses
getinstead of indexing the commits vec, so a stale selection during a snapshot swap cannot panic. - Filter changes kept
Selection::Commit(i)on a commit the filter hid (or on the working tree row, which a filter hides); the nextj/kjumped to the top.rebuild_filternow moves the selection to the first visible row when the current one disappears.
- Bracketed paste ends at the literal
ESC [ 201 ~; pasted text containing that sequence is cut short. Protocol limitation, no escaping exists. - Unstaging in an empty repository ignores
remove_path/remove_direrrors: exactly one of the two applies per path, the other always fails. - Hunk staging skips binary deltas; the UI never offers hunk buttons for a binary diff, so nothing is silently lost.
- Untracked files above 2 MB are read once to decide
too_large. Cheap enough, only happens when the file is selected. - Screenshot path from the agent socket is not sandboxed. The socket is owner-only now, and the agent runs as the same user anyway.
- Detail pane under about 200 pt: the staged header gets clipped by the commit box. Acceptable, but hiding the staged section when it does not fit would look cleaner.
- Ghostty
font-familyis still not loaded; egui bundled fonts. probe.rsparses the reply buffer twice (once to check DA arrival, once for real). Harmless.- Test gaps: paste containing ESC, kitty super modifier, malformed SGR mouse, escape split at odd byte boundaries, atlas update past bounds.
The feature set of the most used terminal git client was mapped against
gitgui, panel by panel. Everything below was missing and is now in. The
git layer gained git/actions.rs (git2) and git/rebase.rs (rebase todo
rewriting); the worker got SetDiffOpts and the CLI-backed sequencer
commands; the UI got a commit menu (ui/menus.rs), a help table
(ui/help.rs), generic confirm and input dialogs, and a diff viewer with
line selection and search.
Added:
- Working tree: line-level stage / unstage / discard (click, Shift+click, drag), discard hunk, discard all, ignore file, copy path, toggle staged with Space, stash with keep-index and include-untracked options, stash apply, branch from stash.
- Diff: search with highlighted matches and next / previous, context lines
{/}(0 to 20), ignore whitespace. Hunk indices always come from a diff produced with the same options, so hunk staging stays consistent. - Commits: new branch, tag (light or annotated), checkout detached,
cherry-pick, revert, reset soft / mixed / hard, copy hash / message, open
in browser (GitHub, GitLab, Bitbucket style URLs). History rewriting on
the current branch: reword (amend for HEAD, rebase otherwise), squash,
fixup, drop, move up / down, edit, create fixup commit, autosquash. All
through
git rebase -iwith gitgui as the sequence editor; nothing interactive ever opens. - Branches: rename, merge into current (fast-forward when possible, merge commit otherwise), rebase current onto, fast-forward from upstream, set / unset upstream, open pull request, delete on remote. Push sets the upstream automatically when the branch has none and an origin exists. Force push with lease and pull with rebase from the toolbar menus.
- Remotes: add, rename, edit URL, remove, fetch one. Tags: delete, push.
- State: merge, rebase (with progress), cherry-pick and revert show a
footer banner with Continue / Abort,
madds Skip. Conflicted files show the file with its markers (ours as removed, theirs as added) and resolve with Use ours / Use theirs / Mark resolved from the file menu or the diff header. ?help dialog generated from one table.
Left out on purpose (still available in the git CLI next door):
- Range selection in lists, file tree view, custom patches across commits, reflog undo / redo, bisect, worktrees, submodules, git-flow, editing files in an external editor (it would fight over the terminal), amending a non-HEAD commit with staged changes, reset author, moving commits to a new branch, commit filtering by path, screen modes.
Bugs found on the way:
- libgit2 caches the index per repository and does not re-read it before
most operations. When another process (the test helper, the CLI, an
agent) wrote the index, writes used stale entries.
Repo::index()now re-reads it before every write; rule 11 in CLAUDE.md. - libgit2's cherry-pick and revert leave their result in the in-memory
index and their safe checkout neither creates new files nor deletes
removed ones in this version.
finish_pickwrites the index, commits from it and syncs the touched paths from HEAD with a forced checkout limited to those paths. - Stash apply in libgit2 refuses a dirty index, unlike the CLI. The error is shown as is.
Branch iced replaces the egui layer with iced 0.14 driven headless:
shell.rs builds App::view into an iced_runtime::UserInterface, feeds
it the terminal events, and rasterizes with tiny-skia straight into the
framebuffer (Renderer::draw on a PixmapMut, then an R/B swap because
tiny-skia keeps BGRA). Layout is a pane_grid; the commit log and the diff
are custom widgets that draw only the visible rows.
Workarounds that matter when touching the widgets:
- tiny-skia applies a text's
clip_boundsonly when the rect pokes outside the current layer, and nested layers do not intersect.log::draw_textwraps each text in a layer of exactly its clip rect and passes a rect one pixel larger; rows intersect themselves with the widget bounds first. - A geometry group's clip rect gets the layer translation applied twice, so
the graph is drawn with
Frame::with_boundsin absolute coordinates, not throughwith_translation. text_editorconsultskey_bindingeven when unfocused: custom bindings must checkKeyPress::statusor an unfocused editor eats Escape.- Opening a dialog pushes an
unfocusoperation first so editors underneath stop receiving keys.
Frame cost after the direct draw and single build per idle frame: 6.9 ms at 1600x1000 @2x, about 14 ms at 2400x1500 @2x (egui: 6.4 and 8 ms).
Conflicts: ui/merge.rs is a three-way resolver (ours | result | theirs)
opened from the conflict banner, the Conflicts section or the file menu.
It parses the markers itself (diff3 base blocks kept aside), builds the
result from per-conflict choices (ours, theirs, both, neither) and Apply
writes the file and stages it. scripts/conflict-demo.sh recreates a repo
with three conflicted files for manual checks.
Still owed on the branch: editor line-number gutter, diff word wrap, agent screenshot tests, README and SPEC for the pane layout, real-pane checks of drag / resize / menus.
Priority order for the next releases. Sizes: small = an evening, medium = a few sessions, large = a milestone.
- Terminal fonts (medium): load Ghostty
font-familyvia CoreText / fontconfig so UI text matches the terminal face at the computed size. - File history and blame (medium):
hon a file opens its log,bopens blame in the diff pane. Read-only,git2has both. - tmux / Zellij passthrough (medium): detect and enable kitty graphics passthrough instead of hard exit.
- Repo switcher (small): recent repos list,
--repohistory file under XDG config,Ctrl+Rpicker; needs a workerOpenRepocommand. - Multi-select (medium): Shift+click ranges in the file lists, stage / unstage / discard the range; file tree view with collapsing directories.
- Reflog (medium): read-only reflog list in the sidebar, undo / redo of the last ref move.
- Agent API parity (small): expose checkout, branch, tag, cherry-pick, revert, reset and state actions on the socket.
- Per-hunk ours / theirs (medium): resolve one conflict block at a time in the conflict view instead of the whole file.
Done 2026-09-04: README demo (inline GIFs, 960 px, 6 fps). Done 2026-09-05: diff search, conflict UI, commit menu, remote management (see "Feature audit 2026-09-05").
Not planned for v0.x: an interactive rebase editor, bisect, submodules, worktrees, sparse checkout, patch export. Use the git CLI in the neighbouring pane.
-
v0.9.0: AI commit messages:
AI suggestorCtrl+Gpipes the staged patch into the AI CLI on the machine (claude,codex,gemini,ollama,llm, orgit config gitgui.ai-command), the answer fills the commit box as one undo step; prompt template overridable withgitgui.ai-prompt,--probenames the tool (git/ai.rs) -
v0.8.2: the update notice no longer hides a release for a day:
--check-updatewrites what it found to the cache, and a cached answer naming a version older than the running binary is asked again instead of being trusted for the rest of the day -
v0.8.1: the changes pane is fluid: drag the bars between the unstaged list, the staged list and the commit box to resize them (
ui/vsplit.rs), each section keeps a minimum, the heights are fractions of the pane so they survive a resize and are saved per repository -
v0.8.0: gitgui tells you when a newer release is out: a footer chip that opens the release page, a line on the terminal when you quit,
--check-updateon its own; the check isgit ls-remoteon the public repository, cached for a day, off with--no-update-check.scripts/stats.shprints download, star and traffic counts for the repository -
v0.7.8: a paste prefers gitgui's own last copy until the system clipboard changes, so copy then paste inside the editor round-trips even when the terminal ignores OSC 52
-
v0.7.7: Ctrl+V in a terminal pastes the system clipboard (pbpaste / wl-paste / xclip) or the last text gitgui copied; before it only worked through the terminal's own paste
-
v0.7.6: Ctrl+C in a focused field copies in the real terminal too (the runtime used to quit on it before the UI saw the key); three quick Ctrl+C force quit
-
v0.7.5: a toast confirms every clipboard copy (the terminal may still refuse OSC 52)
-
v0.7.4: the editor layout is derived from the main one when a file opens: the left column stays exactly as arranged, the rest becomes the editor; the editor layout is no longer saved
-
v0.7.3: the x on the editor's pane (or the last pane of the editor layout) closes the editor or merge tool and restores the previous layout
-
v0.7.2: opening a file or the merge tool brings back a Diff pane hidden in the saved editor layout (the file had nowhere to show)
-
v0.7.1: double-click on a file row opens the editor again (lost in the iced port), Cmd variants of the custom bindings in the native window
-
v0.7.0: text selection with the mouse in the diff view (drag over the text, Ctrl+C copies, click still selects the line), the commit message body is a selectable read-only editor, undo everywhere, paste and Ctrl+A / C / X / V on macOS, Escape closes dialogs on the first press
-
v0.6.4: undo / redo in every text field (
ui/undo.rs: commit box, dialog fields, reword editor, commit filter, diff search), typing grouped per word, Escape closes a dialog on the first press even with a focused field -
v0.6.3: Ctrl+A / C / X / V in every field on macOS (Ctrl carries the command bit, ModifiersChanged emitted), editor undo / redo with Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z (iced has none), Ctrl+C in a focused editor copies instead of quitting
-
v0.6.2: paste works on macOS again (bracketed paste is delivered as Cmd+V with a ModifiersChanged pair, since iced binds paste to Modifiers::COMMAND and text_input tracks modifiers separately)
-
v0.6.1: agent writes take an
idand a retry returns the first outcome;statushasheadandlast_op;{"cmd":"result"};Repo::commitrefuses an index equal to HEAD -
v0.6.0:
window.rs, the same App in a native window through the iced umbrella crate,--windowor automatic without kitty graphics; open another repository from a folder dialog (Ctrl+O, footerChange folder, no-repository screen),Command::Open; logo on the no-repository screen and as the app icon; zoom keys; SHA-256 repositories through git2'sunstable-sha256 -
v0.5.1: layout and settings saved per repository in
.git/gitgui.json(ui/state.rs) -
v0.5.0: Files as its own pane, hide / show panes from an x and the footer, macOS-style maximize glyphs, title bar hover highlight and OSC 22 pointer shapes, resizable commit columns, scrollbars beside the content, README screenshots from the demo repos
-
v0.4.0: iced port: draggable pane grid, custom log / diff / merge widgets, three-way conflict resolver, collapsible sidebar sections, footer with name and version, direct tiny-skia draw
-
v0.3.3: a file opened from the sidebar tree hides the commit column (editor only, right of the sidebar)
-
v0.3.2: README install / run blocks first, install options moved down, editor header button
open/preview -
v0.3.1: editor layout (commit list above the file column, editor at full height), narrow commit rows drop the author
-
v0.3.0: built-in editor with syntax colors, external editor and cmux preview hand-off, sidebar file tree, panel hide / show,
--open,--editor, compact README -
v0.2.0: feature parity pass, see "Feature audit 2026-09-05"
-
v0.1.6: commit detail shows the full message body, word-wrapped, above the file list (
CommitRow::body); footer ahead/behind uses words because the bundled fonts have no arrow glyphs -
v0.1.5: review pass, see "Review 2026-09-04"
-
v0.1.4: footer toolbar with fetch / pull / push / refresh / quit, branch picker with dirty-tree confirm, publish to GitHub via
gh, init screen for non-git folders, compact commit box, wrap-aware diff row heights -
v0.1.3 and earlier:
-
Install script fix (binary vs tar.gz)
-
Commit and push button and worker command
-
Status bar key letter highlighting
-
Keyboard:
Shift+Sstash,Ctrl+Shift+Entercommit and push,rrefresh (manual) -
Agent API:
fetch,pull,push,commit_and_push -
Auto refresh: git worker polls every 2 s; refreshes when worktree fingerprint or
.git/mtimes change, even when the gitgui pane is unfocused (edits from pi in a neighboring cmux pane show up without pressingr)
1 / 2 / 3, a hide button in each panel header and footer toggles
(digits below 900 pt) hide the sidebar, the commit list and the detail pane.
A hidden log or detail pane gives its space to the other, Tab skips hidden
panes, focus leaves a pane when it is hidden.
With a file open the editor takes the full height as the third column; the
commit list and the file column stack in the second column (Panel::left ("editor_column") in App::ui). changes::show_files draws the file
column alone so both layouts share it. Opened from the sidebar file tree
(App::editor_full) the commit column is hidden as well.
- Keyboard navigation inside the file tree (j / k, Enter, Left / Right).
- Editor: search (
Ctrl+Fcurrently belongs to the diff), go to line, soft wrap toggle, tab width from.editorconfig. - Highlighter: string interpolation, doc comments, more languages on demand.
- File tree clicks, folder expansion and the file menu in a real pane
(2026-09-05: tree rendering and
everified on screen, clicks only in the harness test).
Commit via button, Stage hunk, branch context menus, fetch/pull/push log,
branch picker, publish to GitHub. The 2026-09-04 layout fixes were verified
with headless frames at three sizes; a real cmux pane check of the narrow
(icon-only) footer and the short detail pane is still owed. Harness tests
cover the commit button; layout math is unit-tested in changes.rs.
2026-09-05 additions verified headless (1600x1000, 1100x700, 3344x1870 at
scale 2, and a merge-conflict fixture at 1200x760) and through harness
tests for the keys (?, { }, Ctrl+W, Ctrl+F, s on a line
selection, Shift+D, Shift+T, g, Shift+R, d, n, y). The git
layer has tests for every new operation including a real git rebase and
--abort through the worker. Still owed in a real pane: right-click menus
(egui context menus need a real secondary click), drag selection in the
diff, the rebase editor round trip (gitgui --sequence-editor needs the
installed binary, the test binary cannot play that role), and the footer
banner during a rebase.
- SSH manual check not done: Remote Login is off on the dev Mac. The direct
transport was verified with
--no-shminstead. cmux send-keydoes not deliver keys to the pane.cmux sendtypes text through the terminal's key encoder (ESC arrives asCSI 27 u), so it can exercise the keyboard path but not the mouse path. Mouse checks usescripts/click.swift, which posts real CoreGraphics events.- Terminal cursor shape cannot follow egui's cursor icon. Ignored.
- The demo app's spinner is off by default so an idle session uses no CPU. Turning it on repaints at 60 fps on purpose.
-
Detail column overlapped the commit box in short panes (~225 pt): nested
Panel::bottomdid not clip the lists above it. Fixed with explicit height allocation (allocate_ui_with_layout) and shrinking commit rows. -
A gitgui whose pane was closed kept running at 100% CPU: the stdin thread treated EOF like a timeout and spun, and the main loop could sleep up to an hour before looking at the SIGHUP flag. EOF now ends the thread (the loop exits on the closed channel) and the idle wait is capped at 500 ms.
-
Phase 3/4: on screen the UI was drawn at 4x (2x too big), clicks landed on the wrong widgets and the status bar was off screen. Cause: egui's
pixels_per_point = zoom_factor * native_pixels_per_point; the runtime set the zoom viaset_pixels_per_pointAND passed the native scale. Only the native scale is passed now, with a regression test. -
Phase 1: glyph rendering cost 17 ms of a 20 ms full-pane frame. Fixed with transparent texel skipping and nearest sampling for pixel-snapped glyphs.
-
Phase 1: framebuffer
mark_sentcopied 12 MB per frame. Now swaps buffers.