gitm8 is a terminal UI first. It does not try to replace Git. It runs the
installed git and gh commands and gives them a cleaner screen.
These are the core library docs worth keeping open while reading the code:
- Bubble Tea tutorial: Model, Init, Update, and View
- Bubble Tea API:
tea.Model,tea.Msg,tea.Cmd,tea.NewProgram - Bubble Tea messages/options used here:
tea.KeyMsg,tea.WindowSizeMsg,tea.Tick,tea.Batch,tea.ExecProcess,tea.WithAltScreen - Bubbles components used here:
viewport,textinput,spinner - Lip Gloss styling and layout:
lipgloss.Style,lipgloss.JoinHorizontal,lipgloss.JoinVertical,lipgloss.Place - Go process execution:
os/exec
These names show up everywhere:
Defined in internal/tui/model.go.
Model is the full state of the terminal UI. If something can change on screen,
it probably lives on Model: selected file, current mode, repo info, loaded
files, branch list, spinner state, and the current output text.
Bubble Tea repeatedly passes Model through:
Model.Update(...)
-> returns changed Model
-> Model.View() draws that changed state
Defined in internal/git/git.go.
Runner is the app's doorway to Git. The TUI should not build raw git
commands itself. It should call methods like:
runner.StageOutput(...)
runner.Branches(...)
Then internal/git decides the exact command to run.
Defined in internal/config/config.go.
Config is loaded once at startup and stored on Model. It controls things
like theme, fetch-on-startup, commit graph display, and identity profiles.
Defined in internal/tui/model.go.
These messages are how background work reports back to Update:
repoLoadedMsg: repo info, changed files, and viewer text finished loading.branchesLoadedMsg: branch list finished loading.gitActionFinishedMsg: a Git command finished, with output or an error.
Start here when you want to understand how the program launches:
main.gochecks that the user ran plaingitm8, loads config, optionally fetches on startup, and starts the UI withtea.NewProgramandtea.WithAltScreen.internal/config/config.goreads defaults, dotfiles, environment variables, and identity profiles.internal/tui/model.gocreates the first UI state that satisfies Bubble Tea'stea.Modelinterface.
The startup path is:
main.go
-> config.Load()
-> git.NewRunner("")
-> tui.New(runner, cfg)
-> tea.NewProgram(...).Run()
Terms used below:
Model: the struct that holds the current UI state. See Bubble Tea'sModel.Update: the function Bubble Tea calls when something happens, such as a key press or a finished Git command. See the Bubble Tea Update tutorial.View: the function that turns the current state into terminal text. See the Bubble Tea View tutorial.tea.Cmd: a background job. In this app it usually loads Git data or runs a Git command, then sends the result back toUpdate. See Bubble Tea'sCmd.
The config layer reads user settings and keeps them separate from UI state:
Read this layer when changing:
- environment variables such as
GITM8_THEME ~/.gitm8/.gitm8rc,~/.gitm8/gitm8rc, legacy~/.gitm8rc, or~/.gitm8/credentialsparsing- profile parsing from
~/.gitm8/profiles - default behavior such as fetch-on-startup or commit graph display
The Git layer is the only place that should run git or gh. The UI asks for
things like "stage this file" or "rebase this branch"; this package decides the
exact command to run.
- Core types:
internal/git/git.go - Process execution:
internal/git/exec.go - Basic commands:
internal/git/commands.go - Staging and diffs:
internal/git/changes.go - Branch workflows:
internal/git/branch.go - Repository status/header data:
internal/git/repo.go - File previews:
internal/git/preview.go - Commit logs:
internal/git/log.go - Pull requests via
gh:internal/git/pr.go - Rebase commands:
internal/git/rebase.go - Remote URL helpers:
internal/git/remote.go - Text formatting helpers:
internal/git/text.go - Tests:
internal/git/git_test.go
Use this layer when changing what Git command runs. Do not put raw
exec.Command calls in the TUI. Add a git.Runner method instead.
The Bubble Tea model is plain on purpose. State lives in one struct. The rest of the files are split by job.
- Model and messages:
internal/tui/model.go - Key handling and mode changes:
internal/tui/update.gouses Bubble TeaMsg,KeyMsg, andWindowSizeMsg. - Git actions that run in the background:
internal/tui/actions.goreturns Bubble TeaCmdvalues and usestea.Batchfor spinner plus work. - Data loading that runs in the background:
internal/tui/load.goreturns custom messages back intoUpdate; this follows Bubble Tea'sCmdpattern. - Cursor and list offset logic:
internal/tui/cursor.go
The core UI loop is:
Model.Init()
-> starts the first background job
-> Model.Update(message from that job or from a key press)
-> maybe starts another background job
-> Model.View() draws the screen
Most user actions follow this path:
key press
-> updateKey
-> updateDashboardKey or updateFocusedMode
-> action(...) or load...
-> git.Runner method
-> result message: gitActionFinishedMsg, repoLoadedMsg, or branchesLoadedMsg
-> handleGitActionFinished / handleRepoLoaded / handleBranchesLoaded
-> View()
Rendering is separate from key handling:
- Full screen layout, picker views, help, and splash screen:
internal/tui/view.go, built around Bubble Tea'sViewmethod and Lip Gloss layout helpers likeJoinHorizontal,JoinVertical, andPlace. - Themes and styles:
internal/tui/theme.go, built aroundlipgloss.Style. - Preview syntax highlighting:
internal/tui/syntax.go - Text/layout helpers:
internal/tui/text.go - Tests:
internal/tui/model_test.go
The Bubbles components are stored in Model and rendered from View:
viewport.Modelpowers the scrollable review/preview/log/help panel.textinput.Modelpowers commit-message and branch-name inputs.spinner.Modelpowers the small "git working..." indicator.
Read these files when changing how something looks. Read internal/git when
changing what Git command runs.
- README:
README.md - Abstractions guide:
docs/ABSTRACTIONS.md - Flow examples:
docs/FLOWS.md - Man page:
docs/man/gitm8.1 - Installer:
scripts/install.sh - Example yazi config:
configs/yazi/yazi.toml - Example yazi theme:
configs/yazi/theme.toml
When user-visible behavior changes, update the README and man page together.
When the names feel unclear, read docs/ABSTRACTIONS.md.
When the control flow feels unclear, read docs/FLOWS.md. It walks
through real keys like s, b, c, and r.
- Add the key handling in
internal/tui/update.go. Normal dashboard keys and one-screen-only keys both live there. - Put Git operations in
internal/git, not directly in the TUI. - Update visible key references in
internal/tui/view.go. - Update
README.mdanddocs/man/gitm8.1.
- Add a method on
git.Runnerin the relevantinternal/gitfile. - Return output when the UI should show command details.
- Call the runner method from
internal/tui/actions.goor the relevant mode handler. - Add a focused test if the change parses text, formats text, or recovers from a failed Git command.
- Update
internal/tui/view.gofor the main screen layout. - Update
internal/tui/view.gofor picker content. - Update
internal/tui/theme.gowhen changing colors or style variables. - Run the TUI in a real terminal after tests, because layout issues are easiest to see interactively.
Use writable Go caches in this environment:
GOCACHE=/tmp/gitm8-go-build GOMODCACHE=/tmp/gitm8-go-mod go test ./...
GOCACHE=/tmp/gitm8-go-build GOMODCACHE=/tmp/gitm8-go-mod go build ./...