detached aa3698c

feat(daemon): publish floating pane configuration

commit
aa3698c72bfb1d7400b643dbffefaa47865a9baa
author
brice · · 2026-07-09 23:24 UTC
committer
brice · · 2026-07-10 05:40 UTC
parent
edb3828
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +30 -0
M internal/usecase/daemon/config.go +9 -0
@@ -27,6 +27,8 @@ func (d *Daemon) ApplyConfig(cfg domain.Config) {
2727 d.bindings.Store(bindings)
2828 d.codeOverrides.Store(&overrides)
2929 d.restoreProcessAllowlist.Store(&allowlist)
30+ floating := cfg.Floating
31+ d.floatingConfig.Store(&floating)
3032 d.themeMode.Store(uint32(cfg.Theme))
3133 if d.barScripts != nil {
3234 d.barScripts.mu.Lock()
@@ -176,6 +178,13 @@ func (d *Daemon) logConfigWarning(w domain.Warning) {
176178 d.log.Warn("config warning", "msg", w.Msg)
177179 }
178180
181+func (d *Daemon) currentFloatingConfig() domain.FloatingConfig {
182+ if cfg := d.floatingConfig.Load(); cfg != nil {
183+ return *cfg
184+ }
185+ return domain.Defaults().Floating
186+}
187+
179188 func (d *Daemon) codeOverrideSnapshot() map[string]string {
180189 overrides := d.codeOverrides.Load()
181190 if overrides == nil {
@@ -27,6 +27,8 @@ func (d *Daemon) ApplyConfig(cfg domain.Config) {
27 d.bindings.Store(bindings)27 d.bindings.Store(bindings)
28 d.codeOverrides.Store(&overrides)28 d.codeOverrides.Store(&overrides)
29 d.restoreProcessAllowlist.Store(&allowlist)29 d.restoreProcessAllowlist.Store(&allowlist)
30+ floating := cfg.Floating
31+ d.floatingConfig.Store(&floating)
30 d.themeMode.Store(uint32(cfg.Theme))32 d.themeMode.Store(uint32(cfg.Theme))
31 if d.barScripts != nil {33 if d.barScripts != nil {
32 d.barScripts.mu.Lock()34 d.barScripts.mu.Lock()
@@ -176,6 +178,13 @@ func (d *Daemon) logConfigWarning(w domain.Warning) {
176 d.log.Warn("config warning", "msg", w.Msg)178 d.log.Warn("config warning", "msg", w.Msg)
177 }179 }
178 180
181+func (d *Daemon) currentFloatingConfig() domain.FloatingConfig {
182+ if cfg := d.floatingConfig.Load(); cfg != nil {
183+ return *cfg
184+ }
185+ return domain.Defaults().Floating
186+}
187+
179 func (d *Daemon) codeOverrideSnapshot() map[string]string {188 func (d *Daemon) codeOverrideSnapshot() map[string]string {
180 overrides := d.codeOverrides.Load()189 overrides := d.codeOverrides.Load()
181 if overrides == nil {190 if overrides == nil {
M internal/usecase/daemon/config_test.go +18 -0
@@ -49,6 +49,24 @@ func TestApplyConfigHotReloadSwapsBindingsAndCodes(t *testing.T) {
4949 require.Equal(t, "NT", cmd.Code)
5050 }
5151
52+func TestApplyConfigPublishesImmutableFloatingSnapshot(t *testing.T) {
53+ d := newTestDaemon(t, nil, stubClock{})
54+ require.Equal(t, domain.Defaults().Floating, d.currentFloatingConfig())
55+
56+ firstConfig := domain.Defaults()
57+ firstConfig.Floating = domain.FloatingConfig{Command: "btop", Width: 70, Height: 60}
58+ d.ApplyConfig(firstConfig)
59+ first := d.currentFloatingConfig()
60+
61+ secondConfig := domain.Defaults()
62+ secondConfig.Floating = domain.FloatingConfig{Command: "lazygit", Width: 90, Height: 85}
63+ d.ApplyConfig(secondConfig)
64+ second := d.currentFloatingConfig()
65+
66+ require.Equal(t, domain.FloatingConfig{Command: "btop", Width: 70, Height: 60}, first)
67+ require.Equal(t, domain.FloatingConfig{Command: "lazygit", Width: 90, Height: 85}, second)
68+}
69+
5270 func TestApplyConfigSnapshotRestoreProcesses(t *testing.T) {
5371 tests := []struct {
5472 name string
@@ -49,6 +49,24 @@ func TestApplyConfigHotReloadSwapsBindingsAndCodes(t *testing.T) {
49 require.Equal(t, "NT", cmd.Code)49 require.Equal(t, "NT", cmd.Code)
50 }50 }
51 51
52+func TestApplyConfigPublishesImmutableFloatingSnapshot(t *testing.T) {
53+ d := newTestDaemon(t, nil, stubClock{})
54+ require.Equal(t, domain.Defaults().Floating, d.currentFloatingConfig())
55+
56+ firstConfig := domain.Defaults()
57+ firstConfig.Floating = domain.FloatingConfig{Command: "btop", Width: 70, Height: 60}
58+ d.ApplyConfig(firstConfig)
59+ first := d.currentFloatingConfig()
60+
61+ secondConfig := domain.Defaults()
62+ secondConfig.Floating = domain.FloatingConfig{Command: "lazygit", Width: 90, Height: 85}
63+ d.ApplyConfig(secondConfig)
64+ second := d.currentFloatingConfig()
65+
66+ require.Equal(t, domain.FloatingConfig{Command: "btop", Width: 70, Height: 60}, first)
67+ require.Equal(t, domain.FloatingConfig{Command: "lazygit", Width: 90, Height: 85}, second)
68+}
69+
52 func TestApplyConfigSnapshotRestoreProcesses(t *testing.T) {70 func TestApplyConfigSnapshotRestoreProcesses(t *testing.T) {
53 tests := []struct {71 tests := []struct {
54 name string72 name string
M internal/usecase/daemon/daemon.go +3 -0
@@ -126,6 +126,7 @@ type Daemon struct {
126126 bindings atomic.Pointer[keys.Bindings]
127127 codeOverrides atomic.Pointer[map[string]string]
128128 restoreProcessAllowlist atomic.Pointer[map[string]struct{}]
129+ floatingConfig atomic.Pointer[domain.FloatingConfig]
129130 themeMode atomic.Uint32
130131 barScripts *barScriptState
131132 resumeParkGrace time.Duration
@@ -306,6 +307,8 @@ func New(ptys ports.PTYFactory, clock ports.Clock, log *slog.Logger, opts ...Opt
306307 running: make(map[domain.SessionID]bool),
307308 },
308309 }
310+ defaultFloating := domain.Defaults().Floating
311+ d.floatingConfig.Store(&defaultFloating)
309312 for _, o := range opts {
310313 o(d)
311314 }
@@ -126,6 +126,7 @@ type Daemon struct {
126 bindings atomic.Pointer[keys.Bindings]126 bindings atomic.Pointer[keys.Bindings]
127 codeOverrides atomic.Pointer[map[string]string]127 codeOverrides atomic.Pointer[map[string]string]
128 restoreProcessAllowlist atomic.Pointer[map[string]struct{}]128 restoreProcessAllowlist atomic.Pointer[map[string]struct{}]
129+ floatingConfig atomic.Pointer[domain.FloatingConfig]
129 themeMode atomic.Uint32130 themeMode atomic.Uint32
130 barScripts *barScriptState131 barScripts *barScriptState
131 resumeParkGrace time.Duration132 resumeParkGrace time.Duration
@@ -306,6 +307,8 @@ func New(ptys ports.PTYFactory, clock ports.Clock, log *slog.Logger, opts ...Opt
306 running: make(map[domain.SessionID]bool),307 running: make(map[domain.SessionID]bool),
307 },308 },
308 }309 }
310+ defaultFloating := domain.Defaults().Floating
311+ d.floatingConfig.Store(&defaultFloating)
309 for _, o := range opts {312 for _, o := range opts {
310 o(d)313 o(d)
311 }314 }

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