detached 624e9d5

Merge pull request #110 from bnema/fix/issue-107-go-install

fix(config): disable external bar commands by default
commit
624e9d5e760a06072f77ffcbaa5095f06ae6f524
author
bnema · · 2026-07-18 14:01 UTC
committer
GitHub · · 2026-07-18 14:01 UTC
merge of
df34440 + 13f3c56 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
5 files · +30 -15
M README.md +1 -1
@@ -20,7 +20,7 @@ What it is built around:
2020 go install github.com/bnema/vev@latest
2121 ```
2222
23-From a checkout, `make install` also installs the default status bar scripts.
23+The default UI runs entirely from the `vev` binary. Optional external bar anchors can be configured in `~/.config/vev/config`.
2424
2525 ## Usage
2626
@@ -20,7 +20,7 @@ What it is built around:
20 go install github.com/bnema/vev@latest20 go install github.com/bnema/vev@latest
21 ```21 ```
22 22
23-From a checkout, `make install` also installs the default status bar scripts.23+The default UI runs entirely from the `vev` binary. Optional external bar anchors can be configured in `~/.config/vev/config`.
24 24
25 ## Usage25 ## Usage
26 26
M docs/configuration.md +4 -4
@@ -34,9 +34,9 @@ switch-tab-1 = alt+1
3434 # Processes relaunched when a named session is restored. Empty disables relaunch.
3535 snapshot.restore_processes = vi,vim,nvim,emacs,man,less,more,tail,top,htop,btop,claude,codex,pi,opencode
3636
37-# Right bar anchors: commands run on the daemon host. Empty disables an anchor.
38-bar.top-right = vev-bar-top-right
39-bar.bottom-right = vev-bar-bottom-right
37+# Optional right bar anchors: commands run on the daemon host. Empty disables an anchor.
38+bar.top-right =
39+bar.bottom-right =
4040 bar.interval = 5s
4141
4242 # Command palette codes: 2-3 letters or digits.
@@ -102,4 +102,4 @@ The command runs through your shell. A changed command applies on the next launc
102102
103103 Anchor commands run on the daemon host every `bar.interval` (minimum 1s). vev reads the first line of stdout, strips ANSI codes, and keeps the last good value on failure. Scripts get `VEV_ANCHOR`, `VEV_SESSION`, `VEV_TAB`, `VEV_PANE`, `VEV_PANE_CWD`, and `VEV_COLS`.
104104
105-The default bottom-right script runs `git status --porcelain` on each refresh; raise the interval or replace it if that is too heavy for your repository.
105+Both anchors are disabled by default, so `go install github.com/bnema/vev@latest` needs no companion scripts. Set either command to opt in; explicit commands are run unchanged. Checkout and release installs include `vev-bar-top-right` and `vev-bar-bottom-right` example scripts, which can be configured by name when they are on the daemon host's `PATH`. The bottom-right example runs `git status --porcelain` on each refresh; raise the interval or replace it if that is too heavy for your repository.
@@ -34,9 +34,9 @@ switch-tab-1 = alt+1
34 # Processes relaunched when a named session is restored. Empty disables relaunch.34 # Processes relaunched when a named session is restored. Empty disables relaunch.
35 snapshot.restore_processes = vi,vim,nvim,emacs,man,less,more,tail,top,htop,btop,claude,codex,pi,opencode35 snapshot.restore_processes = vi,vim,nvim,emacs,man,less,more,tail,top,htop,btop,claude,codex,pi,opencode
36 36
37-# Right bar anchors: commands run on the daemon host. Empty disables an anchor.37+# Optional right bar anchors: commands run on the daemon host. Empty disables an anchor.
38-bar.top-right = vev-bar-top-right38+bar.top-right =
39-bar.bottom-right = vev-bar-bottom-right39+bar.bottom-right =
40 bar.interval = 5s40 bar.interval = 5s
41 41
42 # Command palette codes: 2-3 letters or digits.42 # Command palette codes: 2-3 letters or digits.
@@ -102,4 +102,4 @@ The command runs through your shell. A changed command applies on the next launc
102 102
103 Anchor commands run on the daemon host every `bar.interval` (minimum 1s). vev reads the first line of stdout, strips ANSI codes, and keeps the last good value on failure. Scripts get `VEV_ANCHOR`, `VEV_SESSION`, `VEV_TAB`, `VEV_PANE`, `VEV_PANE_CWD`, and `VEV_COLS`.103 Anchor commands run on the daemon host every `bar.interval` (minimum 1s). vev reads the first line of stdout, strips ANSI codes, and keeps the last good value on failure. Scripts get `VEV_ANCHOR`, `VEV_SESSION`, `VEV_TAB`, `VEV_PANE`, `VEV_PANE_CWD`, and `VEV_COLS`.
104 104
105-The default bottom-right script runs `git status --porcelain` on each refresh; raise the interval or replace it if that is too heavy for your repository.105+Both anchors are disabled by default, so `go install github.com/bnema/vev@latest` needs no companion scripts. Set either command to opt in; explicit commands are run unchanged. Checkout and release installs include `vev-bar-top-right` and `vev-bar-bottom-right` example scripts, which can be configured by name when they are on the daemon host's `PATH`. The bottom-right example runs `git status --porcelain` on each refresh; raise the interval or replace it if that is too heavy for your repository.
M internal/adapters/config/config_test.go +13 -7
@@ -16,6 +16,12 @@ import (
1616 "github.com/stretchr/testify/require"
1717 )
1818
19+func TestDefaultsDisableBarCommands(t *testing.T) {
20+ bar := domain.Defaults().Bar
21+ require.Empty(t, bar.TopRight)
22+ require.Empty(t, bar.BottomRight)
23+}
24+
1925 func TestParse(t *testing.T) {
2026 t.Parallel()
2127
@@ -217,7 +223,7 @@ func TestParse(t *testing.T) {
217223 Theme: domain.ThemeAuto,
218224 Bar: domain.BarConfig{
219225 TopRight: "echo foo #1",
220- BottomRight: "vev-bar-bottom-right",
226+ BottomRight: "",
221227 Interval: 5 * time.Second,
222228 },
223229 BindingEntries: []domain.ConfigEntry{},
@@ -230,8 +236,8 @@ func TestParse(t *testing.T) {
230236 want: domain.Config{
231237 Theme: domain.ThemeAuto,
232238 Bar: domain.BarConfig{
233- TopRight: "vev-bar-top-right",
234- BottomRight: "vev-bar-bottom-right",
239+ TopRight: "",
240+ BottomRight: "",
235241 Interval: 5 * time.Second,
236242 },
237243 BindingEntries: []domain.ConfigEntry{},
@@ -259,8 +265,8 @@ func TestParse(t *testing.T) {
259265 want: domain.Config{
260266 Theme: domain.ThemeAuto,
261267 Bar: domain.BarConfig{
262- TopRight: "vev-bar-top-right",
263- BottomRight: "vev-bar-bottom-right",
268+ TopRight: "",
269+ BottomRight: "",
264270 Interval: 5 * time.Second,
265271 },
266272 BindingEntries: []domain.ConfigEntry{},
@@ -274,8 +280,8 @@ func TestParse(t *testing.T) {
274280 want: domain.Config{
275281 Theme: domain.ThemeAuto,
276282 Bar: domain.BarConfig{
277- TopRight: "vev-bar-top-right",
278- BottomRight: "vev-bar-bottom-right",
283+ TopRight: "",
284+ BottomRight: "",
279285 Interval: time.Second,
280286 },
281287 BindingEntries: []domain.ConfigEntry{},
@@ -16,6 +16,12 @@ import (
16 "github.com/stretchr/testify/require"16 "github.com/stretchr/testify/require"
17 )17 )
18 18
19+func TestDefaultsDisableBarCommands(t *testing.T) {
20+ bar := domain.Defaults().Bar
21+ require.Empty(t, bar.TopRight)
22+ require.Empty(t, bar.BottomRight)
23+}
24+
19 func TestParse(t *testing.T) {25 func TestParse(t *testing.T) {
20 t.Parallel()26 t.Parallel()
21 27
@@ -217,7 +223,7 @@ func TestParse(t *testing.T) {
217 Theme: domain.ThemeAuto,223 Theme: domain.ThemeAuto,
218 Bar: domain.BarConfig{224 Bar: domain.BarConfig{
219 TopRight: "echo foo #1",225 TopRight: "echo foo #1",
220- BottomRight: "vev-bar-bottom-right",226+ BottomRight: "",
221 Interval: 5 * time.Second,227 Interval: 5 * time.Second,
222 },228 },
223 BindingEntries: []domain.ConfigEntry{},229 BindingEntries: []domain.ConfigEntry{},
@@ -230,8 +236,8 @@ func TestParse(t *testing.T) {
230 want: domain.Config{236 want: domain.Config{
231 Theme: domain.ThemeAuto,237 Theme: domain.ThemeAuto,
232 Bar: domain.BarConfig{238 Bar: domain.BarConfig{
233- TopRight: "vev-bar-top-right",239+ TopRight: "",
234- BottomRight: "vev-bar-bottom-right",240+ BottomRight: "",
235 Interval: 5 * time.Second,241 Interval: 5 * time.Second,
236 },242 },
237 BindingEntries: []domain.ConfigEntry{},243 BindingEntries: []domain.ConfigEntry{},
@@ -259,8 +265,8 @@ func TestParse(t *testing.T) {
259 want: domain.Config{265 want: domain.Config{
260 Theme: domain.ThemeAuto,266 Theme: domain.ThemeAuto,
261 Bar: domain.BarConfig{267 Bar: domain.BarConfig{
262- TopRight: "vev-bar-top-right",268+ TopRight: "",
263- BottomRight: "vev-bar-bottom-right",269+ BottomRight: "",
264 Interval: 5 * time.Second,270 Interval: 5 * time.Second,
265 },271 },
266 BindingEntries: []domain.ConfigEntry{},272 BindingEntries: []domain.ConfigEntry{},
@@ -274,8 +280,8 @@ func TestParse(t *testing.T) {
274 want: domain.Config{280 want: domain.Config{
275 Theme: domain.ThemeAuto,281 Theme: domain.ThemeAuto,
276 Bar: domain.BarConfig{282 Bar: domain.BarConfig{
277- TopRight: "vev-bar-top-right",283+ TopRight: "",
278- BottomRight: "vev-bar-bottom-right",284+ BottomRight: "",
279 Interval: time.Second,285 Interval: time.Second,
280 },286 },
281 BindingEntries: []domain.ConfigEntry{},287 BindingEntries: []domain.ConfigEntry{},
M internal/domain/config.go +1 -3
@@ -105,9 +105,7 @@ func Defaults() Config {
105105 Theme: ThemeAuto,
106106 ThemePalette: true,
107107 Bar: BarConfig{
108- TopRight: "vev-bar-top-right",
109- BottomRight: "vev-bar-bottom-right",
110- Interval: 5 * time.Second,
108+ Interval: 5 * time.Second,
111109 },
112110 BindingEntries: []ConfigEntry{},
113111 Codes: map[string]string{},
@@ -105,9 +105,7 @@ func Defaults() Config {
105 Theme: ThemeAuto,105 Theme: ThemeAuto,
106 ThemePalette: true,106 ThemePalette: true,
107 Bar: BarConfig{107 Bar: BarConfig{
108- TopRight: "vev-bar-top-right",108+ Interval: 5 * time.Second,
109- BottomRight: "vev-bar-bottom-right",
110- Interval: 5 * time.Second,
111 },109 },
112 BindingEntries: []ConfigEntry{},110 BindingEntries: []ConfigEntry{},
113 Codes: map[string]string{},111 Codes: map[string]string{},
M internal/usecase/daemon/bar_refresh_test.go +11 -0
@@ -139,6 +139,17 @@ func TestBarScriptRefreshSkipsRunnerWhenCommandsDisabled(t *testing.T) {
139139 require.Empty(t, state.bottomRight)
140140 }
141141
142+func TestDefaultBarConfigDoesNotInvokeRunner(t *testing.T) {
143+ r := &fakeBarRunner{}
144+ d := newBarRefreshTestDaemon(r, time.Second)
145+ d.barScripts.cfg = barConfigFromDomain(domain.Defaults().Bar)
146+ sess := newBarRefreshTestSession()
147+ sess.client = &attachedClient{size: domain.Size{Cols: 80, Rows: 24}}
148+
149+ require.False(t, d.refreshBarScriptsIfDue(sess, time.Unix(0, 0), true))
150+ require.Empty(t, r.calls)
151+}
152+
142153 func TestBarScriptForcedRefreshRespectsMinimumInterval(t *testing.T) {
143154 r := &fakeBarRunner{outs: []string{"top1", "bottom1", "top2", "bottom2"}}
144155 d := newBarRefreshTestDaemon(r, 5*time.Second)
@@ -139,6 +139,17 @@ func TestBarScriptRefreshSkipsRunnerWhenCommandsDisabled(t *testing.T) {
139 require.Empty(t, state.bottomRight)139 require.Empty(t, state.bottomRight)
140 }140 }
141 141
142+func TestDefaultBarConfigDoesNotInvokeRunner(t *testing.T) {
143+ r := &fakeBarRunner{}
144+ d := newBarRefreshTestDaemon(r, time.Second)
145+ d.barScripts.cfg = barConfigFromDomain(domain.Defaults().Bar)
146+ sess := newBarRefreshTestSession()
147+ sess.client = &attachedClient{size: domain.Size{Cols: 80, Rows: 24}}
148+
149+ require.False(t, d.refreshBarScriptsIfDue(sess, time.Unix(0, 0), true))
150+ require.Empty(t, r.calls)
151+}
152+
142 func TestBarScriptForcedRefreshRespectsMinimumInterval(t *testing.T) {153 func TestBarScriptForcedRefreshRespectsMinimumInterval(t *testing.T) {
143 r := &fakeBarRunner{outs: []string{"top1", "bottom1", "top2", "bottom2"}}154 r := &fakeBarRunner{outs: []string{"top1", "bottom1", "top2", "bottom2"}}
144 d := newBarRefreshTestDaemon(r, 5*time.Second)155 d := newBarRefreshTestDaemon(r, 5*time.Second)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close