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+38-0
Changes to packages/ios/fixtures/native-navigation/NativeNavigationUnitTests.swift
@@ -194,6 +194,44 @@ final class NativeNavigationUnitTests: XCTestCase {
194194 XCTAssertEqual(link.gestureRecognizers?.filter { $0 is UITapGestureRecognizer }.count, 1)
195195 }
196196
197 func testRepeatedSelectorsRetainDistinctProtocolViewsAcrossReorders() throws {
198 let controller = CraftNativeScreenController(config: CraftConfig())
199 window.rootViewController = controller
200 controller.loadViewIfNeeded()
201 window.makeKeyAndVisible()
202 controller.view.layoutIfNeeded()
203 func document(_ ids: [String]) -> [String: Any] {
204 ["id": "identity-root", "type": "View", "children": ids.map { id in
205 ["id": "row-\(id)", "type": "View", "props": ["testID": "repeated-row"],
206 "children": [["id": "input-\(id)", "type": "TextInput", "props": ["testID": "repeated-input", "accessibilityLabel": "\(id) draft"]]]]
207 }]
208 }
209 func matching(_ view: UIView, _ id: String) -> [UIView] {
210 (view.accessibilityIdentifier == id ? [view] : []) + view.subviews.flatMap { matching($0, id) }
211 }
212 controller.render(document(["alpha", "beta"]))
213 controller.view.layoutIfNeeded()
214 let original = matching(controller.view, "repeated-row")
215 XCTAssertEqual(original.count, 2)
216 guard original.count == 2 else { return }
217 let alpha = original[0]
218 let beta = original[1]
219 let betaInput = try XCTUnwrap(matching(beta, "repeated-input").first as? UITextField)
220 XCTAssertTrue(betaInput.becomeFirstResponder())
221 betaInput.text = "Beta private draft"
222 betaInput.sendActions(for: .editingChanged)
223 controller.render(document(["beta", "alpha"]))
224 controller.view.layoutIfNeeded()
225 let reordered = matching(controller.view, "repeated-row")
226 XCTAssertEqual(reordered.count, 2)
227 guard reordered.count == 2 else { return }
228 XCTAssertTrue(reordered[0] === beta, "the repeated selector overrode Beta's protocol identity")
229 XCTAssertTrue(reordered[1] === alpha, "the repeated selector overrode Alpha's protocol identity")
230 XCTAssertTrue(matching(reordered[0], "repeated-input").first === betaInput)
231 XCTAssertEqual(betaInput.text, "Beta private draft")
232 XCTAssertTrue(betaInput.isFirstResponder, "reordering replaced the focused native input")
233 }
234
197235 func testIdlePreparationKeepsSelectionAndCurrentStackIntact() async throws {
198236 let home = try XCTUnwrap(stack("/"))
199237 let root = home.topViewController