"Every click worked. Only drags were dead, and the window moved under the pointer."
Why the Big Mac Apps Stay in AppKit
The family's editor, terminal and file workbench are AppKit-owned for concrete reasons, not nostalgia. The text system (NSTextView and TextKit) carries input methods, marked-text composition, spell checking, services and accessibility that a from-scratch text surface does not. Split views, custom tab strips and title-bar accessories need control over layout and mouse tracking that SwiftUI does not expose. And drag and drop between apps is decided by flags on AppKit objects. The price is that AppKit's power comes with its own sharp edges. These are the ones that cost the family real sessions.
Mouse Tracking
- A view in the title bar never sees
mouseDragged. A tab strip placed in a title-bar accessory handled clicks, double-clicks and right-clicks, but drag-to-reorder never fired — the whole window slid instead. AnNSViewanswersmouseDownCanMoveWindowwith!isOpaque, so a plain transparent view says yes and the window move takes the event stream. Override it to returnfalse, and callwindow.performDrag(with:)yourself on empty strip space to keep the title-bar feel. (Buttons never show this:NSControlalready returnsfalse.) - Rebuilding views mid-drag ends the drag. Reordering tabs by notifying the model on every swap made the parent rebuild the strip's arranged subviews, which removed the tracked view and silently ended its
mouseDraggedandmouseUpstream. Mutate only the strip's own order during the drag, and commit one model change on mouse-up. - Dragging files to other apps needs a non-local mask. Rows could be dropped inside the workbench but were dead over Finder and other apps. Table and collection views default to an empty source operation mask for non-local drags; one call per view fixes it.
Split Views
Nested NSSplitViews were the single largest source of layout bugs. A split whose parent sets its frame by hand does not get the autolayout-driven resize a normal split does, and a one-shot setPosition at the first non-zero height left one half filling the panel and the other invisible. Place the divider in layout() whenever the tracked height changes, record the user's fraction when they drag, and guard the programmatic call so it does not feed back. Give arranged subviews non-zero frames before activating constraints, verify during a real window resize rather than at launch, and check the unified log: a recovered constraint conflict is invisible on screen.
Windows and Tabs
Native window tabs make every tab its own NSWindow, cannot be coloured per tab, and stretch a tab accessory view to full width unless it pins its own size constraints. The terminal disallowed native tabbing and drew its own strip. A tab added to a group must join it (addTabbedWindow(_:ordered:)) before any code snapshots windows by tab group, or it is saved as a separate window.