refactor(daemon): simplify floating pane launch
- commit
- 59ce3a48690bf457646745de67c701321aa79019
- author
- brice · · 2026-07-10 05:47 UTC
- parent
- 5fdd7fe
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +92 -65
M internal/usecase/daemon/floating.go +75 -46
| @@ -29,32 +29,30 @@ type floatingSlot struct { | |||
| 29 | 29 | pane *pane | |
| 30 | 30 | desiredVisible bool | |
| 31 | 31 | generation uint64 | |
| 32 | - | launch domain.FloatingConfig | |
| 33 | 32 | } | |
| 34 | 33 | ||
| 35 | 34 | // beginFloatingWarmLocked starts a single background launch. The caller must | |
| 36 | 35 | // hold tb.mu and must only call it for an uninitialized slot. | |
| 37 | - | func (tb *tab) beginFloatingWarmLocked(launch domain.FloatingConfig, desiredVisible bool) uint64 { | |
| 36 | + | func (tb *tab) beginFloatingWarmLocked(desiredVisible bool) uint64 { | |
| 38 | 37 | if tb == nil || tb.floating.state != floatingUninitialized { | |
| 39 | 38 | return 0 | |
| 40 | 39 | } | |
| 41 | 40 | tb.floating.generation++ | |
| 42 | 41 | tb.floating.state = floatingWarming | |
| 43 | 42 | tb.floating.desiredVisible = desiredVisible | |
| 44 | - | tb.floating.launch = launch | |
| 45 | 43 | return tb.floating.generation | |
| 46 | 44 | } | |
| 47 | 45 | ||
| 48 | 46 | // toggleFloatingLocked applies the user action. Its result says whether a new | |
| 49 | 47 | // PTY launch is required and returns the generation associated with that launch | |
| 50 | 48 | // or current slot. | |
| 51 | - | func (tb *tab) toggleFloatingLocked(launch domain.FloatingConfig) (bool, uint64) { | |
| 49 | + | func (tb *tab) toggleFloatingLocked() (bool, uint64) { | |
| 52 | 50 | if tb == nil { | |
| 53 | 51 | return false, 0 | |
| 54 | 52 | } | |
| 55 | 53 | switch tb.floating.state { | |
| 56 | 54 | case floatingUninitialized: | |
| 57 | - | generation := tb.beginFloatingWarmLocked(launch, true) | |
| 55 | + | generation := tb.beginFloatingWarmLocked(true) | |
| 58 | 56 | return generation != 0, generation | |
| 59 | 57 | case floatingWarming: | |
| 60 | 58 | tb.floating.desiredVisible = !tb.floating.desiredVisible | |
| @@ -90,7 +88,6 @@ func (tb *tab) failFloatingLocked(generation uint64) bool { | |||
| 90 | 88 | tb.floating.state = floatingUninitialized | |
| 91 | 89 | tb.floating.pane = nil | |
| 92 | 90 | tb.floating.desiredVisible = false | |
| 93 | - | tb.floating.launch = domain.FloatingConfig{} | |
| 94 | 91 | return true | |
| 95 | 92 | } | |
| 96 | 93 | ||
| @@ -116,7 +113,6 @@ func (tb *tab) takeFloatingLocked() *pane { | |||
| 116 | 113 | tb.floating.state = floatingUninitialized | |
| 117 | 114 | tb.floating.pane = nil | |
| 118 | 115 | tb.floating.desiredVisible = false | |
| 119 | - | tb.floating.launch = domain.FloatingConfig{} | |
| 120 | 116 | return p | |
| 121 | 117 | } | |
| 122 | 118 | ||
| @@ -172,7 +168,7 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||
| 172 | 168 | } | |
| 173 | 169 | cfg := d.currentFloatingConfig() | |
| 174 | 170 | tb.mu.Lock() | |
| 175 | - | start, generation := tb.toggleFloatingLocked(cfg) | |
| 171 | + | start, generation := tb.toggleFloatingLocked() | |
| 176 | 172 | visible := tb.floating.desiredVisible | |
| 177 | 173 | tb.mu.Unlock() | |
| 178 | 174 | if start { | |
| @@ -189,28 +185,53 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||
| 189 | 185 | func (d *Daemon) startFloating(sess *session, tb *tab, visible bool) { | |
| 190 | 186 | cfg := d.currentFloatingConfig() | |
| 191 | 187 | tb.mu.Lock() | |
| 192 | - | generation := tb.beginFloatingWarmLocked(cfg, visible) | |
| 188 | + | generation := tb.beginFloatingWarmLocked(visible) | |
| 193 | 189 | tb.mu.Unlock() | |
| 194 | 190 | if generation != 0 { | |
| 195 | 191 | d.launchFloating(sess, tb, cfg, generation, visible) | |
| 196 | 192 | } | |
| 197 | 193 | } | |
| 198 | 194 | ||
| 199 | - | // launchFloating snapshots all launch inputs before starting the potentially | |
| 200 | - | // blocking factory call. In particular, no PTY operation occurs while tb.mu is | |
| 201 | - | // held, and config reloads cannot affect this launch. | |
| 195 | + | // floatingLaunchSpec is an immutable snapshot of one launch. It is built | |
| 196 | + | // before the worker starts so config reloads cannot affect that launch. | |
| 197 | + | type floatingLaunchSpec struct { | |
| 198 | + | sessionName string | |
| 199 | + | cwd string | |
| 200 | + | size domain.Size | |
| 201 | + | paneStableID string | |
| 202 | + | env []string | |
| 203 | + | command string | |
| 204 | + | args []string | |
| 205 | + | fallback string | |
| 206 | + | parentCtx context.Context | |
| 207 | + | userOpen bool | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | // launchFloating snapshots launch inputs, then accounts for a worker through | |
| 211 | + | // Open, pane initialization, publication, and reader/scheduler startup. | |
| 202 | 212 | func (d *Daemon) launchFloating(sess *session, tb *tab, cfg domain.FloatingConfig, generation uint64, userOpen bool) { | |
| 213 | + | spec, err := d.newFloatingLaunchSpec(sess, tb, cfg, userOpen) | |
| 214 | + | if err != nil { | |
| 215 | + | d.failFloatingLaunch(tb, generation, userOpen, spec.sessionName, err) | |
| 216 | + | return | |
| 217 | + | } | |
| 218 | + | // Count the launch before its goroutine starts. It remains counted through | |
| 219 | + | // install, including the reader/scheduler Adds, so shutdown cannot return | |
| 220 | + | // before a late Open completion has either been rejected or fully joined. | |
| 221 | + | d.sessWg.Go(func() { d.openAndInstallFloating(sess, tb, spec, generation) }) | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | func (d *Daemon) newFloatingLaunchSpec(sess *session, tb *tab, cfg domain.FloatingConfig, userOpen bool) (floatingLaunchSpec, error) { | |
| 203 | 225 | sess.mu.Lock() | |
| 204 | - | name, cwd, term := sess.name, sess.cwd, sess.terminal | |
| 226 | + | name, cwd, term, sessCtx := sess.name, sess.cwd, sess.terminal, sess.ctx | |
| 205 | 227 | sess.mu.Unlock() | |
| 206 | 228 | tb.mu.Lock() | |
| 207 | - | sz := floatingInnerSize(tb.size, cfg) | |
| 229 | + | size := floatingInnerSize(tb.size, cfg) | |
| 208 | 230 | focused := tb.focusedPane() | |
| 209 | - | tabStableID := tb.stableID | |
| 210 | - | tabCtx := tb.ctx | |
| 231 | + | tabStableID, tabCtx := tb.stableID, tb.ctx | |
| 211 | 232 | tb.mu.Unlock() | |
| 212 | - | if !sz.Valid() { | |
| 213 | - | sz = domain.Size{Cols: 1, Rows: 1} | |
| 233 | + | if !size.Valid() { | |
| 234 | + | size = domain.Size{Cols: 1, Rows: 1} | |
| 214 | 235 | } | |
| 215 | 236 | if focused != nil && d.procCwd != nil && focused.pty != nil { | |
| 216 | 237 | if live, err := d.procCwd(focused.pty.Pid()); err == nil && live != "" { | |
| @@ -222,39 +243,47 @@ func (d *Daemon) launchFloating(sess *session, tb *tab, cfg domain.FloatingConfi | |||
| 222 | 243 | } | |
| 223 | 244 | paneStableID, err := newStableID("p") | |
| 224 | 245 | if err != nil { | |
| 225 | - | d.failFloatingLaunch(tb, generation, userOpen, name, err) | |
| 226 | - | return | |
| 246 | + | return floatingLaunchSpec{sessionName: name}, err | |
| 247 | + | } | |
| 248 | + | if tabCtx == nil { | |
| 249 | + | tabCtx = sessCtx | |
| 250 | + | } | |
| 251 | + | if tabCtx == nil { | |
| 252 | + | tabCtx = context.Background() | |
| 227 | 253 | } | |
| 228 | - | env := d.childEnv(name, tabStableID, paneStableID, term) | |
| 229 | 254 | command, args := d.shell, append([]string(nil), d.shellArgs...) | |
| 230 | 255 | if cfg.Command != "" { | |
| 231 | 256 | args = []string{"-lc", cfg.Command} | |
| 232 | 257 | } | |
| 233 | - | fallback := floatingCommandFallback(cfg.Command, d.shell) | |
| 234 | - | // Count the launch before its goroutine starts. It remains counted through | |
| 235 | - | // install, including the reader/scheduler Adds, so shutdown cannot return | |
| 236 | - | // before a late Open completion has either been rejected or fully joined. | |
| 237 | - | d.sessWg.Go(func() { | |
| 238 | - | pty, openErr := d.ptys.Open(command, args, env, cwd, sz) | |
| 239 | - | if openErr != nil { | |
| 240 | - | d.failFloatingLaunch(tb, generation, userOpen, name, openErr) | |
| 241 | - | return | |
| 242 | - | } | |
| 243 | - | p := newPaneWithStableID(layout.PaneID("floating"), paneStableID, pty, sz) | |
| 244 | - | p.title.displayFallback = fallback | |
| 245 | - | paneCtx := tabCtx | |
| 246 | - | if paneCtx == nil { | |
| 247 | - | paneCtx = sess.ctx | |
| 248 | - | } | |
| 249 | - | if paneCtx == nil { | |
| 250 | - | paneCtx = context.Background() | |
| 251 | - | } | |
| 252 | - | p.ctx, p.cancel = context.WithCancel(paneCtx) | |
| 253 | - | // The reader may run as soon as install returns; make its exit policy | |
| 254 | - | // immutable before publishing the pane to the slot. | |
| 255 | - | p.onExit = func() { d.reapFloating(sess, tb, p, generation) } | |
| 256 | - | d.installFloating(sess, tb, p, generation) | |
| 257 | - | }) | |
| 258 | + | return floatingLaunchSpec{ | |
| 259 | + | sessionName: name, | |
| 260 | + | cwd: cwd, | |
| 261 | + | size: size, | |
| 262 | + | paneStableID: paneStableID, | |
| 263 | + | env: d.childEnv(name, tabStableID, paneStableID, term), | |
| 264 | + | command: command, | |
| 265 | + | args: args, | |
| 266 | + | fallback: floatingCommandFallback(cfg.Command, d.shell), | |
| 267 | + | parentCtx: tabCtx, | |
| 268 | + | userOpen: userOpen, | |
| 269 | + | }, nil | |
| 270 | + | } | |
| 271 | + | ||
| 272 | + | // openAndInstallFloating runs entirely in the launch worker. No PTY operation | |
| 273 | + | // occurs under tb.mu; the pane is fully initialized before publication. | |
| 274 | + | func (d *Daemon) openAndInstallFloating(sess *session, tb *tab, spec floatingLaunchSpec, generation uint64) { | |
| 275 | + | pty, err := d.ptys.Open(spec.command, spec.args, spec.env, spec.cwd, spec.size) | |
| 276 | + | if err != nil { | |
| 277 | + | d.failFloatingLaunch(tb, generation, spec.userOpen, spec.sessionName, err) | |
| 278 | + | return | |
| 279 | + | } | |
| 280 | + | p := newPaneWithStableID(layout.PaneID("floating"), spec.paneStableID, pty, spec.size) | |
| 281 | + | p.title.displayFallback = spec.fallback | |
| 282 | + | p.ctx, p.cancel = context.WithCancel(spec.parentCtx) | |
| 283 | + | // The reader may run as soon as install returns; make its exit policy | |
| 284 | + | // immutable before publishing the pane to the slot. | |
| 285 | + | p.onExit = func() { d.reapFloating(sess, tb, p, generation) } | |
| 286 | + | d.installFloating(sess, tb, p, generation) | |
| 258 | 287 | } | |
| 259 | 288 | ||
| 260 | 289 | func (d *Daemon) failFloatingLaunch(tb *tab, generation uint64, userOpen bool, sessionName string, err error) { | |
| @@ -29,32 +29,30 @@ type floatingSlot struct { | |||||
| 29 | pane *pane | 29 | pane *pane | ||
| 30 | desiredVisible bool | 30 | desiredVisible bool | ||
| 31 | generation uint64 | 31 | generation uint64 | ||
| 32 | - | launch domain.FloatingConfig | |||
| 33 | } | 32 | } | ||
| 34 | 33 | ||||
| 35 | // beginFloatingWarmLocked starts a single background launch. The caller must | 34 | // beginFloatingWarmLocked starts a single background launch. The caller must | ||
| 36 | // hold tb.mu and must only call it for an uninitialized slot. | 35 | // hold tb.mu and must only call it for an uninitialized slot. | ||
| 37 | - | func (tb *tab) beginFloatingWarmLocked(launch domain.FloatingConfig, desiredVisible bool) uint64 { | 36 | + | func (tb *tab) beginFloatingWarmLocked(desiredVisible bool) uint64 { |
| 38 | if tb == nil || tb.floating.state != floatingUninitialized { | 37 | if tb == nil || tb.floating.state != floatingUninitialized { | ||
| 39 | return 0 | 38 | return 0 | ||
| 40 | } | 39 | } | ||
| 41 | tb.floating.generation++ | 40 | tb.floating.generation++ | ||
| 42 | tb.floating.state = floatingWarming | 41 | tb.floating.state = floatingWarming | ||
| 43 | tb.floating.desiredVisible = desiredVisible | 42 | tb.floating.desiredVisible = desiredVisible | ||
| 44 | - | tb.floating.launch = launch | |||
| 45 | return tb.floating.generation | 43 | return tb.floating.generation | ||
| 46 | } | 44 | } | ||
| 47 | 45 | ||||
| 48 | // toggleFloatingLocked applies the user action. Its result says whether a new | 46 | // toggleFloatingLocked applies the user action. Its result says whether a new | ||
| 49 | // PTY launch is required and returns the generation associated with that launch | 47 | // PTY launch is required and returns the generation associated with that launch | ||
| 50 | // or current slot. | 48 | // or current slot. | ||
| 51 | - | func (tb *tab) toggleFloatingLocked(launch domain.FloatingConfig) (bool, uint64) { | 49 | + | func (tb *tab) toggleFloatingLocked() (bool, uint64) { |
| 52 | if tb == nil { | 50 | if tb == nil { | ||
| 53 | return false, 0 | 51 | return false, 0 | ||
| 54 | } | 52 | } | ||
| 55 | switch tb.floating.state { | 53 | switch tb.floating.state { | ||
| 56 | case floatingUninitialized: | 54 | case floatingUninitialized: | ||
| 57 | - | generation := tb.beginFloatingWarmLocked(launch, true) | 55 | + | generation := tb.beginFloatingWarmLocked(true) |
| 58 | return generation != 0, generation | 56 | return generation != 0, generation | ||
| 59 | case floatingWarming: | 57 | case floatingWarming: | ||
| 60 | tb.floating.desiredVisible = !tb.floating.desiredVisible | 58 | tb.floating.desiredVisible = !tb.floating.desiredVisible | ||
| @@ -90,7 +88,6 @@ func (tb *tab) failFloatingLocked(generation uint64) bool { | |||||
| 90 | tb.floating.state = floatingUninitialized | 88 | tb.floating.state = floatingUninitialized | ||
| 91 | tb.floating.pane = nil | 89 | tb.floating.pane = nil | ||
| 92 | tb.floating.desiredVisible = false | 90 | tb.floating.desiredVisible = false | ||
| 93 | - | tb.floating.launch = domain.FloatingConfig{} | |||
| 94 | return true | 91 | return true | ||
| 95 | } | 92 | } | ||
| 96 | 93 | ||||
| @@ -116,7 +113,6 @@ func (tb *tab) takeFloatingLocked() *pane { | |||||
| 116 | tb.floating.state = floatingUninitialized | 113 | tb.floating.state = floatingUninitialized | ||
| 117 | tb.floating.pane = nil | 114 | tb.floating.pane = nil | ||
| 118 | tb.floating.desiredVisible = false | 115 | tb.floating.desiredVisible = false | ||
| 119 | - | tb.floating.launch = domain.FloatingConfig{} | |||
| 120 | return p | 116 | return p | ||
| 121 | } | 117 | } | ||
| 122 | 118 | ||||
| @@ -172,7 +168,7 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||||
| 172 | } | 168 | } | ||
| 173 | cfg := d.currentFloatingConfig() | 169 | cfg := d.currentFloatingConfig() | ||
| 174 | tb.mu.Lock() | 170 | tb.mu.Lock() | ||
| 175 | - | start, generation := tb.toggleFloatingLocked(cfg) | 171 | + | start, generation := tb.toggleFloatingLocked() |
| 176 | visible := tb.floating.desiredVisible | 172 | visible := tb.floating.desiredVisible | ||
| 177 | tb.mu.Unlock() | 173 | tb.mu.Unlock() | ||
| 178 | if start { | 174 | if start { | ||
| @@ -189,28 +185,53 @@ func (d *Daemon) toggleFloating(sess *session, ac *attachedClient) error { | |||||
| 189 | func (d *Daemon) startFloating(sess *session, tb *tab, visible bool) { | 185 | func (d *Daemon) startFloating(sess *session, tb *tab, visible bool) { | ||
| 190 | cfg := d.currentFloatingConfig() | 186 | cfg := d.currentFloatingConfig() | ||
| 191 | tb.mu.Lock() | 187 | tb.mu.Lock() | ||
| 192 | - | generation := tb.beginFloatingWarmLocked(cfg, visible) | 188 | + | generation := tb.beginFloatingWarmLocked(visible) |
| 193 | tb.mu.Unlock() | 189 | tb.mu.Unlock() | ||
| 194 | if generation != 0 { | 190 | if generation != 0 { | ||
| 195 | d.launchFloating(sess, tb, cfg, generation, visible) | 191 | d.launchFloating(sess, tb, cfg, generation, visible) | ||
| 196 | } | 192 | } | ||
| 197 | } | 193 | } | ||
| 198 | 194 | ||||
| 199 | - | // launchFloating snapshots all launch inputs before starting the potentially | 195 | + | // floatingLaunchSpec is an immutable snapshot of one launch. It is built |
| 200 | - | // blocking factory call. In particular, no PTY operation occurs while tb.mu is | 196 | + | // before the worker starts so config reloads cannot affect that launch. |
| 201 | - | // held, and config reloads cannot affect this launch. | 197 | + | type floatingLaunchSpec struct { |
| 198 | + | sessionName string | |||
| 199 | + | cwd string | |||
| 200 | + | size domain.Size | |||
| 201 | + | paneStableID string | |||
| 202 | + | env []string | |||
| 203 | + | command string | |||
| 204 | + | args []string | |||
| 205 | + | fallback string | |||
| 206 | + | parentCtx context.Context | |||
| 207 | + | userOpen bool | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | // launchFloating snapshots launch inputs, then accounts for a worker through | |||
| 211 | + | // Open, pane initialization, publication, and reader/scheduler startup. | |||
| 202 | func (d *Daemon) launchFloating(sess *session, tb *tab, cfg domain.FloatingConfig, generation uint64, userOpen bool) { | 212 | func (d *Daemon) launchFloating(sess *session, tb *tab, cfg domain.FloatingConfig, generation uint64, userOpen bool) { | ||
| 213 | + | spec, err := d.newFloatingLaunchSpec(sess, tb, cfg, userOpen) | |||
| 214 | + | if err != nil { | |||
| 215 | + | d.failFloatingLaunch(tb, generation, userOpen, spec.sessionName, err) | |||
| 216 | + | return | |||
| 217 | + | } | |||
| 218 | + | // Count the launch before its goroutine starts. It remains counted through | |||
| 219 | + | // install, including the reader/scheduler Adds, so shutdown cannot return | |||
| 220 | + | // before a late Open completion has either been rejected or fully joined. | |||
| 221 | + | d.sessWg.Go(func() { d.openAndInstallFloating(sess, tb, spec, generation) }) | |||
| 222 | + | } | |||
| 223 | + | ||||
| 224 | + | func (d *Daemon) newFloatingLaunchSpec(sess *session, tb *tab, cfg domain.FloatingConfig, userOpen bool) (floatingLaunchSpec, error) { | |||
| 203 | sess.mu.Lock() | 225 | sess.mu.Lock() | ||
| 204 | - | name, cwd, term := sess.name, sess.cwd, sess.terminal | 226 | + | name, cwd, term, sessCtx := sess.name, sess.cwd, sess.terminal, sess.ctx |
| 205 | sess.mu.Unlock() | 227 | sess.mu.Unlock() | ||
| 206 | tb.mu.Lock() | 228 | tb.mu.Lock() | ||
| 207 | - | sz := floatingInnerSize(tb.size, cfg) | 229 | + | size := floatingInnerSize(tb.size, cfg) |
| 208 | focused := tb.focusedPane() | 230 | focused := tb.focusedPane() | ||
| 209 | - | tabStableID := tb.stableID | 231 | + | tabStableID, tabCtx := tb.stableID, tb.ctx |
| 210 | - | tabCtx := tb.ctx | |||
| 211 | tb.mu.Unlock() | 232 | tb.mu.Unlock() | ||
| 212 | - | if !sz.Valid() { | 233 | + | if !size.Valid() { |
| 213 | - | sz = domain.Size{Cols: 1, Rows: 1} | 234 | + | size = domain.Size{Cols: 1, Rows: 1} |
| 214 | } | 235 | } | ||
| 215 | if focused != nil && d.procCwd != nil && focused.pty != nil { | 236 | if focused != nil && d.procCwd != nil && focused.pty != nil { | ||
| 216 | if live, err := d.procCwd(focused.pty.Pid()); err == nil && live != "" { | 237 | if live, err := d.procCwd(focused.pty.Pid()); err == nil && live != "" { | ||
| @@ -222,39 +243,47 @@ func (d *Daemon) launchFloating(sess *session, tb *tab, cfg domain.FloatingConfi | |||||
| 222 | } | 243 | } | ||
| 223 | paneStableID, err := newStableID("p") | 244 | paneStableID, err := newStableID("p") | ||
| 224 | if err != nil { | 245 | if err != nil { | ||
| 225 | - | d.failFloatingLaunch(tb, generation, userOpen, name, err) | 246 | + | return floatingLaunchSpec{sessionName: name}, err |
| 226 | - | return | 247 | + | } |
| 248 | + | if tabCtx == nil { | |||
| 249 | + | tabCtx = sessCtx | |||
| 250 | + | } | |||
| 251 | + | if tabCtx == nil { | |||
| 252 | + | tabCtx = context.Background() | |||
| 227 | } | 253 | } | ||
| 228 | - | env := d.childEnv(name, tabStableID, paneStableID, term) | |||
| 229 | command, args := d.shell, append([]string(nil), d.shellArgs...) | 254 | command, args := d.shell, append([]string(nil), d.shellArgs...) | ||
| 230 | if cfg.Command != "" { | 255 | if cfg.Command != "" { | ||
| 231 | args = []string{"-lc", cfg.Command} | 256 | args = []string{"-lc", cfg.Command} | ||
| 232 | } | 257 | } | ||
| 233 | - | fallback := floatingCommandFallback(cfg.Command, d.shell) | 258 | + | return floatingLaunchSpec{ |
| 234 | - | // Count the launch before its goroutine starts. It remains counted through | 259 | + | sessionName: name, |
| 235 | - | // install, including the reader/scheduler Adds, so shutdown cannot return | 260 | + | cwd: cwd, |
| 236 | - | // before a late Open completion has either been rejected or fully joined. | 261 | + | size: size, |
| 237 | - | d.sessWg.Go(func() { | 262 | + | paneStableID: paneStableID, |
| 238 | - | pty, openErr := d.ptys.Open(command, args, env, cwd, sz) | 263 | + | env: d.childEnv(name, tabStableID, paneStableID, term), |
| 239 | - | if openErr != nil { | 264 | + | command: command, |
| 240 | - | d.failFloatingLaunch(tb, generation, userOpen, name, openErr) | 265 | + | args: args, |
| 241 | - | return | 266 | + | fallback: floatingCommandFallback(cfg.Command, d.shell), |
| 242 | - | } | 267 | + | parentCtx: tabCtx, |
| 243 | - | p := newPaneWithStableID(layout.PaneID("floating"), paneStableID, pty, sz) | 268 | + | userOpen: userOpen, |
| 244 | - | p.title.displayFallback = fallback | 269 | + | }, nil |
| 245 | - | paneCtx := tabCtx | 270 | + | } |
| 246 | - | if paneCtx == nil { | 271 | + | |
| 247 | - | paneCtx = sess.ctx | 272 | + | // openAndInstallFloating runs entirely in the launch worker. No PTY operation |
| 248 | - | } | 273 | + | // occurs under tb.mu; the pane is fully initialized before publication. |
| 249 | - | if paneCtx == nil { | 274 | + | func (d *Daemon) openAndInstallFloating(sess *session, tb *tab, spec floatingLaunchSpec, generation uint64) { |
| 250 | - | paneCtx = context.Background() | 275 | + | pty, err := d.ptys.Open(spec.command, spec.args, spec.env, spec.cwd, spec.size) |
| 251 | - | } | 276 | + | if err != nil { |
| 252 | - | p.ctx, p.cancel = context.WithCancel(paneCtx) | 277 | + | d.failFloatingLaunch(tb, generation, spec.userOpen, spec.sessionName, err) |
| 253 | - | // The reader may run as soon as install returns; make its exit policy | 278 | + | return |
| 254 | - | // immutable before publishing the pane to the slot. | 279 | + | } |
| 255 | - | p.onExit = func() { d.reapFloating(sess, tb, p, generation) } | 280 | + | p := newPaneWithStableID(layout.PaneID("floating"), spec.paneStableID, pty, spec.size) |
| 256 | - | d.installFloating(sess, tb, p, generation) | 281 | + | p.title.displayFallback = spec.fallback |
| 257 | - | }) | 282 | + | p.ctx, p.cancel = context.WithCancel(spec.parentCtx) |
| 283 | + | // The reader may run as soon as install returns; make its exit policy | |||
| 284 | + | // immutable before publishing the pane to the slot. | |||
| 285 | + | p.onExit = func() { d.reapFloating(sess, tb, p, generation) } | |||
| 286 | + | d.installFloating(sess, tb, p, generation) | |||
| 258 | } | 287 | } | ||
| 259 | 288 | ||||
| 260 | func (d *Daemon) failFloatingLaunch(tb *tab, generation uint64, userOpen bool, sessionName string, err error) { | 289 | func (d *Daemon) failFloatingLaunch(tb *tab, generation uint64, userOpen bool, sessionName string, err error) { | ||
M internal/usecase/daemon/floating_test.go +16 -18
| @@ -22,8 +22,6 @@ import ( | |||
| 22 | 22 | func TestFloatingSlotTransitions(t *testing.T) { | |
| 23 | 23 | first := &pane{} | |
| 24 | 24 | second := &pane{} | |
| 25 | - | launch := domain.FloatingConfig{Command: "top", Width: 80, Height: 80} | |
| 26 | - | ||
| 27 | 25 | tests := []struct { | |
| 28 | 26 | name string | |
| 29 | 27 | run func(t *testing.T, tb *tab) | |
| @@ -32,12 +30,12 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||
| 32 | 30 | name: "warming double toggle retains hidden completion", | |
| 33 | 31 | run: func(t *testing.T, tb *tab) { | |
| 34 | 32 | tb.mu.Lock() | |
| 35 | - | generation := tb.beginFloatingWarmLocked(launch, false) | |
| 36 | - | start, gotGeneration := tb.toggleFloatingLocked(launch) | |
| 33 | + | generation := tb.beginFloatingWarmLocked(false) | |
| 34 | + | start, gotGeneration := tb.toggleFloatingLocked() | |
| 37 | 35 | if start || gotGeneration != generation || !tb.floating.desiredVisible { | |
| 38 | 36 | t.Fatalf("first warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | |
| 39 | 37 | } | |
| 40 | - | start, gotGeneration = tb.toggleFloatingLocked(launch) | |
| 38 | + | start, gotGeneration = tb.toggleFloatingLocked() | |
| 41 | 39 | if start || gotGeneration != generation || tb.floating.desiredVisible { | |
| 42 | 40 | t.Fatalf("second warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | |
| 43 | 41 | } | |
| @@ -51,15 +49,15 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||
| 51 | 49 | name: "open and hide retain same pane", | |
| 52 | 50 | run: func(t *testing.T, tb *tab) { | |
| 53 | 51 | tb.mu.Lock() | |
| 54 | - | generation := tb.beginFloatingWarmLocked(launch, false) | |
| 52 | + | generation := tb.beginFloatingWarmLocked(false) | |
| 55 | 53 | if !tb.installFloatingLocked(first, generation) { | |
| 56 | 54 | t.Fatal("install failed") | |
| 57 | 55 | } | |
| 58 | - | start, _ := tb.toggleFloatingLocked(launch) | |
| 56 | + | start, _ := tb.toggleFloatingLocked() | |
| 59 | 57 | if start || tb.floating.state != floatingVisible || tb.floating.pane != first { | |
| 60 | 58 | t.Fatal("hidden open did not retain pane") | |
| 61 | 59 | } | |
| 62 | - | tb.toggleFloatingLocked(launch) | |
| 60 | + | tb.toggleFloatingLocked() | |
| 63 | 61 | if tb.floating.state != floatingHidden || tb.floating.pane != first { | |
| 64 | 62 | t.Fatal("visible hide did not retain pane") | |
| 65 | 63 | } | |
| @@ -70,7 +68,7 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||
| 70 | 68 | name: "current launch failure clears desired visibility", | |
| 71 | 69 | run: func(t *testing.T, tb *tab) { | |
| 72 | 70 | tb.mu.Lock() | |
| 73 | - | generation := tb.beginFloatingWarmLocked(launch, true) | |
| 71 | + | generation := tb.beginFloatingWarmLocked(true) | |
| 74 | 72 | if !tb.failFloatingLocked(generation) || tb.floating.state != floatingUninitialized || tb.floating.desiredVisible || tb.floating.pane != nil { | |
| 75 | 73 | t.Fatal("current launch failure did not clear slot") | |
| 76 | 74 | } | |
| @@ -81,14 +79,14 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||
| 81 | 79 | name: "current exit clears and stale completion is ignored", | |
| 82 | 80 | run: func(t *testing.T, tb *tab) { | |
| 83 | 81 | tb.mu.Lock() | |
| 84 | - | generation := tb.beginFloatingWarmLocked(launch, true) | |
| 82 | + | generation := tb.beginFloatingWarmLocked(true) | |
| 85 | 83 | if !tb.installFloatingLocked(first, generation) { | |
| 86 | 84 | t.Fatal("install failed") | |
| 87 | 85 | } | |
| 88 | 86 | if !tb.clearFloatingLocked(first, generation) || tb.floating.state != floatingUninitialized || tb.floating.pane != nil { | |
| 89 | 87 | t.Fatal("current exit did not clear slot") | |
| 90 | 88 | } | |
| 91 | - | newGeneration := tb.beginFloatingWarmLocked(launch, true) | |
| 89 | + | newGeneration := tb.beginFloatingWarmLocked(true) | |
| 92 | 90 | if tb.installFloatingLocked(second, generation) { | |
| 93 | 91 | t.Fatal("stale completion installed") | |
| 94 | 92 | } | |
| @@ -266,11 +264,11 @@ func TestFloatingLifecycleStaleSuccessAndOldExitCannotReplaceCurrentSlot(t *test | |||
| 266 | 264 | first := &pane{} | |
| 267 | 265 | second := &pane{} | |
| 268 | 266 | tb.mu.Lock() | |
| 269 | - | g1 := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | |
| 267 | + | g1 := tb.beginFloatingWarmLocked(true) | |
| 270 | 268 | require.True(t, tb.installFloatingLocked(first, g1)) | |
| 271 | 269 | g2 := tb.clearFloatingLocked(first, g1) | |
| 272 | 270 | require.True(t, g2) | |
| 273 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | |
| 271 | + | generation := tb.beginFloatingWarmLocked(true) | |
| 274 | 272 | require.True(t, tb.installFloatingLocked(second, generation)) | |
| 275 | 273 | tb.mu.Unlock() | |
| 276 | 274 | d.reapFloating(sess, tb, first, g1) | |
| @@ -284,7 +282,7 @@ func TestFloatingReapAndTeardownCloseMatchingPaneOnce(t *testing.T) { | |||
| 284 | 282 | tb := newFloatingTestTab(t) | |
| 285 | 283 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | |
| 286 | 284 | tb.mu.Lock() | |
| 287 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | |
| 285 | + | g := tb.beginFloatingWarmLocked(true) | |
| 288 | 286 | require.True(t, tb.installFloatingLocked(p, g)) | |
| 289 | 287 | tb.mu.Unlock() | |
| 290 | 288 | d := newTestDaemon(t, nil, stubClock{}) | |
| @@ -298,7 +296,7 @@ func TestFloatingTeardownClosesInstalledPaneOnce(t *testing.T) { | |||
| 298 | 296 | tb := newFloatingTestTab(t) | |
| 299 | 297 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | |
| 300 | 298 | tb.mu.Lock() | |
| 301 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, false) | |
| 299 | + | g := tb.beginFloatingWarmLocked(false) | |
| 302 | 300 | require.True(t, tb.installFloatingLocked(p, g)) | |
| 303 | 301 | tb.mu.Unlock() | |
| 304 | 302 | d := newTestDaemon(t, nil, stubClock{}) | |
| @@ -328,7 +326,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||
| 328 | 326 | return 0, io.EOF | |
| 329 | 327 | }).Once() | |
| 330 | 328 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | |
| 331 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, state == floatingVisible) | |
| 329 | + | generation := tb.beginFloatingWarmLocked(state == floatingVisible) | |
| 332 | 330 | require.True(t, tb.installFloatingLocked(floating, generation)) | |
| 333 | 331 | if state == floatingHidden { | |
| 334 | 332 | require.Equal(t, floatingHidden, tb.floating.state) | |
| @@ -395,7 +393,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||
| 395 | 393 | generation := tb.floating.generation | |
| 396 | 394 | require.True(t, tb.clearFloatingLocked(stale, generation)) | |
| 397 | 395 | current := newPane(layout.PaneID("replacement"), newBlockingPanePTY(t), domain.Size{Cols: 20, Rows: 8}) | |
| 398 | - | currentGeneration := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | |
| 396 | + | currentGeneration := tb.beginFloatingWarmLocked(true) | |
| 399 | 397 | require.True(t, tb.installFloatingLocked(current, currentGeneration)) | |
| 400 | 398 | tb.mu.Unlock() | |
| 401 | 399 | exit() | |
| @@ -530,7 +528,7 @@ func TestFloatingToggleUsesVisibleTarget(t *testing.T) { | |||
| 530 | 528 | tb := newFloatingTestTab(t) | |
| 531 | 529 | sess := &session{tabs: []*tab{tb}, ctx: t.Context()} | |
| 532 | 530 | tb.mu.Lock() | |
| 533 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, false) | |
| 531 | + | g := tb.beginFloatingWarmLocked(false) | |
| 534 | 532 | floating := &pane{} | |
| 535 | 533 | require.True(t, tb.installFloatingLocked(floating, g)) | |
| 536 | 534 | tb.mu.Unlock() | |
| @@ -22,8 +22,6 @@ import ( | |||||
| 22 | func TestFloatingSlotTransitions(t *testing.T) { | 22 | func TestFloatingSlotTransitions(t *testing.T) { | ||
| 23 | first := &pane{} | 23 | first := &pane{} | ||
| 24 | second := &pane{} | 24 | second := &pane{} | ||
| 25 | - | launch := domain.FloatingConfig{Command: "top", Width: 80, Height: 80} | |||
| 26 | - | ||||
| 27 | tests := []struct { | 25 | tests := []struct { | ||
| 28 | name string | 26 | name string | ||
| 29 | run func(t *testing.T, tb *tab) | 27 | run func(t *testing.T, tb *tab) | ||
| @@ -32,12 +30,12 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||||
| 32 | name: "warming double toggle retains hidden completion", | 30 | name: "warming double toggle retains hidden completion", | ||
| 33 | run: func(t *testing.T, tb *tab) { | 31 | run: func(t *testing.T, tb *tab) { | ||
| 34 | tb.mu.Lock() | 32 | tb.mu.Lock() | ||
| 35 | - | generation := tb.beginFloatingWarmLocked(launch, false) | 33 | + | generation := tb.beginFloatingWarmLocked(false) |
| 36 | - | start, gotGeneration := tb.toggleFloatingLocked(launch) | 34 | + | start, gotGeneration := tb.toggleFloatingLocked() |
| 37 | if start || gotGeneration != generation || !tb.floating.desiredVisible { | 35 | if start || gotGeneration != generation || !tb.floating.desiredVisible { | ||
| 38 | t.Fatalf("first warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | 36 | t.Fatalf("first warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | ||
| 39 | } | 37 | } | ||
| 40 | - | start, gotGeneration = tb.toggleFloatingLocked(launch) | 38 | + | start, gotGeneration = tb.toggleFloatingLocked() |
| 41 | if start || gotGeneration != generation || tb.floating.desiredVisible { | 39 | if start || gotGeneration != generation || tb.floating.desiredVisible { | ||
| 42 | t.Fatalf("second warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | 40 | t.Fatalf("second warming toggle = start %v, generation %d, desired %v", start, gotGeneration, tb.floating.desiredVisible) | ||
| 43 | } | 41 | } | ||
| @@ -51,15 +49,15 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||||
| 51 | name: "open and hide retain same pane", | 49 | name: "open and hide retain same pane", | ||
| 52 | run: func(t *testing.T, tb *tab) { | 50 | run: func(t *testing.T, tb *tab) { | ||
| 53 | tb.mu.Lock() | 51 | tb.mu.Lock() | ||
| 54 | - | generation := tb.beginFloatingWarmLocked(launch, false) | 52 | + | generation := tb.beginFloatingWarmLocked(false) |
| 55 | if !tb.installFloatingLocked(first, generation) { | 53 | if !tb.installFloatingLocked(first, generation) { | ||
| 56 | t.Fatal("install failed") | 54 | t.Fatal("install failed") | ||
| 57 | } | 55 | } | ||
| 58 | - | start, _ := tb.toggleFloatingLocked(launch) | 56 | + | start, _ := tb.toggleFloatingLocked() |
| 59 | if start || tb.floating.state != floatingVisible || tb.floating.pane != first { | 57 | if start || tb.floating.state != floatingVisible || tb.floating.pane != first { | ||
| 60 | t.Fatal("hidden open did not retain pane") | 58 | t.Fatal("hidden open did not retain pane") | ||
| 61 | } | 59 | } | ||
| 62 | - | tb.toggleFloatingLocked(launch) | 60 | + | tb.toggleFloatingLocked() |
| 63 | if tb.floating.state != floatingHidden || tb.floating.pane != first { | 61 | if tb.floating.state != floatingHidden || tb.floating.pane != first { | ||
| 64 | t.Fatal("visible hide did not retain pane") | 62 | t.Fatal("visible hide did not retain pane") | ||
| 65 | } | 63 | } | ||
| @@ -70,7 +68,7 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||||
| 70 | name: "current launch failure clears desired visibility", | 68 | name: "current launch failure clears desired visibility", | ||
| 71 | run: func(t *testing.T, tb *tab) { | 69 | run: func(t *testing.T, tb *tab) { | ||
| 72 | tb.mu.Lock() | 70 | tb.mu.Lock() | ||
| 73 | - | generation := tb.beginFloatingWarmLocked(launch, true) | 71 | + | generation := tb.beginFloatingWarmLocked(true) |
| 74 | if !tb.failFloatingLocked(generation) || tb.floating.state != floatingUninitialized || tb.floating.desiredVisible || tb.floating.pane != nil { | 72 | if !tb.failFloatingLocked(generation) || tb.floating.state != floatingUninitialized || tb.floating.desiredVisible || tb.floating.pane != nil { | ||
| 75 | t.Fatal("current launch failure did not clear slot") | 73 | t.Fatal("current launch failure did not clear slot") | ||
| 76 | } | 74 | } | ||
| @@ -81,14 +79,14 @@ func TestFloatingSlotTransitions(t *testing.T) { | |||||
| 81 | name: "current exit clears and stale completion is ignored", | 79 | name: "current exit clears and stale completion is ignored", | ||
| 82 | run: func(t *testing.T, tb *tab) { | 80 | run: func(t *testing.T, tb *tab) { | ||
| 83 | tb.mu.Lock() | 81 | tb.mu.Lock() | ||
| 84 | - | generation := tb.beginFloatingWarmLocked(launch, true) | 82 | + | generation := tb.beginFloatingWarmLocked(true) |
| 85 | if !tb.installFloatingLocked(first, generation) { | 83 | if !tb.installFloatingLocked(first, generation) { | ||
| 86 | t.Fatal("install failed") | 84 | t.Fatal("install failed") | ||
| 87 | } | 85 | } | ||
| 88 | if !tb.clearFloatingLocked(first, generation) || tb.floating.state != floatingUninitialized || tb.floating.pane != nil { | 86 | if !tb.clearFloatingLocked(first, generation) || tb.floating.state != floatingUninitialized || tb.floating.pane != nil { | ||
| 89 | t.Fatal("current exit did not clear slot") | 87 | t.Fatal("current exit did not clear slot") | ||
| 90 | } | 88 | } | ||
| 91 | - | newGeneration := tb.beginFloatingWarmLocked(launch, true) | 89 | + | newGeneration := tb.beginFloatingWarmLocked(true) |
| 92 | if tb.installFloatingLocked(second, generation) { | 90 | if tb.installFloatingLocked(second, generation) { | ||
| 93 | t.Fatal("stale completion installed") | 91 | t.Fatal("stale completion installed") | ||
| 94 | } | 92 | } | ||
| @@ -266,11 +264,11 @@ func TestFloatingLifecycleStaleSuccessAndOldExitCannotReplaceCurrentSlot(t *test | |||||
| 266 | first := &pane{} | 264 | first := &pane{} | ||
| 267 | second := &pane{} | 265 | second := &pane{} | ||
| 268 | tb.mu.Lock() | 266 | tb.mu.Lock() | ||
| 269 | - | g1 := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | 267 | + | g1 := tb.beginFloatingWarmLocked(true) |
| 270 | require.True(t, tb.installFloatingLocked(first, g1)) | 268 | require.True(t, tb.installFloatingLocked(first, g1)) | ||
| 271 | g2 := tb.clearFloatingLocked(first, g1) | 269 | g2 := tb.clearFloatingLocked(first, g1) | ||
| 272 | require.True(t, g2) | 270 | require.True(t, g2) | ||
| 273 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | 271 | + | generation := tb.beginFloatingWarmLocked(true) |
| 274 | require.True(t, tb.installFloatingLocked(second, generation)) | 272 | require.True(t, tb.installFloatingLocked(second, generation)) | ||
| 275 | tb.mu.Unlock() | 273 | tb.mu.Unlock() | ||
| 276 | d.reapFloating(sess, tb, first, g1) | 274 | d.reapFloating(sess, tb, first, g1) | ||
| @@ -284,7 +282,7 @@ func TestFloatingReapAndTeardownCloseMatchingPaneOnce(t *testing.T) { | |||||
| 284 | tb := newFloatingTestTab(t) | 282 | tb := newFloatingTestTab(t) | ||
| 285 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | 283 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | ||
| 286 | tb.mu.Lock() | 284 | tb.mu.Lock() | ||
| 287 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | 285 | + | g := tb.beginFloatingWarmLocked(true) |
| 288 | require.True(t, tb.installFloatingLocked(p, g)) | 286 | require.True(t, tb.installFloatingLocked(p, g)) | ||
| 289 | tb.mu.Unlock() | 287 | tb.mu.Unlock() | ||
| 290 | d := newTestDaemon(t, nil, stubClock{}) | 288 | d := newTestDaemon(t, nil, stubClock{}) | ||
| @@ -298,7 +296,7 @@ func TestFloatingTeardownClosesInstalledPaneOnce(t *testing.T) { | |||||
| 298 | tb := newFloatingTestTab(t) | 296 | tb := newFloatingTestTab(t) | ||
| 299 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | 297 | p := newPane(layout.PaneID("floating"), pty, domain.Size{Cols: 10, Rows: 10}) | ||
| 300 | tb.mu.Lock() | 298 | tb.mu.Lock() | ||
| 301 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, false) | 299 | + | g := tb.beginFloatingWarmLocked(false) |
| 302 | require.True(t, tb.installFloatingLocked(p, g)) | 300 | require.True(t, tb.installFloatingLocked(p, g)) | ||
| 303 | tb.mu.Unlock() | 301 | tb.mu.Unlock() | ||
| 304 | d := newTestDaemon(t, nil, stubClock{}) | 302 | d := newTestDaemon(t, nil, stubClock{}) | ||
| @@ -328,7 +326,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||||
| 328 | return 0, io.EOF | 326 | return 0, io.EOF | ||
| 329 | }).Once() | 327 | }).Once() | ||
| 330 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | 328 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | ||
| 331 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, state == floatingVisible) | 329 | + | generation := tb.beginFloatingWarmLocked(state == floatingVisible) |
| 332 | require.True(t, tb.installFloatingLocked(floating, generation)) | 330 | require.True(t, tb.installFloatingLocked(floating, generation)) | ||
| 333 | if state == floatingHidden { | 331 | if state == floatingHidden { | ||
| 334 | require.Equal(t, floatingHidden, tb.floating.state) | 332 | require.Equal(t, floatingHidden, tb.floating.state) | ||
| @@ -395,7 +393,7 @@ func TestFloatingEOFRepaintsVisibleSlotOnly(t *testing.T) { | |||||
| 395 | generation := tb.floating.generation | 393 | generation := tb.floating.generation | ||
| 396 | require.True(t, tb.clearFloatingLocked(stale, generation)) | 394 | require.True(t, tb.clearFloatingLocked(stale, generation)) | ||
| 397 | current := newPane(layout.PaneID("replacement"), newBlockingPanePTY(t), domain.Size{Cols: 20, Rows: 8}) | 395 | current := newPane(layout.PaneID("replacement"), newBlockingPanePTY(t), domain.Size{Cols: 20, Rows: 8}) | ||
| 398 | - | currentGeneration := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, true) | 396 | + | currentGeneration := tb.beginFloatingWarmLocked(true) |
| 399 | require.True(t, tb.installFloatingLocked(current, currentGeneration)) | 397 | require.True(t, tb.installFloatingLocked(current, currentGeneration)) | ||
| 400 | tb.mu.Unlock() | 398 | tb.mu.Unlock() | ||
| 401 | exit() | 399 | exit() | ||
| @@ -530,7 +528,7 @@ func TestFloatingToggleUsesVisibleTarget(t *testing.T) { | |||||
| 530 | tb := newFloatingTestTab(t) | 528 | tb := newFloatingTestTab(t) | ||
| 531 | sess := &session{tabs: []*tab{tb}, ctx: t.Context()} | 529 | sess := &session{tabs: []*tab{tb}, ctx: t.Context()} | ||
| 532 | tb.mu.Lock() | 530 | tb.mu.Lock() | ||
| 533 | - | g := tb.beginFloatingWarmLocked(domain.FloatingConfig{}, false) | 531 | + | g := tb.beginFloatingWarmLocked(false) |
| 534 | floating := &pane{} | 532 | floating := &pane{} | ||
| 535 | require.True(t, tb.installFloatingLocked(floating, g)) | 533 | require.True(t, tb.installFloatingLocked(floating, g)) | ||
| 536 | tb.mu.Unlock() | 534 | tb.mu.Unlock() | ||
M internal/usecase/daemon/snapshot_test.go +1 -1
| @@ -72,7 +72,7 @@ func TestSnapshotCaptureExcludesFloatingRuntime(t *testing.T) { | |||
| 72 | 72 | floatingPTY, _ := newBlockingPTY(t) | |
| 73 | 73 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | |
| 74 | 74 | tb.mu.Lock() | |
| 75 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{Command: "top"}, false) | |
| 75 | + | generation := tb.beginFloatingWarmLocked(false) | |
| 76 | 76 | require.True(t, tb.installFloatingLocked(floating, generation)) | |
| 77 | 77 | tb.mu.Unlock() | |
| 78 | 78 | ||
| @@ -72,7 +72,7 @@ func TestSnapshotCaptureExcludesFloatingRuntime(t *testing.T) { | |||||
| 72 | floatingPTY, _ := newBlockingPTY(t) | 72 | floatingPTY, _ := newBlockingPTY(t) | ||
| 73 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | 73 | floating := newPane(layout.PaneID("floating"), floatingPTY, domain.Size{Cols: 20, Rows: 8}) | ||
| 74 | tb.mu.Lock() | 74 | tb.mu.Lock() | ||
| 75 | - | generation := tb.beginFloatingWarmLocked(domain.FloatingConfig{Command: "top"}, false) | 75 | + | generation := tb.beginFloatingWarmLocked(false) |
| 76 | require.True(t, tb.installFloatingLocked(floating, generation)) | 76 | require.True(t, tb.installFloatingLocked(floating, generation)) | ||
| 77 | tb.mu.Unlock() | 77 | tb.mu.Unlock() | ||
| 78 | 78 | ||||