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:
parent
18bdbef882
commit
6849b83f8d
11 changed files with 1863 additions and 151 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue