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
- “Real engineering lies in the abstractions and the architecture.” → Code Is Cheap Now. Software Isn’t. — Chris Gregori
-
working with upstream:
- if an issue should be an issue with jjui or jj
project stuff
bugs
claude’s bug analysis
- 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.
- 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.
- 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.
- 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.
- 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
previewpanel triggered byp. note that thepreviewpanel 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 intointernal/jj/commands.gofor 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.gofor 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()::$bookmarkin the left panel (view from jj log)- when
mis pressed, focus jumps back to left panel, display is like how rebase operation is shown (aontotag 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
mis pressed, focus jumps back to log view, display likerebaseis shown (themoveandontotag) -
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
statusinfo:- 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-targetfile’s diff -
for renaming files, somehow rename works
- pressing
donR {copy-source => rename-target}works fine
- pressing
~/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 2logs:
- 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”]
help menu search
- refactor
entries
type helpEntry struct {
content string
isModeEntry bool
}
type entryGroup struct {
modeEntry helpEntry
normalEntries []helpEntry
}- the search input hits
normalEntry, show itsmodeEntryand itself - the search input hits
modeEntry, show the wholegroupEntries
questions
- how to implement search?
- if the entry’s content (or description) is
copy selection - does
cop sefind the match or not?- lazygit does
internal/ui/fuzzy_input/fuzzy_input.goalready has it
- if the entry’s content (or description) is
bubble tea list
- bubble tea list example has fuzzy search to, let’s use this one
- Create a list with items that implement the
Iteminterface, which requires aFilterValue()method that returns the string to filter against
- Create a list with items that implement the
Iteminterface:
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
isMatchedtohelpItemstruct - do dimming:
- unmatched item is dimmed
- matched item is normal color
- matched characters in matched item is highlighted - idk how to implement this yet