Background
Original UX gap: in a split-pane layout, the user has to click the
tab strip at the top of a pane to make that Document active.
Clicking the terminal body itself does nothing — the win32 console
control consumes the mouse event before WPF sees it.
What was tried (commit 6143c60 → reverted in )
terminal.GotFocus += (_, _) => { if (!tab.Document.IsActive) tab.Document.IsActive = true; }
Why it failed
Setting IsActive from inside GotFocus puts the AvalonDock active-
document state into a feedback loop:
- User clicks terminal A → win32 child takes keyboard focus
- Routed
GotFocus bubbles to the WPF tree
- Handler sets
tab.Document.IsActive = true for A
- AvalonDock pushes focus around to enforce the new active state,
which moves keyboard focus and fires GotFocus on terminal B
- B's handler sets B's Document active
- Step 4 again, but in the other direction
Effective result: the active highlight ricochets between panes at
win32 input speeds and no input lands anywhere. Severe UX freeze
reported within seconds of the build going live.
The !IsActive guard didn't break the loop because each cycle
does legitimately change IsActive — the guard only stops a true
no-op rebind, not the ping-pong.
Candidates for a future re-attempt
- One-shot Win32 hook on
WM_LBUTTONDOWN in EasyTerminalControl's
HWND. Fires exactly once per click, no focus-loop entry. Requires
picking up the underlying HWND after ConPTYTerm is bound and
unsubscribing on tab close.
Dispatcher.BeginInvoke debounce so the IsActive write happens
after the focus-traffic settles. May still loop if AvalonDock's
focus push is itself dispatched.
- AvalonDock
dockManager.Layout.RootDocument / ActivateDocument
may have a path that doesn't drive focus. Needs library reading.
- Track who initiated the focus (mouse vs programmatic) so the
handler only acts on user gestures.
Each candidate needs an empirical test in a 2-pane layout before
landing.
Acceptance test
- Build a 2-pane split with terminal A on the left, terminal B on the right
- Click terminal A's body (not its tab strip) → A's pane becomes active, no flicker
- Type into A → keystrokes land in A
- Click terminal B's body → B's pane becomes active, no flicker
- Type into B → keystrokes land in B
- Repeat the click-and-type cycle 10×; no observable lag, no active-highlight ricochet
Closes-when
Background
Original UX gap: in a split-pane layout, the user has to click the
tab strip at the top of a pane to make that Document active.
Clicking the terminal body itself does nothing — the win32 console
control consumes the mouse event before WPF sees it.
What was tried (commit 6143c60 → reverted in )
terminal.GotFocus += (_, _) => { if (!tab.Document.IsActive) tab.Document.IsActive = true; }Why it failed
Setting
IsActivefrom insideGotFocusputs the AvalonDock active-document state into a feedback loop:
GotFocusbubbles to the WPF treetab.Document.IsActive = truefor Awhich moves keyboard focus and fires
GotFocuson terminal BEffective result: the active highlight ricochets between panes at
win32 input speeds and no input lands anywhere. Severe UX freeze
reported within seconds of the build going live.
The
!IsActiveguard didn't break the loop because each cycledoes legitimately change
IsActive— the guard only stops a trueno-op rebind, not the ping-pong.
Candidates for a future re-attempt
WM_LBUTTONDOWNinEasyTerminalControl'sHWND. Fires exactly once per click, no focus-loop entry. Requires
picking up the underlying HWND after
ConPTYTermis bound andunsubscribing on tab close.
Dispatcher.BeginInvokedebounce so the IsActive write happensafter the focus-traffic settles. May still loop if AvalonDock's
focus push is itself dispatched.
dockManager.Layout.RootDocument/ActivateDocumentmay have a path that doesn't drive focus. Needs library reading.
handler only acts on user gestures.
Each candidate needs an empirical test in a 2-pane layout before
landing.
Acceptance test
Closes-when