ReviewOS

also looking at this

craft-native/craft

fix(native): preserve workspace sources and native overlay lifecycle

#384
Merged chris wants to merge codex/windows-zig-cache-diagnostics into main
20 files +500 -227

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/android/fixtures/native-navigation/NativeNavigationTest.ktmodified+7-1
Changes to packages/android/fixtures/native-navigation/NativeNavigationTest.kt
@@ -1639,7 +1639,13 @@ class NativeNavigationTest {
16391639 assertTrue("dismissed nested popup should be removed", nestedPopupRemoved || nestedPopup.visibility != View.VISIBLE)
16401640 assertEquals("dismissing a nested popup should retain its parent modal", View.VISIBLE, modal.visibility)
16411641 click(activity, "toggle-popup")
1642 val popup = awaitView(activity, "native-popup")
1642 val popupDensity = activity.resources.displayMetrics.density
1643 val popup = awaitView(activity, "native-popup") { view ->
1644 kotlin.math.abs(view.left - 24 * popupDensity) <= 1f
1645 && kotlin.math.abs(view.top - 36 * popupDensity) <= 1f
1646 && kotlin.math.abs(view.width - 180 * popupDensity) <= 1f
1647 && kotlin.math.abs(view.height - 72 * popupDensity) <= 1f
1648 }
16431649 awaitText(activity, "popup-content", "Popup content")
16441650 assertSame("standalone popup must share the screen viewport", popupRoot, popup.parent)
16451651 val safeRoot = awaitView(activity, "root")
packages/android/src/retained-identity.test.tsmodified+91-0
Changes to packages/android/src/retained-identity.test.ts
@@ -74,3 +74,94 @@ fun main() {
7474}
7575`)
7676}, 180_000)
77
78test('preserves modal viewport promotion and ordering across targeted reconciliation', async () => {
79 const template = await Bun.file(new URL('../templates/MainActivityNative.kt.template', import.meta.url)).text()
80 const promote = template.match(/ private fun promoteModalOverlays\(screen: Screen, controls: Collection<View>\) \{[\s\S]*?\n \}/)?.[0]
81 const targeted = template.match(/ private fun applyTargetedUpdates\(screen: Screen, ids: List<String>\): Boolean \{[\s\S]*?\n \}/)?.[0]
82 if (!promote || !targeted) throw new Error('Native modal reconciliation implementation is missing')
83 await runKotlinFixture(`
84open class View {
85 var parent: ViewGroup? = null
86 fun bringToFront() { val group = parent ?: return; group.children.remove(this); group.children.add(this) }
87}
88open class ViewGroup : View() {
89 val children = mutableListOf<View>()
90 val childStyles = mutableMapOf<View, JSONObject>()
91 fun removeView(view: View) { children.remove(view); childStyles.remove(view); view.parent = null }
92 fun setChildStyle(view: View, style: JSONObject, density: Float) { childStyles[view] = style }
93 fun addView(view: View) { view.parent?.removeView(view); children.add(view); view.parent = this }
94}
95class CraftNativeModalView : ViewGroup() {
96 val content = ViewGroup().also(::addView)
97 var viewportStyle = JSONObject("popup-frame")
98}
99class DisplayMetrics { val density = 1f }
100class Resources { val displayMetrics = DisplayMetrics() }
101val resources = Resources()
102class JSONObject(val id: String = "") { fun optJSONObject(name: String): JSONObject? = null }
103class MutationDocument(val parents: Map<String, String>) {
104 fun parentId(id: String): String? = parents[id]
105 fun node(id: String): JSONObject = JSONObject(id)
106}
107class Screen {
108 val view = ViewGroup()
109 val controls = linkedMapOf<String, View>()
110 val nodeViews = mutableMapOf<String, View>()
111 val nodePaths = mutableMapOf<String, String>()
112 val protocolIds = mutableMapOf<View, String>()
113 val lastLayoutBounds = mutableMapOf<View, String>()
114 val mutations = MutationDocument(mapOf("parent" to "root", "nested" to "parent", "standalone" to "root"))
115 var layoutEvents = 0
116}
117fun drawnTarget(screen: Screen, id: String): String = id
118fun makeView(screen: Screen, document: JSONObject, path: String, controls: MutableMap<String, View>) {
119 // Targeted parent reconciliation restores its logical child before promotion.
120 if (document.id == "parent") (screen.nodeViews.getValue("parent") as CraftNativeModalView).content.addView(screen.nodeViews.getValue("nested"))
121}
122fun updateParentLayoutStyle(view: View, style: JSONObject): Boolean = true
123fun scheduleLayoutEvents(screen: Screen) { screen.layoutEvents++ }
124${promote}
125${targeted}
126fun main() {
127 val screen = Screen()
128 val root = ViewGroup()
129 val parent = CraftNativeModalView()
130 val nested = CraftNativeModalView()
131 val standalone = CraftNativeModalView()
132 screen.view.addView(root)
133 root.addView(parent)
134 parent.content.addView(nested)
135 root.addView(standalone)
136 for (modal in listOf(parent, nested, standalone)) screen.view.setChildStyle(modal, modal.viewportStyle, 1f)
137 for ((id, view) in listOf("root" to root, "nested" to nested, "parent" to parent, "standalone" to standalone)) {
138 screen.nodeViews[id] = view
139 screen.nodePaths[id] = id
140 screen.protocolIds[view] = id
141 screen.controls[id] = view
142 screen.controls["id:" + id] = view
143 }
144 fun verify() {
145 check(parent.parent === screen.view && nested.parent === screen.view && standalone.parent === screen.view) { "An overlay escaped its viewport" }
146 for (modal in listOf(parent, nested, standalone)) check(screen.view.childStyles[modal] === modal.viewportStyle) { "A promoted overlay lost its viewport layout style" }
147 val children = screen.view.children
148 check(children.indexOf(parent) < children.indexOf(nested)) { "The parent scrim covered its nested popup" }
149 check(children.indexOf(nested) < children.indexOf(standalone)) { "Sibling overlay order changed" }
150 }
151 promoteModalOverlays(screen, screen.controls.values)
152 verify()
153 check(applyTargetedUpdates(screen, listOf("parent")))
154 verify()
155 check(screen.layoutEvents == 1)
156 check(applyTargetedUpdates(screen, listOf("standalone")))
157 verify()
158 root.addView(parent)
159 parent.content.addView(nested)
160 root.addView(standalone)
161 screen.protocolIds.clear()
162 promoteModalOverlays(screen, screen.controls.values)
163 verify()
164 println("PASS modal viewport, nested scrim ordering, sibling ordering, targeted updates and legacy renders")
165}
166`)
167}, 180_000)
packages/android/templates/MainActivityNative.kt.templatemodified+23-1
Changes to packages/android/templates/MainActivityNative.kt.template
@@ -2608,6 +2608,7 @@ private class CraftNativeIconView(context: Context) : TextView(context) {
26082608}
26092609
26102610private class CraftNativeModalView(context: Context) : FrameLayout(context) {
2611 var viewportStyle = JSONObject()
26112612 val content = CraftNativeFlexLayout(context)
26122613 private val blocker = View(context)
26132614 private var renderedVisible = false
@@ -2712,6 +2713,7 @@ private class CraftNativeModalView(context: Context) : FrameLayout(context) {
27122713 isClickable = true
27132714 blocksBackground = true
27142715 dismissOnOutsideTap = false
2716 viewportStyle = JSONObject()
27152717 blocker.isEnabled = true
27162718 onRequestClose = null
27172719 }
@@ -4998,6 +5000,7 @@ class MainActivity : AppCompatActivity() {
49985000 if (!updateParentLayoutStyle(updated, document.optJSONObject("style") ?: JSONObject())) return false
49995001 }
50005002 if (applied.isNotEmpty()) {
5003 promoteModalOverlays(screen, screen.controls.values)
50015004 scheduleLayoutEvents(screen)
50025005 }
50035006 return true
@@ -5098,13 +5101,31 @@ class MainActivity : AppCompatActivity() {
50985101
50995102 /** Keep Modal nodes in the screen viewport even when declared in a clipped subtree. */
51005103 private fun promoteModalOverlays(screen: Screen, controls: Collection<View>) {
5101 controls.filterIsInstance<CraftNativeModalView>().forEach { modal ->
5104 val promoted = mutableSetOf<CraftNativeModalView>()
5105 fun promote(modal: CraftNativeModalView) {
5106 if (!promoted.add(modal)) return
5107 // Native parents change after promotion; the committed tree keeps
5108 // logical modal ancestry so a parent scrim stays below its popup.
5109 var parentId = screen.protocolIds[modal]?.let { screen.mutations.parentId(it) }
5110 while (parentId != null) {
5111 (screen.nodeViews[parentId] as? CraftNativeModalView)?.let { promote(it) }
5112 parentId = screen.mutations.parentId(parentId)
5113 }
5114 // Legacy full renders may not have protocol IDs yet.
5115 var parent = modal.parent as? View
5116 while (parent != null && parent !== screen.view) {
5117 val nextParent = parent.parent as? View
5118 if (parent is CraftNativeModalView) promote(parent)
5119 parent = nextParent
5120 }
51025121 if (modal.parent !== screen.view) {
51035122 (modal.parent as? ViewGroup)?.removeView(modal)
51045123 screen.view.addView(modal)
5124 screen.view.setChildStyle(modal, modal.viewportStyle, resources.displayMetrics.density)
51055125 }
51065126 modal.bringToFront()
51075127 }
5128 controls.filterIsInstance<CraftNativeModalView>().forEach { promote(it) }
51085129 }
51095130
51105131 private fun applyStatusBar(screen: Screen) {
@@ -5475,6 +5496,7 @@ class MainActivity : AppCompatActivity() {
54755496 // the screen root. Keep the authored layout style with that
54765497 // promoted child so non-modal absolute coordinates remain in
54775498 // the screen viewport rather than the old clipped parent.
5499 viewportStyle = style
54785500 screen.view.setChildStyle(this, style, resources.displayMetrics.density)
54795501 content.configure(style, resources.displayMetrics.density)
54805502 reconcileChildren(screen, content, children, identity, next, style, previousControls)