ReviewOS

also looking at this

craft-native/craft

fix(native): interaction, audio, document and checkpoint gaps stranded after #401

#415
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
19 files +987 -50

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/ios/templates/CraftNativeScreen.swiftmodified+32-0
Changes to packages/ios/templates/CraftNativeScreen.swift
@@ -4295,6 +4295,36 @@ final class CraftMapTapDelegate: NSObject, UIGestureRecognizerDelegate {
42954295 }
42964296}
42974297
4298/// A pressable wrapper must yield to an actionable descendant. Without this
4299/// ordering UIKit may let the ancestor's tap recognizer win the same touch,
4300/// so a nested Button never receives its physical action. A descendant
4301/// control or tap recognizer is the source of the event; STX handles the
4302/// logical bubbling from there.
4303final class CraftPressGestureDelegate: NSObject, UIGestureRecognizerDelegate {
4304 static let shared = CraftPressGestureDelegate()
4305
4306 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
4307 guard let parent = gestureRecognizer.view,
4308 let child = otherGestureRecognizer.view,
4309 child !== parent else { return false }
4310 return child.isDescendant(of: parent)
4311 }
4312
4313 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
4314 guard let container = gestureRecognizer.view else { return true }
4315 let hitView = container.hitTest(touch.location(in: container), with: nil)
4316 guard var candidate = hitView ?? touch.view else { return true }
4317 while candidate !== container {
4318 if candidate is UIControl { return false }
4319 if candidate.gestureRecognizers?.contains(where: { $0 !== gestureRecognizer && $0 is UITapGestureRecognizer }) == true {
4320 return false
4321 }
4322 candidate = candidate.superview ?? container
4323 }
4324 return true
4325 }
4326}
4327
42984328/// The delegate of a Pressable's press-in/out feedback. That recognizer is
42994329/// a long press of no duration, so it begins the moment a finger lands; on its
43004330/// own it would claim the touch, and the tap that is the Pressable's onPress
@@ -9148,6 +9178,8 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
91489178 let actionable = handler != nil || linkTargets[id] != nil
91499179 if actionable, tapRecognizers[id] == nil {
91509180 let recognizer = UITapGestureRecognizer(target: self, action: #selector(viewPressed(_:)))
9181 recognizer.cancelsTouchesInView = false
9182 recognizer.delegate = CraftPressGestureDelegate.shared
91519183 tapRecognizers[id] = recognizer
91529184 view.addGestureRecognizer(recognizer)
91539185 } else if !actionable, let recognizer = tapRecognizers.removeValue(forKey: id) {