Skip to content

ADR-0049: Native map input ownership ​

Status: Accepted · 2026-09-06 · Refines ADR-0047 and ADR-0048

Context ​

Click-to-activate combined SwiftUI tap gestures and keyboard focus with a separate native event monitor. Blank viewport hit testing and focus transitions could prevent activation or clear it immediately. Custom Records/Map buttons also lacked an explicit full-segment hit area, making clicks beside their text unreliable.

Decision ​

One native NSView responder owns the graph's pointer, trackpad, and keyboard input. Clicking activates it and makes it first responder. Pointer exit, first-responder resignation, scene deactivation, or view removal releases it. Inactive scroll events pass up the responder chain; active scroll, drag, pinch, and right-drag update the existing camera. There are no app-wide event monitors and no competing SwiftUI focus or gesture state.

Pointer selection and hovering use the same projected coordinates as rendering and choose the nearest-depth node when targets overlap. Native input receives drags even when they start on a node. SwiftUI node buttons remain accessibility actions without competing for pointer events. The active-state badge remains; bottom gesture instructions are removed. The type legend and graph counts remain visible.

Each Records/Map/Connections button declares a rectangular hit area inside its full-width label, with a 28-point minimum height. Selection remains local and does not trigger network loading.

Consequences ​

  • Input ownership has one lifecycle and the inactive graph cannot consume page scrolling.
  • Tests exercise actual native responder methods for click activation, scroll forwarding, exit, resignation, selection, and reactivation. Live UI checks remain necessary for host hit testing.
  • The same behavior applies to Pen, Settings, and expanded graphs without new dependencies.

Alternatives considered ​

More flags or delayed retries around SwiftUI focus would preserve competing state owners. An always-active wheel monitor would restore interaction by breaking page scrolling again.