ReviewOS

also looking at this

craft-native/craft

fix(native): retain keyed controls and correct map and tab chrome

#368
Merged chris wants to merge codex/native-repeated-selector-identity into main
32 files +449 -100

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/fixtures/native-navigation/NativeNavigationUnitTests.swiftmodified+18-0
Changes to packages/ios/fixtures/native-navigation/NativeNavigationUnitTests.swift
@@ -419,6 +419,24 @@ final class NativeNavigationUnitTests: XCTestCase {
419419 XCTAssertNil(CraftChrome.shared.tabAccessory)
420420 }
421421
422 func testNativePushKeepsItsAccessoryAfterTabPreparation() async throws {
423 await displayWait("the Details tab is prepared") { [self] in stack("/details") != nil }
424 let home = try XCTUnwrap(stack("/"))
425 let root = try XCTUnwrap(home.topViewController as? CraftNativeScreenController)
426 func button(in view: UIView) -> UIButton? {
427 if view.accessibilityIdentifier == "navigation-open-details" { return view as? UIButton }
428 return view.subviews.lazy.compactMap { button(in: $0) }.first
429 }
430 let open = try XCTUnwrap(button(in: root.view))
431 open.sendActions(for: .touchUpInside)
432 await displayWait("the compiled button pushes Details") {
433 home.topViewController !== root && home.transitionCoordinator == nil
434 }
435 await renderFrames()
436 XCTAssertEqual(CraftChrome.shared.selected, "/")
437 XCTAssertEqual(CraftChrome.shared.tabAccessory?.id, "/capabilities")
438 }
439
422440 func testDisappearingHiddenNativeTabCannotClearTheVisibleAccessory() async throws {
423441 shell.open("/linked-details")
424442 let home = try XCTUnwrap(stack("/"))
packages/ios/templates/CraftApp.swiftmodified+4-0
Changes to packages/ios/templates/CraftApp.swift
@@ -783,6 +783,10 @@ final class CraftChrome: ObservableObject {
783783 } else {
784784 next = nil
785785 }
786 #if DEBUG
787 NSLog("[craft chrome trace] accessory present=%d owner=%@", next != nil ? 1 : 0,
788 owner.map { String(describing: ObjectIdentifier($0)) } ?? "none")
789 #endif
786790 tabAccessoryOwner = owner
787791 guard tabAccessory != next else { return }
788792 tabAccessory = next
packages/ios/templates/CraftNativeScreen.swiftmodified+11-0
Changes to packages/ios/templates/CraftNativeScreen.swift
@@ -4675,6 +4675,12 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
46754675
46764676 private func restoreShellChrome() {
46774677 guard !isAppScript else { return }
4678 #if DEBUG
4679 NSLog("[craft chrome trace] restore route=%@ controller=%@ top=%d hidden=%d accessory=%d",
4680 routeName ?? "none", String(describing: ObjectIdentifier(self)),
4681 navigationController?.topViewController === self ? 1 : 0,
4682 navigationController?.view.isHidden == true ? 1 : 0, tabAccessory != nil ? 1 : 0)
4683 #endif
46784684 CraftChrome.shared.setHiddenByScreen(tabBarHidden)
46794685 CraftChrome.shared.updateTabAccessory(tabAccessory, owner: self)
46804686 }
@@ -5346,6 +5352,11 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
53465352 guard !isAppScript else { return }
53475353 if options.keys.contains("tabAccessory") {
53485354 tabAccessory = options["tabAccessory"] as? [String: Any]
5355 #if DEBUG
5356 NSLog("[craft chrome trace] options route=%@ controller=%@ accessory=%d top=%d window=%d",
5357 routeName ?? "none", String(describing: ObjectIdentifier(self)), tabAccessory != nil ? 1 : 0,
5358 navigationController?.topViewController === self ? 1 : 0, view.window != nil ? 1 : 0)
5359 #endif
53495360 if navigationController?.topViewController === self, view.window != nil {
53505361 CraftChrome.shared.updateTabAccessory(tabAccessory, owner: self)
53515362 }