This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
differ — terminal UI git diff viewer built with Go + Bubble Tea. Two-panel layout: file list + syntax-highlighted diff.
make build # → bin/differ
make install # → $GOPATH/bin/differ
go run . # all changes (staged + unstaged + untracked)
go run . -s # staged only
go run . -r main # compare against ref
go run . -c # open in commit mode
go run . log # commit browser
go run . commit # review staged + commitmake test # go test ./...
golangci-lint run # CI uses v2.10.1, no custom configUnit tests exist across all packages. Test manually in a real git repo with staged, unstaged, and untracked files.
main.go → cmd/root.go (cobra commands)
├── internal/config — Config struct, load/save ~/.config/differ/config.json
├── internal/git — Repo struct, all git ops via os/exec
├── internal/theme — color hex values only (no lipgloss)
└── internal/ui
├── model.go — Model (diff viewer, 4 modes: file list / diff / commit / branch picker)
├── log.go — LogModel (commit log browser)
├── diff.go — diff parser + renderer
├── highlight.go — Chroma syntax highlighting
└── styles.go — all lipgloss styles, bridges theme → lipgloss
Two Bubble Tea models: Model (main diff viewer with file list/diff/commit/branch-picker modes) and LogModel (log browser). Both follow Init()/Update()/View(). All async work (git calls, AI commit messages) returned as tea.Cmd — never block in Update.
Version injected via ldflags at build (-X .../cmd.version), falls back to debug.ReadBuildInfo() for go install.
- Git via shell:
os/exec.Command("git", ...)for all git ops. No go-git. Setcmd.Dirto repo root. - Styles in one place: all lipgloss styles in
styles.go, derived fromtheme.Theme. No inline styles. - Theme decoupled:
internal/theme/defines color values only.styles.gobridges to lipgloss.
- New features and bug fixes: always use TDD (red-green-refactor). Use
/tddskill.- Write failing test first
- Implement minimal code to pass
- Refactor while keeping tests green
- No global mutable state. Pass config/theme through structs.
- Return errors up, don't panic. User-friendly messages in
cmd/. - Keep functions under ~50 lines.
- Use
internal/for all packages — nothing is public API.
Only these external deps (don't add more without strong justification):
github.com/charmbracelet/bubbletea # TUI framework
github.com/charmbracelet/bubbles # viewport, textinput
github.com/charmbracelet/lipgloss # styling
github.com/alecthomas/chroma/v2 # syntax highlighting
github.com/spf13/cobra # CLI
- Fast startup — instant feel. No changes → print one line and exit.
- Readable diffs — syntax highlighting correct. Added/removed with distinct but non-harsh backgrounds.
- Keyboard flow — vim-style (j/k/g/G/d/u). No mouse needed.
- Information density — file status, staged state, line numbers, diff. No decorative waste.
- Add to appropriate
update*Modemethod inmodel.go - Add to
renderHelp()in the same file
- Add method to
Repoininternal/git/repo.go - Test the git command manually first
- Handle errors — git commands fail for many reasons
- Define color values in
internal/theme/theme.go, add toThemesmap styles.gopicks it up automatically
- Chroma + lipgloss: apply Chroma foreground colors token-by-token, keep diff background from line type. Chroma must not override background.
- Terminal width: always respect
tea.WindowSizeMsg. File list panel fixed ~35 chars (fileListWidth), diff gets the rest. - Viewport: call
viewport.SetContent()on content change,viewport.GotoTop()on file switch. - Unicode width: use
lipgloss.Width()notlen(). - Git diff flags: always
--no-ext-diff --color=neverfor predictable output. - Untracked files: no diff available — read file content directly, format as new-file diff via
RenderNewFile(). - AI commit messages: runs configurable
commit_msg_cmd(defaultclaude -p). Diff truncated to 8000 chars. Falls back gracefully if CLI unavailable.