Skip to content

Unified Actions + Bindings system#533

Merged
idursun merged 21 commits intomainfrom
exp/actions-bindings
Feb 17, 2026
Merged

Unified Actions + Bindings system#533
idursun merged 21 commits intomainfrom
exp/actions-bindings

Conversation

@idursun
Copy link
Copy Markdown
Owner

@idursun idursun commented Feb 14, 2026

Note

Diff is huge but mostly because of generated code for built-in actions and large chunks of removed leader/custom command/key handling code.

This PR replaces the legacy keybinding system (hardcoded KeyMsg handling, [keys] config, [custom_commands], [leader] sequences) with a unified actions + bindings architecture. All keyboard input now flows through a single dispatch pipeline:

KeyMsg -> Dispatcher -> Binding -> Action -> Intent -> Model.Update

Motivation

Previously, keybindings were hardcoded in each model's Update method via key.Matches(msg, ...) blocks. Custom commands, leader keys, and sequence overlays were separate systems with their own config formats and dispatch logic. This made it difficult to add new bindings, customize behavior, or expose actions to Lua scripts.

The new system centralises all input handling into a single dispatcher. Models no longer deal with key presses directly; they only respond to intents. This makes models simpler, input handling uniform, and the full set of actions discoverable and configurable.

What Changed

New Dispatch Pipeline (internal/ui/dispatch/)

  • Dispatcher: Resolves key presses against scoped bindings, supporting both single-key and multi-key sequence bindings.
  • Resolver: Extends the dispatcher to resolve all the way from key press to intent, checking operation overrides, the generated catalog, and configured Lua actions in order.

Code Generation (cmd/genactions/)

Intent types in internal/ui/intents/ are annotated with //jjui:bind comments that declare which scope and action they belong to, along with optional field mappings. A code generator scans these annotations and produces:

  • catalog_gen.go (internal/ui/actions/): Maps action IDs to intent constructors with argument parsing.
  • builtins_gen.go (internal/ui/actionmeta/): Action metadata (arg schemas, required args, owners) used for config validation help.

This eliminates manual boilerplate when adding new actions. Annotate the intent type and regenerate. It also makes built-in actions callable from Lua scripts through the shared action catalog.

To regenerate after changing intent annotations:

go run ./cmd/genactions

A staleness test ensures generated code stays in sync with annotations.

Note

A Makefile with build, run, test, and generate targets would improve this workflow.

Declarative Binding Configuration

All default key bindings are declared in internal/config/default/bindings.toml, separate from code. Each entry specifies key (or seq for multi-key sequences), action, scope, and optional args:

# Single key binding
[[bindings]]
key = "a"
action = "revisions.abandon" # built-in action
scope = "revisions"          # where this binding is active

# Multiple keys for the same action
[[bindings]]
key = ["up", "k"]
action = "revisions.move_up"
scope = "revisions"

# Multi-key sequence (replaces leader keys)
[[bindings]]
seq = ["g", "r"]
action = "ui.open_revset"
scope = "revisions"

# Binding with arguments
[[bindings]]
key = "r"
action = "revisions.rebase.set_source"
scope = "revisions.rebase"
args = { source = "revision" }

Scopes form a chain from innermost to outermost. The dispatcher walks the chain to find the first matching binding, giving inner scopes priority.

User customization: users add [[bindings]] entries in config to override or extend defaults. A merge strategy removes shadowed bindings by scope+key/seq, so users only specify what they want to change:

# Rebind abandon to 'x' instead of 'a'
[[bindings]]
key = "x"
action = "revisions.abandon"
scope = "revisions"

Custom Actions in TOML and Lua

Users can define custom actions in TOML and bind them via [[bindings]]:

[[actions]]
name = "copy_diff"
lua = '''
local diff = jj("diff", "-r", context.change_id(), "--git")
copy_to_clipboard(diff)
'''

[[bindings]]
action = "copy_diff"
scope = "revisions"
seq = ["w", "y"]
desc = "copy diff"

This branch also adds first-class config.lua setup support:

  • config.lua lives next to config.toml.
  • setup(config) can register actions and bindings programmatically.
  • New API names:
    • config.action(name, fn, opts?)
    • config.bind({...})
  • config.action(..., opts) supports inline binding via key/seq + scope.
  • config.action(..., opts.desc) and config.bind({ desc = ... }) set per-binding help text.
  • config.action(..., opts) enforces key and seq as mutually exclusive, and requires scope when either is provided.
  • config.bind({...}) currently supports action, scope, key/seq, and desc (binding args are not currently supported from config.lua).
  • Lua require(...) resolves modules from the jjui config directory (?.lua and ?/init.lua).
  • config.lua and TOML actions share the same Lua VM, so Lua helpers defined in config.lua can be used by TOML actions.
  • Global functions declared in config.lua can be called directly from [[actions]].lua scripts in config.toml.

Example:

function say_name(prefix)
  flash(prefix)
end

function setup(config)
  config.action("say hello to me", function()
    say_name("hello mister")
  end, {
    desc = "hello to me",
    seq = { "w", "p" },
    scope = "revisions",
  })

  config.bind({
    action = "ui.open_revset",
    key = "R",
    scope = "revisions",
  })
end

The status bar help now derives keys directly from active bindings in the current scope, so custom and rebound keys are reflected automatically without maintaining separate hardcoded lists.

A bindings_profile config option allows loading an alternative base bindings file.

Configuration UX and Migration

  • jjui --config opens (and creates if needed) config.toml in $EDITOR/$VISUAL.
  • jjui --config --migrate migrates legacy [custom_commands] into [[actions]] + [[bindings]].
    • Creates config.old.toml backup on first run.
    • Removes [custom_commands] from active config.
    • Appends generated [[actions]] / [[bindings]].
    • Prints migrated entries and warnings.

How can I test this branch?

  1. Build and run:
go build ./cmd/jjui
./jjui
  1. Open config and add a TOML action + binding:
jjui --config
[[actions]]
name = "say-hi"
lua = '''
flash("hello from config.toml")
'''

[[bindings]]
action = "say-hi"
key = "H"
scope = "revisions"
desc = "say hi"
  1. Add config.lua next to config.toml and define Lua setup actions:
function setup(config)
  config.action("say hello to me", function()
    flash("hello from config.lua")
  end, {
    desc = "hello to me",
    seq = { "w", "p" },
    scope = "revisions",
  })
end
  1. Verify plugin-style extension loading via require(...):
-- ~/.config/jjui/plugins/my_extension.lua
local M = {}

function M.setup(config)
  config.action("say hello", function()
    say_name("hello world")
  end, { desc = "hello", seq = { "w", "h" }, scope = "revisions" })
end

return M
-- ~/.config/jjui/config.lua
local my_extension = require("plugins.my_extension")

function setup(config)
  my_extension.setup(config)
end

Technically, loading another Lua file and calling its setup from config.lua is enough to support plugins.

You can also define a global helper in config.lua and call it from TOML actions:

function helper(msg)
  flash(msg)
end
[[actions]]
name = "helper-demo"
lua = 'helper("called from TOML")'
  1. Re-run jjui and verify both bindings (H and w,p) in the revisions view.
  2. Use internal/config/default/bindings.toml as the source of truth for available scopes/actions while rebinding.
  3. If you have legacy [custom_commands], run migration and verify generated blocks:
jjui --config --migrate

What Was Removed

The unified system replaces several separate mechanisms:

  • Leader keys ([leader] config, internal/ui/leader/): replaced by sequence bindings (seq in [[bindings]]).
  • Custom commands ([custom_commands] config, internal/ui/custom_commands/, internal/ui/context/custom_*.go): replaced by [[actions]] + [[bindings]].
  • Hardcoded key dispatch (internal/config/keys.go, key.Matches blocks in ui.go and operation models): models no longer handle tea.KeyMsg directly. All key-to-intent translation happens in the dispatch layer.
  • [keys] config section: rejected at load time with a migration message to [[bindings]].

#515

@idursun idursun force-pushed the exp/actions-bindings branch 3 times, most recently from 19312ee to dcce042 Compare February 14, 2026 19:09
@nickchomey
Copy link
Copy Markdown
Contributor

Very impressive! Can I suggest making a migration guide to help us switch to this?

@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 15, 2026

@nickchomey sure, it makes sense to provide a migration guide. But we can also do even better and provide a command like jjui --config --migrate and automate the whole thing. I'll give it a try.

@baggiiiie
Copy link
Copy Markdown
Collaborator

wow always an incredible PR!

would u consider adding support similar to what i mentioned in #435 (comment) ? or something similar to what @vic originally proposed?

@vic
Copy link
Copy Markdown
Collaborator

vic commented Feb 15, 2026

I love this, @idursun. you are awesome!

@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 16, 2026

wow always an incredible PR!

would u consider adding support similar to what i mentioned in #435 (comment) ? or something similar to what @vic originally proposed?

I am thinking about it but I still couldn't figure out the exact flow of it.

Most of the folks will want to use TOML based syntax because it is easier and straight-forward.

It makes sense to load TOML first and then run the config.lua to allow config modifications. Power users will define their own Lua functions and use them in their Lua based action definitions.

However, I am almost 100% sure that some folks will reference their config.lua functions in the TOML actions too. This won't work because Lua scripts run as stand alone.

Maybe, we can say, by convention, setup(config) function in the config.lua is invoked at start up to change the configuration and everything else in the config.lua will be loaded while running actions at runtime so both TOML and config.lua based actions work. I need to spend some time to figure this out. It almost feels like a separate PR.

@baggiiiie
Copy link
Copy Markdown
Collaborator

by convention, setup(config) function in the config.lua is invoked at start up to change the configuration

yeah i think this offers a nicer experience. how lazyvim/neovim do it is quite intuitive (auto-load files under certain dir)

@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 16, 2026

I added --migrate and config.lua support and updated the PR description.

@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 16, 2026

Geez, I have just realised that we are one step away from having a plugin system with this config.lua change.

yeah i think this offers a nicer experience. how lazyvim/neovim do it is quite intuitive (auto-load files under certain dir)

Technically we should be able to load another Lua file (require("plugins.my_extension")) and call its setup function from the config.lua and we are pretty much done in terms of supporting plugins.

-- ~/.config/jjui/plugins/my_extension.lua
local M = {}

function M.setup(config)
  config.action("say hello", function()
    say_name("hello world")
  end, { desc = "hello", seq = { "w", "h" }, scope = "revisions" })
end

return M
-- ~/.config/jjui/config.lua

local my_extension = require("plugins.my_extension")

function setup(config)
  my_extension.setup(config)
end

Hold my 🍺

@baggiiiie
Copy link
Copy Markdown
Collaborator

quietly watching @idursun make things happen 👀

@vic
Copy link
Copy Markdown
Collaborator

vic commented Feb 16, 2026

I have just realised that we are one step away from having a plugin system with this config.lua change

yeah! exactly that was the reason behind me suggesting lua configs. :)

now just mix-in some existing lua-plugin loader and viola, community shareable jjui-configs.

This removes the leader + custom commands in favour of actions +
bindings system.

All hardcoded keybindings are removed from models and tea.KeyMsg
handling code is removed. Instead, all tea.KeyMsg's are handled by the
dispatcher. 

All the builtin-actions are generated from jjui:bind annotations via a
generator. Dispatcher maps the tea.KeyMsg against a binding and updates
the target model with the action's Intent struct.
we already run `go fmt` before building the appp
* decouple confirmation model's next/prev actions from using revisions navigate intent
* move ui level `Cancel` and `Apply` intents out of revset
`ui` is only used refering to shell actions (e.g. `quit`, `suspend`)
jjui --config --migrate

* copies `config.toml` as `config.old.toml` (if doesn't exist)
* read `config.old.toml`
* converts `custom_command` section to new action + binding system
* saves the rest of the config + new action + binding - custom_commands
It's where it makes more sense because actions can have args which change the meaning of the binding.
@idursun idursun force-pushed the exp/actions-bindings branch from 6978986 to a3d5cf4 Compare February 17, 2026 00:17
@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 17, 2026

I think this PR is as good as it can get. After spending more than two weeks on it, I want to ship it one way or the other.

I am marking this ready for review and plan to fix stuff I missed in subsequent PRs. Merging this PR will mark the end of the 0.9 series.

@idursun idursun marked this pull request as ready for review February 17, 2026 00:46
@idursun idursun requested review from Adda0 and vic as code owners February 17, 2026 00:46
go run ./cmd/genactions
```

This produces `catalog_gen.go` and `builtins_gen.go`. A staleness test will fail if generated code is out of sync with annotations.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on having this run manually for now, so good we have developer comments about this and out-of-sync checks.

{ inputs, ... }:
{
imports = [
inputs.treefmt-nix.flakeModule
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on removing treefmt.

I see the nix CI step still verifies that the jjui app is built correctly and that is enough for me.

"github.com/BurntSushi/toml"
)

// Migrate converts legacy [custom_commands] config to [[actions]] + [[bindings]].
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! kudos for this migration tool

}
defer scripting.CloseVM(appContext)

if luaSource, err := config.LoadLuaConfigFile(); err != nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaay! Thanks for lua configs.

vic
vic previously approved these changes Feb 17, 2026
Copy link
Copy Markdown
Collaborator

@vic vic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got my eyes over most of the changes just to see how you implemented it all, and I'm +1 on all of it. Feel free to merge if you've tested it locally, I still have to use this branhc.

Adda0
Adda0 previously approved these changes Feb 17, 2026
Copy link
Copy Markdown
Collaborator

@Adda0 Adda0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Nix changes LGTM. If you found the formatter more of a bother than useful, then by all means, remove it.

Out of curiosity, what did you find problematic about treefmt? Any particular reason, or just generally too strict and bothersome to maintain the other files, other than just Go code, like the markdown, formatted?

Great changes, otherwise.

This is throw away code.

Previous version skipped over keys, didn't convert lua based custom commands and used regex to clean up TOML (which didn't always match)
@idursun idursun dismissed stale reviews from Adda0 and vic via adc943d February 17, 2026 21:07
@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 17, 2026

@Adda0

Out of curiosity, what did you find problematic about treefmt?

I pushed bindings.toml file formatted in an easily scannable way but nixfmt failed the build, which then I disabled it for TOML files but the build failed again. At that point, I didn't know how to fix it and removed it altogether. I didn't like the fact that the build failing even though everything passing on my machine.

@idursun idursun force-pushed the exp/actions-bindings branch from 0442515 to bab1a86 Compare February 17, 2026 22:49
@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 17, 2026

I fixed a couple of issues around Lua scripts and a few issues in the migration command.

I couldn't find anything else but I am sure there will be one as soon as merge this!

@idursun idursun merged commit 36c0c3d into main Feb 17, 2026
4 checks passed
@idursun idursun deleted the exp/actions-bindings branch February 17, 2026 23:05
@baggiiiie
Copy link
Copy Markdown
Collaborator

baggiiiie commented Feb 18, 2026

nice work!

just pulled down main and tried out, migration didn't work perfectly for me, still have quite a bit of manually adjustment, i'll try to find out why and post some fixes if i can

it's a bummer that scope only allows string, would be great if it supports array. what do you think @idursun ?


now pressing p doesn't work in preview, i had a quick look at the codebase, seems like it's due to p only binds to revisions scope. similarly, @ isn't available in rebase/squash

for this specific case, we could add another line in bindings.toml to allow p in revisions.details.

in the long run, i think it would be great to support array for scopes, for better plugins/custom_command.lua UX
or we could have inheritance, e.g., revisions.details automatically inherit bindings from revisions, unless being overwritten

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Feb 18, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [idursun/jjui](https://github.com/idursun/jjui) | patch | `v0.9.11` → `v0.9.12` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>idursun/jjui (idursun/jjui)</summary>

### [`v0.9.12`](https://github.com/idursun/jjui/releases/tag/v0.9.12)

[Compare Source](idursun/jjui@v0.9.11...v0.9.12)

### Release 0.9.12 (Final 0.9.x Series)

> \[!NOTE]
> This is a quick release before marking the end of 0.9.x series due to breaking changes introduced by the work in [#&#8203;533](idursun/jjui#533)

#### Fixes & Improvements

- Modal rendering now fills the entire git, bookmarks, and custom command dialogs before drawing rows, eliminating gaps introduced by the new renderer [#&#8203;535](idursun/jjui#535)
- Fixed a regression where `ui.colors.selected = { bg = "black" }` no longer highlighted the selected revision by restoring the correct lipgloss → ANSI color conversion [#&#8203;534](idursun/jjui#534)
- `jj git fetch --tracked` uses the `t` shortcut so `f` remains dedicated to the default fetch, matching the push command’s tracked shortcut [#&#8203;532](idursun/jjui#532)
- Added a `[keys.diff_view]` config section so diff scrolling, paging, and closing bindings are customizable [#&#8203;527](idursun/jjui#527)
- Completion now auto-inserts `()` when selecting a parameterless function, improving Lua/script authoring speed [#&#8203;530](idursun/jjui#530)
- Lua’s `flash()` accepts `error` and `sticky` fields, so scripts can show error toasts and control persistence without additional helpers [#&#8203;529](idursun/jjui#529)

#### What's Changed

- Auto-append () for parameterless functions in completion by [@&#8203;Dima-369](https://github.com/Dima-369) in [#&#8203;530](idursun/jjui#530)
- Add customizable keybindings for the diff view (\[keys.diff\_view]) by [@&#8203;Dima-369](https://github.com/Dima-369) in [#&#8203;527](idursun/jjui#527)
- fix(ui/git): update key for `jj git fetch --tracked` by [@&#8203;PrayagS](https://github.com/PrayagS) in [#&#8203;532](idursun/jjui#532)

#### New Contributors

- [@&#8203;Dima-369](https://github.com/Dima-369) made their first contribution in [#&#8203;530](idursun/jjui#530)

**Full Changelog**: <idursun/jjui@v0.9.11...0.9.12>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOS4yIiwidXBkYXRlZEluVmVyIjoiNDMuMTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6OnBhdGNoIl19-->
@idursun
Copy link
Copy Markdown
Owner Author

idursun commented Feb 18, 2026

for this specific case, we could add another line in bindings.toml to allow p in revisions.details.

Yes, for the time being I prefer duplication over code complexity. In reality, most users will spend some time with configuration and probably hate the duplication during that time but I will hate the code complexity almost every day.

@baggiiiie
Copy link
Copy Markdown
Collaborator

In reality, most users will spend some time with configuration and probably hate the duplication during that time but I will hate the code complexity almost every day.

well said!

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Mar 11, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [idursun/jjui](https://github.com/idursun/jjui) | minor | `v0.9.12` → `v0.10.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>idursun/jjui (idursun/jjui)</summary>

### [`v0.10.0`](https://github.com/idursun/jjui/releases/tag/v0.10.0)

[Compare Source](idursun/jjui@v0.9.12...v0.10.0)

Wow, a major release after a very long time. As promised, `v0.10` is ready, and it comes with **breaking changes**.

First, thank you to everybody who contributed code, bug reports, ideas, and testing for this release. In particular, thanks to [@&#8203;baggiiiie](https://github.com/baggiiiie) for various features and fixes, [@&#8203;nickchomey](https://github.com/nickchomey) for contributions and continuous feedback, and [@&#8203;vic](https://github.com/vic) for updating the documentation website. Thanks as well to everyone else for various code contributions, reporting issues, and verifying fixes.

We changed a lot in `v0.10`, but the biggest shift is that we finally got rid of some **legacy configuration** and moved to a unified **actions + bindings** model. Old concepts like `[custom_commands]` and `[leader]` are now replaced by a more consistent system built around **actions**, **bindings**, and first-class support for leader-style key sequences.

This release also introduces `config.lua`, which makes it much easier to customise and extend `jjui` with **real scripting** instead of only static configuration. Between `config.toml`, `config.lua`, Lua actions, and bindings, much more of the UI can now be customised in one consistent way.

The documentation has been updated with [**migration notes**](https://idursun.github.io/jjui/migrating/v0_10/) and examples such as the [**Lua Cookbook**](https://idursun.github.io/jjui/lua-cookbook/).

From my testing it looks ready, but I am sure more rough edges will show up once more people start using it, so please keep reporting issues as you find them.

#### ⚠️ Breaking Changes

`v0.10` replaces the **legacy keybinding and customisation model** with the new **actions + bindings** system.

- `[keys]` configuration is no longer used.
- `[custom_commands]` is replaced by `[[actions]]` and `[[bindings]]`.
- `[leader]` sequences are replaced by sequence bindings via `seq`.

If you have an existing configuration, plan to **migrate it** rather than expecting a drop-in upgrade from `v0.9.x`.

#### ✨Highlights

- **Unified actions + bindings system.** Keyboard input now flows through a single dispatch pipeline, default bindings are declared in TOML, actions are discoverable, and custom behaviour can be defined in both TOML and Lua.
- **Much stronger scripting support.** Lua `setup` now receives the full config plus runtime context, generated intent-backed actions replace handwritten drift-prone bridges, and new helpers like `wait_close()` and `wait_refresh()` make multi-step scripts more predictable.
- **Upgraded TUI foundations.** Moving from **Bubble Tea v1** to **v2** brings better terminal support, a new rendering engine for better performance, and better input handling, including support for bindings like `ctrl+space` and dedicated `shift+...` style keys.
- **Improved diff and selection workflows.** The `diff` view now supports word wrap, `revisions` and `details` support modifier-based mouse selection, and several `details` and `evolog` interactions are more consistent.

#### 🚀 New and Improved

##### Command History

- Added [**command history**](https://idursun.github.io/jjui/command-history/), available with `shift+w`, to review dismissed command runs together with their output.
- Moved **running command** information out of the status area and into dedicated `flash` messages, so the command and its output are now shown together in one place.

##### Help and Status

- Reintroduced a dedicated **Help view** for full keybinding discovery, with filtering and scrolling, alongside the existing inline help.
- Changed **status/help** binding presentation to **column-major order** so related bindings are easier to scan.

##### Lua and Configuration

- Shipped the unified **actions + bindings** architecture introduced in `v0.10`.
- Exposed the full config object to Lua `setup`, including runtime context such as repository path via `config.repo`, and terminal state via `config.terminal.dark_mode`, `config.terminal.fg`, and `config.terminal.bg`.
- Standardised closeable action naming around `open_*` and aligned apply/close semantics for more predictable scripted workflows.

##### Oplog

- Added **quick search** to `oplog` and shared the search behavior with revision search.

##### Evolog

- Changed `evolog` preview defaults to show **interdiff** output, matching `jj evolog -p` more closely.

##### Abandon

- Added `s` in `abandon` mode to target descendants of the selected revision(s).

##### Revset

- Kept `revset` editing open when the expression is invalid, improving iteration while editing.
- Improved `revset` editing so function completion inserts `(` when appropriate, while the function list display is cleaner and less noisy.

##### Diff

- Added **word wrap** in the `diff` view, with explicit wrapped and unwrapped modes plus soft-wrap-aware scrolling.
- Added `g` and `G` in the `diff` view to jump to the top and bottom.

##### Details and Revisions

- Added `Ctrl+click` single-item toggle and `Alt+click` range selection in `revisions` and `details`.
- Added a `revisions.details.select_file` action for finer control in `details` workflows.

#### 🛠️ Fixes

- Fixed rendering issues with **double-width characters**.
- Simplified divergent revision handling by using `change_id/offset` directly from the `jj log` prefix template.
- Fixed `git push --all` behaviour for deleted bookmarks and corrected related command descriptions.

#### ⚙️ Internal Updates

- Upgraded to **Bubble Tea v2**.
- Replaced remaining `cellbuf` usage with `ultraviolet`.
- Hardcoded model-level key handling has been removed in favor of dispatcher-driven intents.
- Continued internal cleanup around rendering, overlays, display context, and generated action plumbing to support the new architecture.

#### What's Changed

- Unified Actions + Bindings system by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;533](idursun/jjui#533)
- feat(oplog): add QuickSearch to oplog, share logic with revision search by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;521](idursun/jjui#521)
- fix: add missing keys in bindings by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;538](idursun/jjui#538)
- feat(evolog): change default evolog command to show interdiff by [@&#8203;academician](https://github.com/academician) in [#&#8203;539](idursun/jjui#539)
- fix: add back `esc` to clear checked revisions by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;541](idursun/jjui#541)
- remove gh-pages workflow from main branch. by [@&#8203;vic](https://github.com/vic) in [#&#8203;542](idursun/jjui#542)
- docs: fix readme links by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;549](idursun/jjui#549)
- Update CONTRIBUTING.md for documentation by [@&#8203;vic](https://github.com/vic) in [#&#8203;547](idursun/jjui#547)
- feat(lua): add "filter" and "ordered" option to `choose` lua api by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;548](idursun/jjui#548)
- feat(abandon): use `s` for selecting descendants by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;544](idursun/jjui#544)
- feat: add change\_offset in jj log prefix template for divergent revisions by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;550](idursun/jjui#550)
- fix: make bookmark input visible when user has custom template-aliases by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;551](idursun/jjui#551)
- fix(evolog): keep restore mode navigation on revisions and esc close by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;554](idursun/jjui#554)
- Expose full config to Lua setup and add runtime context (repo + terminal) by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;553](idursun/jjui#553)
- feat(flash): command history by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;556](idursun/jjui#556)
- Update to bubbletea v2 by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;558](idursun/jjui#558)
- Ctrl+click and Alt+click for single and range selection by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;563](idursun/jjui#563)
- feat: add `(` to functions when autocompleted by [@&#8203;nickchomey](https://github.com/nickchomey) in [#&#8203;564](idursun/jjui#564)
- feat(status): change display to column major order by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;569](idursun/jjui#569)
- fix: remove parentheses from functions list, but add when autocomplete by [@&#8203;nickchomey](https://github.com/nickchomey) in [#&#8203;567](idursun/jjui#567)
- fix(git): prevent panic when opening git menu with no selected revision by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;571](idursun/jjui#571)
- Refactor/Lua bridge by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;572](idursun/jjui#572)
- fix(ui): guard against nil commit in OpenBookmarks intent by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;573](idursun/jjui#573)
- feat(diff): word wrap by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;575](idursun/jjui#575)
- Add pgup/pgdown keybindings to evolog and details by [@&#8203;academician](https://github.com/academician) in [#&#8203;576](idursun/jjui#576)
- feat(lua): make default actions overridable and expose jjui.builtin.\* by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;577](idursun/jjui#577)
- feat(help): add "help view" bound to `f1` by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;578](idursun/jjui#578)

#### New Contributors

- [@&#8203;academician](https://github.com/academician) made their first contribution in [#&#8203;539](idursun/jjui#539)

**Full Changelog**: <idursun/jjui@v0.9.12...v0.10.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS40IiwidXBkYXRlZEluVmVyIjoiNDMuNTkuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants