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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +9 -15
M internal/usecase/daemon/attention.go +3 -8
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "time" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/vev/internal/domain" | |
| 8 | + | "github.com/bnema/vev/internal/ports" | |
| 8 | 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 | 169 | if d == nil { | |
| 169 | 170 | return 0 | |
| 170 | 171 | } | |
| @@ -175,7 +176,7 @@ func (d *Daemon) attentionFrame(time.Time) int { | |||
| 175 | 176 | ||
| 176 | 177 | func (d *Daemon) attentionAnimator(ctx context.Context) { | |
| 177 | 178 | active := false | |
| 178 | - | var timer portsTimer | |
| 179 | + | var timer ports.Timer | |
| 179 | 180 | for { | |
| 180 | 181 | if !active { | |
| 181 | 182 | if !d.anyAttention() { | |
| @@ -249,9 +250,3 @@ func (d *Daemon) setAttentionFrame(frame int) { | |||
| 249 | 250 | d.animFrame = frame | |
| 250 | 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 | - | } | |
| @@ -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 0 | 170 | 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 := false | 178 | active := false | ||
| 178 | - | var timer portsTimer | 179 | + | 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 = frame | 250 | 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) { | |||
| 433 | 433 | timer := clk.nextTimer(t) | |
| 434 | 434 | timer.fire() | |
| 435 | 435 | _ = mustOutputData(t, sends) | |
| 436 | - | require.Equal(t, 1, d.attentionFrame(time.Time{})) | |
| 436 | + | require.Equal(t, 1, d.attentionFrame()) | |
| 437 | 437 | ||
| 438 | 438 | sess.mu.Lock() | |
| 439 | 439 | sess.tabs[1].attention = false | |
| @@ -441,7 +441,7 @@ func TestAttentionAnimatorParksAdvancesAndResets(t *testing.T) { | |||
| 441 | 441 | sess.mu.Unlock() | |
| 442 | 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 | 445 | _ = mustOutputData(t, sends) | |
| 446 | 446 | select { | |
| 447 | 447 | 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 = false | 439 | 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 ( | |||
| 45 | 45 | type attachedClient struct { | |
| 46 | 46 | tr ports.Transport | |
| 47 | 47 | rend *renderer.Renderer | |
| 48 | - | bars barCache | |
| 48 | + | bars barCache // only touched while sendMu is held | |
| 49 | 49 | size domain.Size | |
| 50 | 50 | keys *keys.Router | |
| 51 | 51 | sess Guarded[*session] | |
| @@ -45,7 +45,7 @@ import ( | |||||
| 45 | type attachedClient struct { | 45 | type attachedClient struct { | ||
| 46 | tr ports.Transport | 46 | tr ports.Transport | ||
| 47 | rend *renderer.Renderer | 47 | rend *renderer.Renderer | ||
| 48 | - | bars barCache | 48 | + | bars barCache // only touched while sendMu is held |
| 49 | size domain.Size | 49 | size domain.Size | ||
| 50 | keys *keys.Router | 50 | keys *keys.Router | ||
| 51 | sess Guarded[*session] | 51 | sess Guarded[*session] | ||
M internal/usecase/daemon/session.go +2 -3
| @@ -20,9 +20,8 @@ | |||
| 20 | 20 | // Locking: a session's screen and per-client renderer shadow are both guarded | |
| 21 | 21 | // by tab.mu; the attached-client pointer by session.mu; the registry by | |
| 22 | 22 | // 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. | |
| 26 | 25 | package daemon | |
| 27 | 26 | ||
| 28 | 27 | import ( | |
| @@ -20,9 +20,8 @@ | |||||
| 20 | // Locking: a session's screen and per-client renderer shadow are both guarded | 20 | // 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 by | 21 | // 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 always | 22 | // 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 on | 24 | + | // tab.mu, so it never blocks on a slow client. |
| 25 | - | // a slow client. | |||
| 26 | package daemon | 25 | 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 { | |||
| 150 | 150 | func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState { | |
| 151 | 151 | state := barState{copyFeedback: copyFeedback} | |
| 152 | 152 | if d != nil { | |
| 153 | - | state.attentionFrame = d.attentionFrame(d.clock.Now()) | |
| 153 | + | state.attentionFrame = d.attentionFrame() | |
| 154 | 154 | } | |
| 155 | 155 | if cur != nil { | |
| 156 | 156 | 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() | ||