You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Port marquee selection to iOS: `IOSElementSource` now conforms to
`MarqueeTargetSource`, walking the UIView tree once from a single window
root to build `[MarqueeCandidate]` and resolving with the shared, pure
`MarqueeTargetRule` — so an identical drag over an identical layout
resolves identically on both platforms by construction, not by two
implementations being kept in step by hand. Returns the same target-first,
broadest-last ladder `componentLadder(at:)` produces, so the session's
widening and the note's `component` field work unchanged.
Also excludes AnnotKit's own overlay from `IOSElementSource.windows()` by
`PassThroughWindow` TYPE identity. Unlike macOS (a separate NSPanel already
unreachable via kAXWindows), the iOS overlay is a UIWindow in the HOST's
scene sharing its pid, and its chrome is genuinely identified and
meaningful — a marquee spans the area it draws across, so a large overlay
surface could win the rule's first pass outright and bind the user's note
to our own UI. Filtering in the shared helper makes snapshot, hitTest,
keyWindow, componentLadder and the marquee path agree.
Verified: `xcrun --sdk iphoneos swiftc -typecheck -target arm64-apple-ios17.0
-swift-version 6` clean (no warnings), and a Mac Catalyst build (the os(iOS)
path) succeeds. macOS unaffected: 99 tests green, AnnotKitOverlayProbe
all-PASS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: PARITY.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,9 @@ row; each asymmetry is closed by code or has a tracked mitigation.
13
13
| Hit test primitive |`AXUIElementCopyElementAtPosition` + NSView `hitTest`|`UIView.hitTest(_:with:)`| iOS has no global AX point query; uses view hitTest. Tracked: F5.2 |
14
14
| Annotation target rule | shared `AnnotationTargetRule` over an AX candidate chain | shared `AnnotationTargetRule` over a UIView candidate chain | none — both build a `[TargetCandidate]` chain and apply the SAME rule (deepest actionable, else deepest meaningful). Closes the earlier split (macOS "deepest meaningful" vs iOS "nearest identified"), cli-got28.2 |
15
15
| Component widening |`ComponentLadderSource` (AX chain) |`ComponentLadderSource` (UIView chain) | none — same ladder (target, then enclosing identified components) |
16
+
| Marquee selection (drawn frame → element) |`MarqueeTargetSource`: shared `MarqueeTargetRule` over `[MarqueeCandidate]` read from the AX tree |`MarqueeTargetSource`: shared `MarqueeTargetRule` over `[MarqueeCandidate]` read from the UIView tree | none — the DECISION is one pure rule (largest ≥85%-surrounded element, else the tightest enclosing one); the adapters differ only in how they read candidates out of their own tree. Both do ONE walk from a single root so depth (the rule's tie-break) is numbered comparably, both collect the subtree WHOLE (an intersects-the-frame filter would discard the enclosing-pass candidates), and both return the SAME target-first, broadest-last ladder as `ComponentLadderSource`, so the session's widening and `component` field work unchanged from a framed selection |
17
+
| Marquee drag threshold | cursor slop (a mouse does not move on a deliberate click) | larger touch slop | ASYMMETRIC BY DESIGN, owned by the drag UI, not the adapters: a finger rolls several points on a deliberate tap, so the macOS threshold on iOS would turn taps into marquees. Below the threshold both platforms route the gesture to the point path (`select(atAXPoint:)`), per the caller contract on `select(inAXRect:)`|
18
+
| Overlay excluded from element lookup | AX window identifier (`AXIntrospection.overlayWindowIdentifier`) filtered out of every `kAXWindows` read |`PassThroughWindow` TYPE identity filtered out of `IOSElementSource.windows()`| ASYMMETRIC BY NECESSITY — the hosts are different window kinds. macOS's overlay is a separate `NSPanel` matched by the identifier the controller stamps on it; iOS's is a `UIWindow` in the HOST's scene sharing its pid, so no pid/scene filter separates it and a type check (internal to the module) cannot drift the way an identifier convention can. Both filter in the shared window lookup, so snapshot / hit-test / region-anchor / marquee agree; leaving it in would let a marquee bind the user's note to AnnotKit's own UI |
16
19
| Coordinate space | Cocoa bottom-left to AX top-left flip | UIKit top-left native | iOS needs no flip; shared `ScreenSpace` used only on macOS |
17
20
| Screenshot | ScreenCaptureKit / `cacheDisplay`|`UIGraphicsImageRenderer` + `drawHierarchy`| both capture own hierarchy only; no cross-window or secure overlays |
18
21
| Overlay host | resizing `NSPanel` (toolbar corner idle, full screen annotating) | pass-through `UIWindow`| both interactive; selection via the shared SwiftUI catcher, not a global monitor |
0 commit comments