terminal user interface to jujutsu. Focused on speed and clarity
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 267 lines 6.7 kB view raw view rendered
1# AGENTS.md - Coding Agent Guidelines for jj_tui 2 3> A terminal UI for the Jujutsu version control system, built in OCaml with Notty/Nottui. 4 5## Project Structure 6 7``` 8jj_tui/ 9├── bin/ # Main executable and UI components 10│ ├── main.ml # Entry point 11│ ├── jj_ui.ml # Main UI orchestration 12│ ├── graph_view.ml, graph_commands.ml # Commit graph UI 13│ ├── file_view.ml, file_commands.ml # File diff UI 14│ └── global_vars.ml # Shared state 15├── lib/ # Core library (jj_tui) 16│ ├── ansiReverse.ml # ANSI escape parsing 17│ ├── render_jj_graph.ml # Commit graph rendering 18│ ├── config.ml, key_map.ml # Configuration 19│ └── *_tests.ml # Inline tests 20├── test/lib/ # Additional test library (jj_tui_test) 21└── forks/ # Vendored dependencies (notty, nottui, lwd) 22``` 23 24## Build Commands 25 26**Requires Nix** - This project uses Nix for dependency management. 27 28```bash 29# Enter development shell (required first) 30nix develop 31 32# Build 33dune build 34 35# Build and watch 36dune build --watch 37 38# Run the application 39dune exec jj_tui 40 41# Run all tests 42dune runtest 43 44# Run tests for specific library 45dune runtest -p jj_tui 46 47# Run tests and show output 48dune runtest --force 49 50# Format code 51dune fmt 52# or 53ocamlformat -i <file.ml> 54 55# Check formatting without applying 56dune fmt --preview 57``` 58 59## GIT: 60This project uses jujutsu not git. for any git command run the jj equivalent 61 62### Running Individual Tests 63 64Tests use `ppx_expect` inline tests. To run tests in a specific file: 65 66```bash 67# Run inline tests for jj_tui library 68dune runtest jj_tui/lib 69 70# Run inline tests for test library 71dune runtest jj_tui/test/lib 72 73# Promote expect test changes (update golden output) 74dune promote 75``` 76## Key things to remember: 77- Run `dune build` if there are type errors, often these can be caused by changes across files not being picked up which requires a rebuild 78- If you are getting a type error and can't fix it: Records with the same fields names can cause weird type inference issues, once it's infered it won't change. 79 You need to either: 80 - locally open the module with the type you want, 81 - or do Module.( myrecord.field) 82 - or explicitly annotate the type 83 84## Code Style Guidelines 85 86### OCamlformat Configuration 87 88Uses `profile = janestreet` with customizations. Key settings: 89- `let-binding-spacing = double-semicolon` - End let bindings with `;;` 90- `break-cases = nested` - Multi-line match statements 91- `if-then-else = keyword-first` - Align `then`/`else` 92- `space-around-records/lists/arrays = true` - Spacing for trailing commas 93 94### Import/Open Patterns 95 96```ocaml 97(* Module-level opens at top of file *) 98open Lwd_infix 99open Notty 100open Nottui 101open Jj_tui 102open! Jj_tui.Util (* open! for shadowing *) 103 104(* Functor-based module creation *) 105module Make (Vars : Global_vars.Vars) = struct 106 open Vars 107 module Process = Jj_process.Make (Vars) 108 open Process 109 (* ... *) 110end 111``` 112 113### Naming Conventions 114 115- **Functions/values**: `snake_case` - `get_hovered_rev`, `parse_escape_seq` 116- **Types**: `snake_case` - `ui_state_t`, `rev_id` 117- **Modules**: `PascalCase` - `Internal`, `Parser`, `Key_Map` 118- **Type parameters**: `'a`, `'acc`, `'b` 119- **Record fields**: `snake_case` with semicolons 120- **Variant constructors**: `PascalCase` - `Unique`, `Duplicate`, `Apply` 121 122### Type Definitions 123 124```ocaml 125(* Record types - use semicolons before fields *) 126type t = { 127 key_map : Key_map.key_config [@updater] 128 ; single_pane_width_threshold : int 129 ; max_commits : int 130} 131[@@deriving yaml, record_updater ~derive:yaml] 132 133(* Variant types *) 134type 'a maybe_unique = 135 | Unique of 'a 136 | Duplicate of 'a 137``` 138 139### Error Handling 140 141```ocaml 142(* Prefer Result for parsing/fallible operations *) 143let of_string remap = 144 match remap with 145 | "up" -> Ok (`Arrow `Up) 146 | _ -> Error (`Msg ("Invalid remap: " ^ remap)) 147 148(* Use Option for optional values *) 149Sys.getenv_opt "XDG_CONFIG_HOME" |> Option.value ~default:"~/.config" 150 151(* Exception handling for I/O *) 152try 153 let ic = open_in config_file in 154 (* ... *) 155with 156| Sys_error _ -> default_config 157| ex -> [%log warn "Error: %s" (Printexc.to_string ex)]; default_config 158``` 159 160### Lwd Operators (Reactive UI) 161 162```ocaml 163let ( <-$ ) f v = Lwd.map ~f (Lwd.get v) 164let ( $-> ) v f = Lwd.map ~f (Lwd.get v) 165let ( let$$ ) v f = Lwd.map ~f (Lwd.get v) 166let ( |>$ ) v f = Lwd.map ~f v 167let ( >> ) f g x = g (f x) (* Compose left-to-right *) 168let ( << ) f g x = f (g x) (* Compose right-to-left *) 169 170(* Usage *) 171let$ root = root in 172root |> Nottui.Ui.event_filter (...) 173``` 174 175### Logging 176 177Uses `logs-ppx` with custom timestamp wrapper: 178 179```ocaml 180open Jj_tui.Logging 181 182[%log info "Loading config..."] 183[%log warn "Error parsing config: %s" msg] 184[%log debug "Old logs cleaned up"] 185``` 186 187### Testing (ppx_expect) 188 189```ocaml 190let%expect_test "test_name" = 191 let result = some_function () in 192 print_endline result; 193 [%expect {| 194 expected output here 195 |}] 196;; 197``` 198 199### Documentation Comments 200 201```ocaml 202(** Module-level documentation *) 203 204(** Function documentation - concise, one line preferred *) 205let get_unique_id maybe_unique_rev = ... 206 207(** 208 Multi-line documentation for complex functions. 209 Explains algorithm or non-obvious behavior. 210*) 211``` 212 213## Key Patterns 214 215### Functor-Based Dependency Injection 216 217```ocaml 218module Make (Vars : Global_vars.Vars) = struct 219 (* Access Vars.* throughout the module *) 220end 221``` 222 223### UI State Management 224 225Global state in `Global_vars.Vars` using `Lwd.var`: 226 227```ocaml 228type ui_state_t = { 229 view : [`Main | `Cmd_I of cmd_args | ...] Lwd.var 230 ; hovered_revision : string maybe_unique Lwd.var 231 (* ... *) 232} 233``` 234 235### Command Handling 236 237Commands defined in `graph_commands.ml` / `file_commands.ml`: 238 239```ocaml 240type command = 241 | Cmd of string list 242 | Cmd_async of string * string list 243 | Dynamic of (unit -> command) 244 | Selection_prompt of (...) 245``` 246 247## Dependencies 248 249Key libraries: 250- **nottui** / **nottui_picos**: Terminal UI framework (forked) 251- **lwd** / **lwd_picos**: Reactive programming (forked) 252- **notty**: Terminal rendering (forked) 253- **angstrom**: Parser combinators 254- **picos**: Multicore/async runtime 255- **ppx_expect**: Inline testing 256- **ppx_deriving_yaml/yojson**: Serialization 257 258## Common Pitfalls 259 2601. **End `let` bindings with `;;`** - Required by ocamlformat config 2612. **Use Lwd operators** - Don't call `Lwd.get` directly in render functions 2623. **Functor pattern** - Most modules require `Make(Vars)` instantiation 2634. **Vendored forks** - Don't modify files in `forks/` unless necessary 2645. **Nix required** - Build system not set up for pure opam/dune 265 266 267