Reduce typing lag from sidebar re-evaluation and hitTest overhead (#1204)

* Add typing hot path timing diagnostics

* Add stress workspace debug menu item

* Restore stress workspace preload debug path

* Reduce typing lag from sidebar re-evaluation and hitTest overhead

hitTest: gate divider/sidebar/drag routing to pointer events only,
avoiding two full view-tree walks per non-pointer event.

forceRefresh: replace per-keystroke ISO8601DateFormatter + FileHandle
I/O with dlog() in DEBUG builds.

TabItemView: replace @EnvironmentObject subscriptions with plain refs
and precomputed parameters, add Equatable conformance to skip body
re-evaluation when parent rebuilds with unchanged values. @self changed
re-evaluations dropped from 668 to 1 during rapid typing.

* Add typing-latency guardrail comments and CLAUDE.md pitfalls

Strategic comments on hitTest, TabItemView, and forceRefresh to prevent
future regressions. Adds typing-latency-sensitive paths to CLAUDE.md
pitfalls section so agents know the constraints before editing.

* Add workspace palette actions and fix release autosave typing guard

Add Move Up/Down/Top, Close Other/Above/Below, Mark Read/Unread to
Cmd+Shift+P command palette and a Workspace submenu in the menu bar.

Fix recordTypingActivity() being gated behind #if DEBUG, which prevented
release builds from honoring the typing quiet period in autosave.
This commit is contained in:
Lawrence Chen 2026-03-11 17:54:02 -07:00 committed by GitHub
parent 18bdbef882
commit 6849b83f8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1863 additions and 151 deletions

View file

@ -129,44 +129,69 @@ final class WindowTerminalHostView: NSView {
clearActiveDividerCursor(restoreArrow: true)
}
// PERF: hitTest is called on EVERY event including keyboard. Keep non-pointer
// path minimal. Do not add work outside the isPointerEvent guard.
override func hitTest(_ point: NSPoint) -> NSView? {
updateDividerCursor(at: point)
if shouldPassThroughToSidebarResizer(at: point) {
return nil
let currentEvent = NSApp.currentEvent
let isPointerEvent: Bool
switch currentEvent?.type {
case .mouseMoved, .mouseEntered, .mouseExited,
.leftMouseDown, .leftMouseUp, .leftMouseDragged,
.rightMouseDown, .rightMouseUp, .rightMouseDragged,
.otherMouseDown, .otherMouseUp, .otherMouseDragged,
.scrollWheel, .cursorUpdate:
isPointerEvent = true
default:
isPointerEvent = false
}
if shouldPassThroughToSplitDivider(at: point) {
return nil
}
if isPointerEvent {
if shouldPassThroughToSidebarResizer(at: point) {
clearActiveDividerCursor(restoreArrow: false)
return nil
}
let dragPasteboardTypes = NSPasteboard(name: .drag).types
let eventType = NSApp.currentEvent?.type
let shouldPassThrough = DragOverlayRoutingPolicy.shouldPassThroughPortalHitTesting(
pasteboardTypes: dragPasteboardTypes,
eventType: eventType
)
if shouldPassThrough {
// Compute divider hit once and reuse for both cursor update and pass-through.
if let kind = splitDividerCursorKind(at: point) {
activeDividerCursorKind = kind
kind.cursor.set()
return nil
}
clearActiveDividerCursor(restoreArrow: true)
let dragPasteboardTypes = NSPasteboard(name: .drag).types
let eventType = currentEvent?.type
let shouldPassThrough = DragOverlayRoutingPolicy.shouldPassThroughPortalHitTesting(
pasteboardTypes: dragPasteboardTypes,
eventType: eventType
)
if shouldPassThrough {
#if DEBUG
logDragRouteDecision(
passThrough: true,
eventType: eventType,
pasteboardTypes: dragPasteboardTypes,
hitView: nil
)
#endif
return nil
}
let hitView = super.hitTest(point)
#if DEBUG
logDragRouteDecision(
passThrough: true,
eventType: eventType,
passThrough: false,
eventType: currentEvent?.type,
pasteboardTypes: dragPasteboardTypes,
hitView: nil
hitView: hitView
)
#endif
return nil
return hitView === self ? nil : hitView
}
// Non-pointer event: skip divider/drag routing, just do standard hit testing.
let hitView = super.hitTest(point)
#if DEBUG
logDragRouteDecision(
passThrough: false,
eventType: eventType,
pasteboardTypes: dragPasteboardTypes,
hitView: hitView
)
#endif
return hitView === self ? nil : hitView
}