# 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 ```clojure (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 ```clojure {: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 ```clojure ;; 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`: ```clojure {: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)