jjui stuff

tags: project

what i’ve learned

  • soft skills

    • before: open source is about being technical, throw out a PR, let others see your work, let your work speak for itself
    • after: no, there’s a lot of soft skills
  • trust

    • it’s someone’s baby, you are a complete stranger to this someone
    • learn what’s needed and what would be appreciated, contribute accordingly to gain trust, before sending your own features
    • it’s even harder to gain trust now with AI slops. from the perspective of a maintainer, there’re so many plausible looking but incorrect and low quality PRs
  • code standard

    • commits messages, merge, rebase…
  • join the community

    • be the real user, identify problems and propose fix, don’t imagine problems
    • hang out and get involved
  • work on something for a long time, see the section in article:

Work on a codebase with other people over a longer period of time

Thorsten Ball - Professional Programming: The First 10 Years

  • impact

    • when people open an issue and say thank you
    • when you see github stars
  • deal with other’s PRs

    • deal with a huge PR of their own specific feature requests
  • it’s a great way to learn more about softwares

    • people opening feature requests, saying i have XYZ in this tool, i want it to be in jjui
    • i’ve never known XYZ, i have to look and study it
  • all sorts of custom configs… all sorts of unexpected bugs

  • heavily using the thing i am building

    • This is a necessary but insufficient condition for building something that is useful.
  • backward compatibility

    • jj has breaking changes, jjui needs to be compatible
  • certain commands are not available on non-unix (/proc/{pid}/status)

  • how to talk to ssh: Add option to hijack SSH Askpass to promp for passphrase/pin

    • it spawns a few subprocesses, it has to talk via unix socket
    • the reason behind the need for a server: ssh spawns a subprocess that jjui has no control, need a server to bridge communication
    • working with ssh askpass
  • learning about other tools

    • lazygit, magit, lazyjj, kajji
  • learning how to think about abstraction, how to plan and have a vision of a product

  • working with upstream:

    • if an issue should be an issue with jjui or jj

project stuff

bugs

claude’s bug analysis

  1. Memory leak in renderer.go:71: The rowRanges slice grows with each Render() call but only clears to [:0], keeping the underlying capacity. Over time with many renders, this could consume excessive memory.
  2. Race condition in list/model.go:108-115: The drag scrolling logic calculates delta from dragStart but doesn’t validate that dragStart was actually set. If someone clicks without dragging, dragStart remains at zero value.
  3. Off-by-one potential in list/model.go:81-87: The ClickAt function uses y >= rr.Start && y < rr.End but doesn’t validate that x is within reasonable bounds - clicking far to the right could still trigger selection.
  4. Missing bounds check in splitview.go:89-95: ClickAt doesn’t verify the pane exists before calling clickAt(x-pane.X, y-pane.Y). If layout changes during a click event, this could panic.
  5. Scroll delta sign inconsistency: Mouse wheel events might have platform-dependent delta directions that aren’t normalized.

abandon on divergent change

  • it should follow commit_id, not change_id

feature requests

bookmark list view

prompt:

come up with a creative way to display an interactive bookmark list (for now, triggered with X).

  • it’s a half page display on the right hand side, like the current preview panel triggered by p. note that the preview panel is not focus-able, but i want the bookmark list to be focus-able (when it’s triggered, the cursor will be set to be in the bookmark list panel)
  • the content of bookmark list is fetched by jj bookmark list. look into internal/jj/commands.go for available commands
  • in the interactive bookmark list, user would be able to create, delete, rename, forget, delete, move, track, untrack, set bookmarks, refer to internal/jj/commands.go for existing commands and https://jj-vcs.github.io/jj/ for jj’s documentation
  • hitting enter on a bookmark list entry updates revsets to be trunk()::$bookmark in the left panel (view from jj log)
  • when m is pressed, focus jumps back to left panel, display is like how rebase operation is shown (a onto tag appears on revisions)
  • half page view on the right hand side, the page should receive focus, cursor can go in

  • default might be jj bookmark list

  • support other options from jj bookmark list -h:

    • remote
    • conflict?
    • revision?
  • hitting enter on a bookmark gives change history of this bookmark (maybe just draw out in jj log view?)

    • jj log -r ‘trunk()::$bookmark_name’
  • select multiple to forget?

  • when m is pressed, focus jumps back to log view, display like rebase is shown (the move and onto tag)

  • jjui-interactive-bookmark-view.excalidraw

    ⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠ You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’

    Excalidraw Data

    Text Elements

    JJ LOG VIEW

    JJ BOOKMARK VIEW

    remote: remoteA remoteB all

    bookmark 1

    bookmark 2

    bookmark 3

    bookmark view: d delete, f forget, s set, u untrack, t track, m move, enter show in log view

    Link to original


new revision from bookmark

rename bookmark

  • press b, there’s rename option
  • press rename, prompt a text input box
  • enter new name, save, update bookmark name

Oct 27, 2025 - copied files don’t show properly

issue

reproduce

# Create a source file
echo "copy1\ncopy2\ncopy3\n" > copy-source
# Create a new commit
jj new
# Modify the source slightly and create a copy with similar content
echo "copy1\ncopy2\ncopy3\nsource\n" > copy-source
echo "copy1\ncopy2\ncopy3\ntarget\n" > copy-target
 
jj status
  • below is what jj shows
M copy-source
C {copy-source => copy-target}
  • but jjui shows:
A {copy-source => copy-target}
  • the command jjui runs to get this status info:
    • this actually gets the correct output
jj log -r kq --summary --no-graph --color never --quiet --template 'separate(";", diff.files().map(|x| x.target().conflict())) ++ "\n"' --ignore-working-copy

  • this is the command getting the preview:
jj diff --color always -r kq 'file:"{new_file.md => copied_file.md}"'
# Warning: No matching entries for paths: {new_file.md => copied_file.md}

todo

  • find out why is the ^correct-log-cmd getting the right result but displaying the wrong output

  • how to get a copy-target file’s diff

  • for renaming files, somehow rename works

    • pressing d on R {copy-source => rename-target} works fine
~/Downloads/jjtest
 j d
Modified regular file copied_file.md (new_file.md => copied_file.md):
   1    1: hello
        2: hi
Modified regular file new_file.md:
   1    1: hello
        2: hello
Modified regular file rename-target (copy-source => rename-target):
 
~/Downloads/jjtest
 j st
Working copy changes:
C {new_file.md => copied_file.md}
M new_file.md
R {copy-source => rename-target}
Working copy  (@) : kqrqtrot 0388123d copied file 2 and rename file
Parent commit (@-): ynumqyvs 184acf35 new file 2

logs:

  • somehow rename has the correct file name
  • but copy doesn’t!!! debug 2025/10/27 21:12:46 details.go:Update, Diff key pressed debug 2025/10/27 21:12:46 details.go:Update, current selected file is &{status:0 name:{new_file.md copied_file.md} fileName:{new_file.md copied_file.md} selected:true conflict:false} debug 2025/10/27 21:12:46 RunCommandImmediate: [diff -r kq —color always —ignore-working-copy file:“{new_file.md copied_file.md}”] debug 2025/10/27 21:12:48 details.go:Update, Diff key pressed debug 2025/10/27 21:12:48 details.go:Update, current selected file is &{status:3 name:{copy-source rename-target} fileName:rename-target selected:false conflict:false} debug 2025/10/27 21:12:48 RunCommandImmediate: [diff -r kq —color always —ignore-working-copy file:“rename-target”]
  • refactor entries
type helpEntry struct {
 content string
 isModeEntry bool
}
type entryGroup struct {
 modeEntry     helpEntry
 normalEntries []helpEntry
}
  • the search input hits normalEntry, show its modeEntry and itself
  • the search input hits modeEntry, show the whole groupEntries

questions

  • how to implement search?
    • if the entry’s content (or description) is copy selection
    • does cop se find the match or not?
      • lazygit does
    • internal/ui/fuzzy_input/fuzzy_input.go already has it

bubble tea list

  • bubble tea list example has fuzzy search to, let’s use this one
    • Create a list with items that implement the Item interface, which requires a FilterValue() method that returns the string to filter against
  • Item interface:
type Item interface {
 // FilterValue is the value we use when filtering against this item when we're filtering the list.
 FilterValue() string
}
m := model{list: list.New(items, list.NewDefaultDelegate(), 0, 0)}
m.list.Title = "My Fave Things"

others

  • add a isMatched to helpItem struct
  • do dimming:
    • unmatched item is dimmed
    • matched item is normal color
    • matched characters in matched item is highlighted - idk how to implement this yet