detached e557ce2

refactor(daemon): tidy attention repaint plumbing

commit
e557ce2d4b0f478405f185f6832571aa379b56b3
author
brice · · 2026-07-03 14:37 UTC
committer
brice · · 2026-07-03 15:33 UTC
parent
791d18b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +9 -15
M internal/usecase/daemon/attention.go +3 -8
@@ -5,6 +5,7 @@ import (
55 "time"
66
77 "github.com/bnema/vev/internal/domain"
8+ "github.com/bnema/vev/internal/ports"
89 "github.com/bnema/vev/internal/usecase/picker"
910 )
1011
@@ -164,7 +165,7 @@ func (d *Daemon) pokeAttentionTicker() {
164165 }
165166 }
166167
167-func (d *Daemon) attentionFrame(time.Time) int {
168+func (d *Daemon) attentionFrame() int {
168169 if d == nil {
169170 return 0
170171 }
@@ -175,7 +176,7 @@ func (d *Daemon) attentionFrame(time.Time) int {
175176
176177 func (d *Daemon) attentionAnimator(ctx context.Context) {
177178 active := false
178- var timer portsTimer
179+ var timer ports.Timer
179180 for {
180181 if !active {
181182 if !d.anyAttention() {
@@ -249,9 +250,3 @@ func (d *Daemon) setAttentionFrame(frame int) {
249250 d.animFrame = frame
250251 d.attnMu.Unlock()
251252 }
252-
253-type portsTimer interface {
254- C() <-chan time.Time
255- Reset(time.Duration) bool
256- Stop() bool
257-}
@@ -5,6 +5,7 @@ import (
5 "time"5 "time"
6 6
7 "github.com/bnema/vev/internal/domain"7 "github.com/bnema/vev/internal/domain"
8+ "github.com/bnema/vev/internal/ports"
8 "github.com/bnema/vev/internal/usecase/picker"9 "github.com/bnema/vev/internal/usecase/picker"
9 )10 )
10 11
@@ -164,7 +165,7 @@ func (d *Daemon) pokeAttentionTicker() {
164 }165 }
165 }166 }
166 167
167-func (d *Daemon) attentionFrame(time.Time) int {168+func (d *Daemon) attentionFrame() int {
168 if d == nil {169 if d == nil {
169 return 0170 return 0
170 }171 }
@@ -175,7 +176,7 @@ func (d *Daemon) attentionFrame(time.Time) int {
175 176
176 func (d *Daemon) attentionAnimator(ctx context.Context) {177 func (d *Daemon) attentionAnimator(ctx context.Context) {
177 active := false178 active := false
178- var timer portsTimer179+ var timer ports.Timer
179 for {180 for {
180 if !active {181 if !active {
181 if !d.anyAttention() {182 if !d.anyAttention() {
@@ -249,9 +250,3 @@ func (d *Daemon) setAttentionFrame(frame int) {
249 d.animFrame = frame250 d.animFrame = frame
250 d.attnMu.Unlock()251 d.attnMu.Unlock()
251 }252 }
252-
253-type portsTimer interface {
254- C() <-chan time.Time
255- Reset(time.Duration) bool
256- Stop() bool
257-}
M internal/usecase/daemon/attention_test.go +2 -2
@@ -433,7 +433,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) {
433433 timer := clk.nextTimer(t)
434434 timer.fire()
435435 _ = mustOutputData(t, sends)
436- require.Equal(t, 1, d.attentionFrame(time.Time{}))
436+ require.Equal(t, 1, d.attentionFrame())
437437
438438 sess.mu.Lock()
439439 sess.tabs[1].attention = false
@@ -441,7 +441,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) {
441441 sess.mu.Unlock()
442442 d.pokeAttentionTicker()
443443
444- require.Eventually(t, func() bool { return d.attentionFrame(time.Time{}) == 0 }, time.Second, time.Millisecond)
444+ require.Eventually(t, func() bool { return d.attentionFrame() == 0 }, time.Second, time.Millisecond)
445445 _ = mustOutputData(t, sends)
446446 select {
447447 case <-clk.timers:
@@ -433,7 +433,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) {
433 timer := clk.nextTimer(t)433 timer := clk.nextTimer(t)
434 timer.fire()434 timer.fire()
435 _ = mustOutputData(t, sends)435 _ = mustOutputData(t, sends)
436- require.Equal(t, 1, d.attentionFrame(time.Time{}))436+ require.Equal(t, 1, d.attentionFrame())
437 437
438 sess.mu.Lock()438 sess.mu.Lock()
439 sess.tabs[1].attention = false439 sess.tabs[1].attention = false
@@ -441,7 +441,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) {
441 sess.mu.Unlock()441 sess.mu.Unlock()
442 d.pokeAttentionTicker()442 d.pokeAttentionTicker()
443 443
444- require.Eventually(t, func() bool { return d.attentionFrame(time.Time{}) == 0 }, time.Second, time.Millisecond)444+ require.Eventually(t, func() bool { return d.attentionFrame() == 0 }, time.Second, time.Millisecond)
445 _ = mustOutputData(t, sends)445 _ = mustOutputData(t, sends)
446 select {446 select {
447 case <-clk.timers:447 case <-clk.timers:
M internal/usecase/daemon/client.go +1 -1
@@ -45,7 +45,7 @@ import (
4545 type attachedClient struct {
4646 tr ports.Transport
4747 rend *renderer.Renderer
48- bars barCache
48+ bars barCache // only touched while sendMu is held
4949 size domain.Size
5050 keys *keys.Router
5151 sess Guarded[*session]
@@ -45,7 +45,7 @@ import (
45 type attachedClient struct {45 type attachedClient struct {
46 tr ports.Transport46 tr ports.Transport
47 rend *renderer.Renderer47 rend *renderer.Renderer
48- bars barCache48+ bars barCache // only touched while sendMu is held
49 size domain.Size49 size domain.Size
50 keys *keys.Router50 keys *keys.Router
51 sess Guarded[*session]51 sess Guarded[*session]
M internal/usecase/daemon/session.go +2 -3
@@ -20,9 +20,8 @@
2020 // Locking: a session's screen and per-client renderer shadow are both guarded
2121 // by tab.mu; the attached-client pointer by session.mu; the registry by
2222 // Daemon.mu. When more than one is held the order is always
23-// Daemon.mu > session.mu, and (for the transport) attachedClient.sendMu >
24-// tab.mu — the PTY reader only ever takes tab.mu, so it never blocks on
25-// a slow client.
23+// sendMu > Daemon.mu > session.mu > tab.mu — the PTY reader only ever takes
24+// tab.mu, so it never blocks on a slow client.
2625 package daemon
2726
2827 import (
@@ -20,9 +20,8 @@
20 // Locking: a session's screen and per-client renderer shadow are both guarded20 // Locking: a session's screen and per-client renderer shadow are both guarded
21 // by tab.mu; the attached-client pointer by session.mu; the registry by21 // by tab.mu; the attached-client pointer by session.mu; the registry by
22 // Daemon.mu. When more than one is held the order is always22 // Daemon.mu. When more than one is held the order is always
23-// Daemon.mu > session.mu, and (for the transport) attachedClient.sendMu >23+// sendMu > Daemon.mu > session.mu > tab.mu — the PTY reader only ever takes
24-// tab.mu — the PTY reader only ever takes tab.mu, so it never blocks on24+// tab.mu, so it never blocks on a slow client.
25-// a slow client.
26 package daemon25 package daemon
27 26
28 import (27 import (
M internal/usecase/daemon/status.go +1 -1
@@ -150,7 +150,7 @@ func (s *session) statusSegments() statusSnapshot {
150150 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {
151151 state := barState{copyFeedback: copyFeedback}
152152 if d != nil {
153- state.attentionFrame = d.attentionFrame(d.clock.Now())
153+ state.attentionFrame = d.attentionFrame()
154154 }
155155 if cur != nil {
156156 state.status = cur.statusSegments()
@@ -150,7 +150,7 @@ func (s *session) statusSegments() statusSnapshot {
150 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {150 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {
151 state := barState{copyFeedback: copyFeedback}151 state := barState{copyFeedback: copyFeedback}
152 if d != nil {152 if d != nil {
153- state.attentionFrame = d.attentionFrame(d.clock.Now())153+ state.attentionFrame = d.attentionFrame()
154 }154 }
155 if cur != nil {155 if cur != nil {
156 state.status = cur.statusSegments()156 state.status = cur.statusSegments()

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 2

Tags 13

↑↓ moveenter openesc close