Files
2026-01-21 17:49:39 -05:00

6.5 KiB

Claude Code Instructions for lazygitclj

Clojure Development

  • Use Clojure skills (nREPL evaluation) when editing code to verify changes compile and work correctly
  • Evaluate code in the REPL to test functions before committing changes
  • Run bb start to launch the application, bb test for unit tests

Local TUI Library

  • The TUI library is at ../clojure-tui/ (local dependency in bb.edn)
  • You have access to edit the local TUI library in conjunction with this repo
  • Use this access to debug issues, support new features, and simplify code
  • Look for opportunities to create better abstraction primitives for TUI layout

Testing with VHS

  • Use VHS to test all changes and verify behavior before finishing any task
  • Run bb test:e2e to run VHS tape tests
  • Run VHS tape files to capture terminal recordings and validate UI behavior
  • Do not consider a task complete until changes have been verified with VHS

Understanding Expected Behavior

  • Reference the lazygit repository and documentation to understand expected behaviors
  • Use VHS locally with lazygit to compare and verify that lazygitclj matches expected interaction patterns
  • When implementing features, check how lazygit handles the same functionality
  • See PRD.md for detailed lazygit behavior documentation

Project Overview

lazygitclj is a lazygit-inspired TUI for Git written in Clojure, targeting Babashka for fast startup. It follows the Elm Architecture pattern (Model-Update-View).

Project Structure

src/lazygitclj/
├── core.clj          # Main TUI app: views, update logic, keybindings (883 lines)
├── git.clj           # Git operations wrapper via shell commands (478 lines)
└── debug_tui.clj     # Layout debugging utility
test/
├── lazygitclj/
│   ├── core_test.clj # Model/update function tests
│   └── git_test.clj  # Git operations tests (uses temp repo fixture)
└── run-tests.clj     # Test runner

Build Commands

Command Purpose
bb start Run lazygitclj TUI
bb debug Debug TUI layout issues
bb test Run unit tests
bb test:e2e Run VHS tape tests

Architecture: Elm Pattern

(tui/run {:init (initial-model)
          :update update-model
          :view view})
  • Model: Application state as a map
  • Update: Pure functions returning [new-model command] tuples
  • View: Pure functions rendering hiccup-style VDom

Model Structure

{:panel :files              ;; Current panel: :files, :branches, :commits, :stash
 :cursor 0                  ;; Cursor position in current panel
 :message nil               ;; Transient message display
 :input-mode nil            ;; :commit, :new-branch, :rename-branch, etc.
 :input-buffer ""           ;; Text input during modal entry
 :menu-mode nil             ;; :stash-options, :reset-options, :help
 :commits-tab :commits      ;; :commits or :reflog
 :branches-tab :local       ;; :local, :remotes, or :tags
 :branch "main"             ;; Current branch
 :sha "abc1234"             ;; HEAD SHA
 :ahead-behind [0 0]        ;; [ahead, behind] upstream
 :staged [...]              ;; Staged files
 :unstaged [...]            ;; Unstaged/untracked files
 :commits [...]             ;; Recent commits
 :branches [...]            ;; Local branches
 :remote-branches [...]     ;; Remote branches
 :tags [...]                ;; Tags
 :stashes [...]             ;; Stash entries
 :reflog [...]              ;; Reflog for undo/redo
 :diff nil}                 ;; Currently displayed diff

Key Functions in core.clj

Function Purpose
initial-model Create initial application state
load-git-data Load all git state via git.clj
refresh Reload git data and update diff
update-model Main dispatcher for keyboard input
update-files Handle files panel (stage, unstage, commit)
update-commits Handle commits panel (checkout, cherry-pick, revert)
update-branches Handle branches panel (checkout, create, delete, merge)
update-stash Handle stash panel (apply, pop, drop)
file-items Combine staged/unstaged files with type metadata
current-items Get items for current panel
view Render UI based on state and dimensions

Keybindings

Global: q quit, ? help, r refresh, h/l panel nav, j/k cursor, z/Z undo/redo, p/P pull/push, D reset menu

Files: space stage/unstage, a stage all, c commit, d discard, s/S stash

Commits: space checkout, g reset to, C cherry-pick, t revert, r reword, [/] tabs

Branches: enter checkout, n new, d delete, R rename, M merge, f fast-forward, [/] tabs

Stash: space apply, g pop, d drop, n branch from stash

Git Operations (git.clj)

All git operations wrap babashka.process/shell and return nil on error.

Categories:

  • Status: current-branch, head-sha, ahead-behind
  • Files: status-files, staged-files, unstaged-files
  • Staging: stage-file, unstage-file, stage-all
  • Diff: diff-file, diff-staged, show-commit, diff-branch
  • Actions: commit, discard-file, pull, push
  • Stash: stash-list, stash-push, stash-pop, stash-apply, stash-drop
  • Reset: reset-soft, reset-mixed, reset-hard
  • Branches: create-branch, checkout-branch, delete-branch, merge-branch
  • Tags: list-tags, create-tag, delete-tag

TUI Library Primitives

;; Containers
[:row {:widths [30 :flex] :gap 1} left right]
[:col {:heights [3 :flex :flex 4]} top mid1 mid2 bottom]
[:box {:border :double :title "Title"} content]

;; Text with styling
[:text {:fg :green :bold true} "content"]
  • :flex keyword items share remaining space equally
  • Borders: :single, :double, :rounded
  • Colors: :red, :green, :yellow, :blue, :magenta, :cyan, :white

Responsive Layout

  • Narrow (< 70 cols): Single-column stacked layout
  • Wide (>= 70 cols): Two-column (left panels 30 cols, right diff flex)

File Status Format

Files parsed from git status --porcelain:

{:index \M :worktree \space :path "file.txt"}

Diff Coloring

  • + lines: green (additions)
  • - lines: red (deletions)
  • @@ lines: cyan (hunk headers)
  • diff/commit lines: yellow (headers)

Conventions

  • Predicates end with ? (e.g., can-fast-forward?)
  • Private functions prefixed with - (e.g., -sh, -parse-status-line)
  • Update functions return [new-model command] tuples
  • Messages are transient (cleared on next refresh)