react-native-gtkx 0.2.0-alpha.1 → 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +11 -2
  2. package/dist/adw/widgets.generated.d.ts +4 -4
  3. package/dist/adw/widgets.generated.js +51 -51
  4. package/dist/adw/widgets.generated.js.map +1 -1
  5. package/dist/animated/create-animated.d.ts +2 -0
  6. package/dist/animated/create-animated.js +2 -0
  7. package/dist/animated/create-animated.js.map +1 -1
  8. package/dist/animated/index.d.ts +2 -0
  9. package/dist/animated/index.js +1 -0
  10. package/dist/animated/index.js.map +1 -1
  11. package/dist/animated/value-xy.d.ts +41 -0
  12. package/dist/animated/value-xy.js +90 -0
  13. package/dist/animated/value-xy.js.map +1 -0
  14. package/dist/animated/value.d.ts +9 -0
  15. package/dist/animated/value.js +30 -2
  16. package/dist/animated/value.js.map +1 -1
  17. package/dist/common/icon.d.ts +23 -0
  18. package/dist/common/icon.js +14 -0
  19. package/dist/common/icon.js.map +1 -0
  20. package/dist/common/index.d.ts +2 -0
  21. package/dist/common/index.js +2 -0
  22. package/dist/common/index.js.map +1 -1
  23. package/dist/common/list.d.ts +82 -0
  24. package/dist/common/list.js +166 -0
  25. package/dist/common/list.js.map +1 -0
  26. package/dist/common/widget.d.ts +1 -22
  27. package/dist/common/widget.js +75 -8
  28. package/dist/common/widget.js.map +1 -1
  29. package/dist/components/animated.d.ts +4 -2
  30. package/dist/components/animated.js +3 -1
  31. package/dist/components/animated.js.map +1 -1
  32. package/dist/components/app-registry.d.ts +26 -0
  33. package/dist/components/app-registry.js.map +1 -1
  34. package/dist/components/host-node.d.ts +7 -0
  35. package/dist/components/host-node.js +26 -1
  36. package/dist/components/host-node.js.map +1 -1
  37. package/dist/components/index.d.ts +6 -2
  38. package/dist/components/index.js +2 -1
  39. package/dist/components/index.js.map +1 -1
  40. package/dist/components/measure.d.ts +13 -0
  41. package/dist/components/measure.js +51 -0
  42. package/dist/components/measure.js.map +1 -0
  43. package/dist/components/modal.d.ts +1 -1
  44. package/dist/components/press-event.d.ts +28 -0
  45. package/dist/components/press-event.js +46 -0
  46. package/dist/components/press-event.js.map +1 -0
  47. package/dist/components/pressable.d.ts +2 -6
  48. package/dist/components/pressable.js +14 -8
  49. package/dist/components/pressable.js.map +1 -1
  50. package/dist/components/scroll-view.d.ts +4 -0
  51. package/dist/components/scroll-view.js +8 -2
  52. package/dist/components/scroll-view.js.map +1 -1
  53. package/dist/components/text-input.js +1 -1
  54. package/dist/components/text-input.js.map +1 -1
  55. package/dist/components/text.js +25 -1
  56. package/dist/components/text.js.map +1 -1
  57. package/dist/components/use-layout-child.js +17 -14
  58. package/dist/components/use-layout-child.js.map +1 -1
  59. package/dist/components/view.d.ts +9 -4
  60. package/dist/components/view.js +8 -2
  61. package/dist/components/view.js.map +1 -1
  62. package/dist/contracts.d.ts +19 -3
  63. package/dist/contracts.js.map +1 -1
  64. package/dist/gtk/index.d.ts +2 -1
  65. package/dist/gtk/index.js +8 -1
  66. package/dist/gtk/index.js.map +1 -1
  67. package/dist/gtk/widgets.generated.d.ts +14 -14
  68. package/dist/gtk/widgets.generated.js +101 -102
  69. package/dist/gtk/widgets.generated.js.map +1 -1
  70. package/dist/gtk/window-actions.d.ts +77 -0
  71. package/dist/gtk/window-actions.js +193 -0
  72. package/dist/gtk/window-actions.js.map +1 -0
  73. package/dist/gtkx/bridge/geometry.d.ts +15 -0
  74. package/dist/gtkx/bridge/geometry.js +38 -0
  75. package/dist/gtkx/bridge/geometry.js.map +1 -0
  76. package/dist/gtkx/bridge/index.d.ts +2 -0
  77. package/dist/gtkx/bridge/index.js +7 -2
  78. package/dist/gtkx/bridge/index.js.map +1 -1
  79. package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
  80. package/dist/gtkx/bridge/slot-portal.js +17 -0
  81. package/dist/gtkx/bridge/slot-portal.js.map +1 -0
  82. package/dist/gtkx/bridge/svg-node.js +10 -54
  83. package/dist/gtkx/bridge/svg-node.js.map +1 -1
  84. package/dist/gtkx/bridge/use-signal.js +2 -2
  85. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  86. package/dist/index.d.ts +2 -2
  87. package/dist/index.js +1 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/mcp/data/generated.d.ts +99 -77
  90. package/dist/mcp/data/generated.js +107 -76
  91. package/dist/mcp/data/generated.js.map +1 -1
  92. package/dist/mcp/server.js +1 -1
  93. package/dist/mcp/server.js.map +1 -1
  94. package/dist/navigation/sidebar.d.ts +31 -1
  95. package/dist/navigation/sidebar.js +12 -2
  96. package/dist/navigation/sidebar.js.map +1 -1
  97. package/dist/responder/system.d.ts +27 -0
  98. package/dist/responder/system.js +184 -0
  99. package/dist/responder/system.js.map +1 -0
  100. package/dist/responder/touch-history.d.ts +10 -0
  101. package/dist/responder/touch-history.js +78 -0
  102. package/dist/responder/touch-history.js.map +1 -0
  103. package/dist/responder/types.d.ts +63 -0
  104. package/dist/responder/types.js +29 -0
  105. package/dist/responder/types.js.map +1 -0
  106. package/dist/responder/use-responder.d.ts +12 -0
  107. package/dist/responder/use-responder.js +98 -0
  108. package/dist/responder/use-responder.js.map +1 -0
  109. package/dist/runner/host-dev.js +3 -1
  110. package/dist/runner/host-dev.js.map +1 -1
  111. package/dist/runner/host.js +5 -1
  112. package/dist/runner/host.js.map +1 -1
  113. package/dist/runner/index.js +1 -1
  114. package/dist/runner/index.js.map +1 -1
  115. package/dist/sea/gtkx-config-module.js +1 -1
  116. package/dist/sea/gtkx-config-module.js.map +1 -1
  117. package/dist/style/box-shadow.d.ts +7 -0
  118. package/dist/style/box-shadow.js +197 -0
  119. package/dist/style/box-shadow.js.map +1 -0
  120. package/dist/style/index.d.ts +2 -0
  121. package/dist/style/index.js +2 -0
  122. package/dist/style/index.js.map +1 -1
  123. package/dist/style/split-style.d.ts +5 -2
  124. package/dist/style/split-style.js +31 -3
  125. package/dist/style/split-style.js.map +1 -1
  126. package/dist/style/text-decoration.d.ts +15 -0
  127. package/dist/style/text-decoration.js +26 -0
  128. package/dist/style/text-decoration.js.map +1 -0
  129. package/dist/style/visual-css.js +37 -1
  130. package/dist/style/visual-css.js.map +1 -1
  131. package/dist/svg/gradient-geometry.js +2 -5
  132. package/dist/svg/gradient-geometry.js.map +1 -1
  133. package/dist/testing/index.js.map +1 -1
  134. package/dist/vendor/react-native/pan-responder.d.ts +102 -0
  135. package/dist/vendor/react-native/pan-responder.js +196 -0
  136. package/dist/vendor/react-native/pan-responder.js.map +1 -0
  137. package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
  138. package/dist/vendor/react-native/touch-history-math.js +102 -0
  139. package/dist/vendor/react-native/touch-history-math.js.map +1 -0
  140. package/dist/vite/index.js +1 -1
  141. package/dist/vite/index.js.map +1 -1
  142. package/dist/vitest/index.d.ts +1 -1
  143. package/dist/vitest/index.js.map +1 -1
  144. package/package.json +9 -9
@@ -25,8 +25,8 @@ export declare const PORTABLE_COMPONENTS: readonly [{
25
25
  readonly name: "View";
26
26
  readonly subpath: "react-native";
27
27
  readonly gtkImplementation: "GtkBox (RnGtkxViewBox)";
28
- readonly supported: "`style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins)";
29
- readonly differences: "nesting another pointerEvents inside a box-only view is not supported";
28
+ readonly supported: "`style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins), ref: `measure`/`measureInWindow`/`measureLayout` (`ViewHandle`, RN's argument order; window coordinates come from `gtk_widget_compute_point`, so they are correct inside a scrolled viewport), the responder and touch props (`onStartShouldSetResponder(Capture)`, `onMoveShouldSetResponder(Capture)`, `onResponderGrant/Start/Move/End/Release/Terminate`, `onTouchStart/Move/End/Cancel` + `Capture`) — spread `PanResponder`'s `panHandlers` here";
29
+ readonly differences: "nesting another pointerEvents inside a box-only view is not supported; responder negotiation is capture-then-bubble as in RN, but there is **no transfer yet**: once a view is granted, `onResponderTerminationRequest`/`onResponderReject` are never called and a second view cannot steal (GTK's `CLAIMED` is irrevocable, so a transfer could not be enforced against native widgets — see docs/research/gestures.md). The lock is one per process as in RN; the negotiation PATH stops at the layout root, so native GTK widgets between or above views take no part. Single-pointer only: a mouse is one fabricated touch, `touches` never exceeds one";
30
30
  }, {
31
31
  readonly name: "Text";
32
32
  readonly subpath: "react-native";
@@ -49,7 +49,7 @@ export declare const PORTABLE_COMPONENTS: readonly [{
49
49
  readonly name: "Pressable";
50
50
  readonly subpath: "react-native";
51
51
  readonly gtkImplementation: "GtkFixed + GestureClick/Motion";
52
- readonly supported: "`onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}`";
52
+ readonly supported: "`onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}`; the `PressEvent` payload is RN's shape (`locationX/Y` target-relative, `pageX/Y` window-relative, `identifier`, `target`, `force`, monotonic `timestamp`, single-element `touches`/`changedTouches` — a desktop pointer is one fabricated touch)";
53
53
  readonly differences: "—";
54
54
  }, {
55
55
  readonly name: "TouchableOpacity";
@@ -61,7 +61,7 @@ export declare const PORTABLE_COMPONENTS: readonly [{
61
61
  readonly name: "ScrollView";
62
62
  readonly subpath: "react-native";
63
63
  readonly gtkImplementation: "GtkScrolledWindow";
64
- readonly supported: "vertical/`horizontal`, `contentContainerStyle`, `onScroll`, `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`)";
64
+ readonly supported: "vertical/`horizontal`, `contentContainerStyle`, `onScroll` (`contentOffset`, `contentSize`, `layoutMeasurement`), `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`)";
65
65
  readonly differences: "`animated` in scrollTo is ignored";
66
66
  }, {
67
67
  readonly name: "FlatList";
@@ -97,7 +97,7 @@ export declare const PORTABLE_COMPONENTS: readonly [{
97
97
  readonly name: "Animated.View";
98
98
  readonly subpath: "react-native";
99
99
  readonly gtkImplementation: "direct widget calls";
100
- readonly supported: "`opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange)";
100
+ readonly supported: "`opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange), plus the same responder and touch props `View` takes — this is where an idiomatic `PanResponder` drag lands";
101
101
  readonly differences: "`rotateX`/`rotateY`/`perspective` (3D), `skewX`/`skewY` and `matrix` are not supported, and the transform origin is always the view's centre (no `transformOrigin`)";
102
102
  }, {
103
103
  readonly name: "SafeAreaView";
@@ -143,8 +143,8 @@ export declare const PORTABLE_APIS: readonly [{
143
143
  }, {
144
144
  readonly name: "AppRegistry";
145
145
  readonly subpath: "react-native";
146
- readonly supported: "`registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,applicationActions,actionAccels,windowActions,windowControllers,breakpoints})`, `getAppKeys`";
147
- readonly differences: "desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `applicationActions`/`actionAccels` reach the underlying `GtkApplication` (`app.*` actions — what a `Gio.Notification` action button targets); `windowActions`/`windowControllers` reach the window (`win.*` actions, a window-scoped `GtkShortcutController`); `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise)";
146
+ readonly supported: "`registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,actionAccels,breakpoints,applicationActions,windowActions,windowControllers})`, `getAppKeys`";
147
+ readonly differences: "desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `actionAccels` binds accelerators to action names on the `GtkApplication`; `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise). **`applicationActions`/`windowActions`/`windowControllers` are deprecated** — reach for [`<ApplicationActions>`/`<WindowActions>`/`<WindowControllers>`](platform-layer.md#actions-and-shortcuts-declared-in-the-app-tree) instead; they still work unchanged";
148
148
  }, {
149
149
  readonly name: "Platform";
150
150
  readonly subpath: "react-native";
@@ -205,11 +205,16 @@ export declare const PORTABLE_APIS: readonly [{
205
205
  readonly subpath: "react-native";
206
206
  readonly supported: "`addEventListener(\"hardwareBackPress\")`, `exitApp`";
207
207
  readonly differences: "no hardware back key on desktop — subscriptions are honored but nothing fires them yet";
208
+ }, {
209
+ readonly name: "PanResponder";
210
+ readonly subpath: "react-native";
211
+ readonly supported: "`create(config)` -> `panHandlers` (spread onto a `View`), full `gestureState` (`dx`/`dy`, `vx`/`vy`, `x0`/`y0`, `moveX`/`moveY`, `numberActiveTouches`) — **react-native's own file, vendored unmodified** (MIT, `Libraries/Interaction/PanResponder.js`), running on our reproduction of RN's `touchHistory` store";
212
+ readonly differences: "multi-touch `gestureState` is single-touch here (one pointer), and `onShouldBlockNativeResponder`'s return value is not consumed yet";
208
213
  }, {
209
214
  readonly name: "Animated";
210
215
  readonly subpath: "react-native";
211
- readonly supported: "`Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity)";
212
- readonly differences: "`useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway";
216
+ readonly supported: "`Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity), `ValueXY` (`setValue`/`setOffset`/`flattenOffset`/`extractOffset`, `getLayout`, `getTranslateTransform`) — the value a `PanResponder` drag writes to";
217
+ readonly differences: "`useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway; `Animated.event` is not implemented — write the value directly (`pan.setValue({x: g.dx, y: g.dy})`), which is what it would do";
213
218
  }, {
214
219
  readonly name: "Easing";
215
220
  readonly subpath: "react-native";
@@ -302,10 +307,6 @@ export declare const GTK_WIDGETS: readonly [{
302
307
  readonly name: "GtkComboBoxText";
303
308
  readonly subpath: "react-native-gtkx/gtk";
304
309
  readonly wrapped: true;
305
- }, {
306
- readonly name: "GtkDragIcon";
307
- readonly subpath: "react-native-gtkx/gtk";
308
- readonly wrapped: true;
309
310
  }, {
310
311
  readonly name: "GtkDrawingArea";
311
312
  readonly subpath: "react-native-gtkx/gtk";
@@ -590,37 +591,42 @@ export declare const GTK_WIDGETS: readonly [{
590
591
  readonly name: "GtkAboutDialog";
591
592
  readonly subpath: "react-native-gtkx/gtk";
592
593
  readonly wrapped: false;
593
- readonly reason: "toplevel (derives Gtk.Window)";
594
+ readonly reason: "toplevel (implements GtkRoot)";
594
595
  }, {
595
596
  readonly name: "GtkAppChooserDialog";
596
597
  readonly subpath: "react-native-gtkx/gtk";
597
598
  readonly wrapped: false;
598
- readonly reason: "toplevel (derives Gtk.Window)";
599
+ readonly reason: "toplevel (implements GtkRoot)";
599
600
  }, {
600
601
  readonly name: "GtkApplicationWindow";
601
602
  readonly subpath: "react-native-gtkx/gtk";
602
603
  readonly wrapped: false;
603
- readonly reason: "toplevel (derives Gtk.Window)";
604
+ readonly reason: "toplevel (implements GtkRoot)";
604
605
  }, {
605
606
  readonly name: "GtkAssistant";
606
607
  readonly subpath: "react-native-gtkx/gtk";
607
608
  readonly wrapped: false;
608
- readonly reason: "toplevel (derives Gtk.Window)";
609
+ readonly reason: "toplevel (implements GtkRoot)";
609
610
  }, {
610
611
  readonly name: "GtkColorChooserDialog";
611
612
  readonly subpath: "react-native-gtkx/gtk";
612
613
  readonly wrapped: false;
613
- readonly reason: "toplevel (derives Gtk.Window)";
614
+ readonly reason: "toplevel (implements GtkRoot)";
614
615
  }, {
615
616
  readonly name: "GtkDialog";
616
617
  readonly subpath: "react-native-gtkx/gtk";
617
618
  readonly wrapped: false;
618
- readonly reason: "toplevel (derives Gtk.Window)";
619
+ readonly reason: "toplevel (implements GtkRoot)";
620
+ }, {
621
+ readonly name: "GtkDragIcon";
622
+ readonly subpath: "react-native-gtkx/gtk";
623
+ readonly wrapped: false;
624
+ readonly reason: "toplevel (implements GtkRoot)";
619
625
  }, {
620
626
  readonly name: "GtkFileChooserDialog";
621
627
  readonly subpath: "react-native-gtkx/gtk";
622
628
  readonly wrapped: false;
623
- readonly reason: "toplevel (derives Gtk.Window)";
629
+ readonly reason: "toplevel (implements GtkRoot)";
624
630
  }, {
625
631
  readonly name: "GtkFlowBoxChild";
626
632
  readonly subpath: "react-native-gtkx/gtk";
@@ -630,7 +636,7 @@ export declare const GTK_WIDGETS: readonly [{
630
636
  readonly name: "GtkFontChooserDialog";
631
637
  readonly subpath: "react-native-gtkx/gtk";
632
638
  readonly wrapped: false;
633
- readonly reason: "toplevel (derives Gtk.Window)";
639
+ readonly reason: "toplevel (implements GtkRoot)";
634
640
  }, {
635
641
  readonly name: "GtkListBoxRow";
636
642
  readonly subpath: "react-native-gtkx/gtk";
@@ -640,27 +646,27 @@ export declare const GTK_WIDGETS: readonly [{
640
646
  readonly name: "GtkMessageDialog";
641
647
  readonly subpath: "react-native-gtkx/gtk";
642
648
  readonly wrapped: false;
643
- readonly reason: "toplevel (derives Gtk.Window)";
649
+ readonly reason: "toplevel (implements GtkRoot)";
644
650
  }, {
645
651
  readonly name: "GtkPageSetupUnixDialog";
646
652
  readonly subpath: "react-native-gtkx/gtk";
647
653
  readonly wrapped: false;
648
- readonly reason: "toplevel (derives Gtk.Window)";
654
+ readonly reason: "toplevel (implements GtkRoot)";
649
655
  }, {
650
656
  readonly name: "GtkPrintUnixDialog";
651
657
  readonly subpath: "react-native-gtkx/gtk";
652
658
  readonly wrapped: false;
653
- readonly reason: "toplevel (derives Gtk.Window)";
659
+ readonly reason: "toplevel (implements GtkRoot)";
654
660
  }, {
655
661
  readonly name: "GtkShortcutsWindow";
656
662
  readonly subpath: "react-native-gtkx/gtk";
657
663
  readonly wrapped: false;
658
- readonly reason: "toplevel (derives Gtk.Window)";
664
+ readonly reason: "toplevel (implements GtkRoot)";
659
665
  }, {
660
666
  readonly name: "GtkWindow";
661
667
  readonly subpath: "react-native-gtkx/gtk";
662
668
  readonly wrapped: false;
663
- readonly reason: "toplevel (derives Gtk.Window)";
669
+ readonly reason: "toplevel (implements GtkRoot)";
664
670
  }];
665
671
  export declare const ADW_WIDGETS: readonly [{
666
672
  readonly name: "AdwAboutDialog";
@@ -850,7 +856,7 @@ export declare const ADW_WIDGETS: readonly [{
850
856
  readonly name: "AdwAboutWindow";
851
857
  readonly subpath: "react-native-gtkx/adw";
852
858
  readonly wrapped: false;
853
- readonly reason: "toplevel (derives Gtk.Window)";
859
+ readonly reason: "toplevel (implements GtkRoot)";
854
860
  }, {
855
861
  readonly name: "AdwActionRow";
856
862
  readonly subpath: "react-native-gtkx/adw";
@@ -860,7 +866,7 @@ export declare const ADW_WIDGETS: readonly [{
860
866
  readonly name: "AdwApplicationWindow";
861
867
  readonly subpath: "react-native-gtkx/adw";
862
868
  readonly wrapped: false;
863
- readonly reason: "toplevel (derives Gtk.Window)";
869
+ readonly reason: "toplevel (implements GtkRoot)";
864
870
  }, {
865
871
  readonly name: "AdwButtonRow";
866
872
  readonly subpath: "react-native-gtkx/adw";
@@ -885,7 +891,7 @@ export declare const ADW_WIDGETS: readonly [{
885
891
  readonly name: "AdwMessageDialog";
886
892
  readonly subpath: "react-native-gtkx/adw";
887
893
  readonly wrapped: false;
888
- readonly reason: "toplevel (derives Gtk.Window)";
894
+ readonly reason: "toplevel (implements GtkRoot)";
889
895
  }, {
890
896
  readonly name: "AdwNavigationPage";
891
897
  readonly subpath: "react-native-gtkx/adw";
@@ -910,7 +916,7 @@ export declare const ADW_WIDGETS: readonly [{
910
916
  readonly name: "AdwPreferencesWindow";
911
917
  readonly subpath: "react-native-gtkx/adw";
912
918
  readonly wrapped: false;
913
- readonly reason: "toplevel (derives Gtk.Window)";
919
+ readonly reason: "toplevel (implements GtkRoot)";
914
920
  }, {
915
921
  readonly name: "AdwSpinRow";
916
922
  readonly subpath: "react-native-gtkx/adw";
@@ -925,16 +931,16 @@ export declare const ADW_WIDGETS: readonly [{
925
931
  readonly name: "AdwWindow";
926
932
  readonly subpath: "react-native-gtkx/adw";
927
933
  readonly wrapped: false;
928
- readonly reason: "toplevel (derives Gtk.Window)";
934
+ readonly reason: "toplevel (implements GtkRoot)";
929
935
  }];
930
936
  export declare const DOC_CHUNKS: readonly [{
931
937
  readonly doc: "docs/api.md";
932
938
  readonly heading: "Components";
933
- readonly text: "| Export | GTK implementation | Supported | Differences from RN |\n| ------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `View` | GtkBox (RnGtkxViewBox) | `style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins) | nesting another pointerEvents inside a box-only view is not supported |\n| `Text` | GtkLabel (Pango) | wrap, `numberOfLines` (ellipsize END), `textAlign`, font styles, `onLayout`, `testID` | nested `Text` elements are concatenated without per-span styles; text is always ellipsizable (shrinkable in narrow windows) |\n| `Image` | GtkPicture | `source={{uri}}`/string — local paths, file:// and **http(s)** (Node fetch → disk cache keyed by URL, in-flight de-duplication), `resizeMode` cover/contain/stretch/center, `onLoad`/`onError`; **`.svg` files load like any other image** — `Gdk.Texture.newFromFilename` rasterizes them via librsvg, no extra code needed (for building vector graphics from state instead of a file, see the \"Svg\" section below — a separate import, not part of this table) | no synchronous size from remote images (style sets the size, as in RN); cache is not size-limited yet |\n| `TextInput` | GtkEntry / GtkTextView | controlled/uncontrolled (`value`/`defaultValue`), `onChangeText`, `onSubmitEditing`, `onFocus`/`onBlur`, `placeholder` (own dim overlay in multiline — GtkTextView has none), `secureTextEntry`, `editable`, `keyboardType`, `multiline`, `clearButtonMode` (GtkEntry's built-in clear icon; RN ships this on iOS only) (real GtkTextView: word wrap, internal scroll, Enter inserts a newline and never fires onSubmitEditing — RN semantics) | multiline needs a height in the style (as RN recommends) |\n| `Pressable` | GtkFixed + GestureClick/Motion | `onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}` | — |\n| `TouchableOpacity` | on top of Pressable | `activeOpacity` | — |\n| `ScrollView` | GtkScrolledWindow | vertical/`horizontal`, `contentContainerStyle`, `onScroll`, `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`) | `animated` in scrollTo is ignored |\n| `FlatList` | windowed core on ScrollView | virtualization (`estimatedItemSize` or `getItemLayout`, **`windowSize`/`initialNumToRender` — the primary scroll-performance knobs**, `maxToRenderPerBatch`/`updateCellsBatchingPeriod`), `data`/`renderItem`/`keyExtractor`/`extraData`, `ItemSeparatorComponent`, `ListHeader/Footer/EmptyComponent`, `onEndReached(-Threshold)`, `onViewableItemsChanged`/`viewabilityConfig` (`ViewToken`), `inverted` (RN chat semantics: opens at `data[0]`, stays pinned on prepend), `refreshing`/`onRefresh`, `horizontal`, `stickyHeaderIndices`, ref: `scrollToIndex`/`scrollToItem`/`scrollToOffset` + ScrollView methods (`FlatListHandle`) | 1000 rows mount windowed in ~120 ms (v1 full mount was 879 ms); `windowSize` defaults to **11**, not RN's 5 — desktop has no mobile memory pressure and a wider window means fewer mount+reflow bursts per scrolled pixel (measured: −21% churn, late frames 10/s → 7.7/s); rows beyond the visible ones are mounted `maxToRenderPerBatch` (10) at a time every `updateCellsBatchingPeriod` (50) ms, so a flick or a long `scrollToOffset` fills its window over several frames instead of stalling one; no pull gesture — `onRefresh` must be app-triggered; an inverted list shorter than its viewport anchors to the top, not the bottom |\n| `SectionList` | on top of FlatList | `sections`, `renderSectionHeader`, sticky section headers by default (`stickySectionHeadersEnabled`) | viewability props are not exposed (section-aware ViewTokens pending) |\n| `Switch` | GtkSwitch | `value`/`onValueChange`, `disabled` | sized by the GTK theme, not iOS metrics |\n| `ActivityIndicator` | GtkSpinner | `animating`, `size` (small/large/number) | no `color` yet |\n| `Modal` | modal GtkWindow (portal) | `visible`, `onRequestClose` (Escape/close button), `title`, `width`/`height`; independently resizable with relayout | desktop semantics: a separate window, not an overlay; `transparent`/`animationType` are no-ops |\n| `Animated.View` | direct widget calls | `opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange) | `rotateX`/`rotateY`/`perspective` (3D), `skewX`/`skewY` and `matrix` are not supported, and the transform origin is always the view's centre (no `transformOrigin`) |\n| `SafeAreaView` | = View | — | no notches on desktop |\n| `StatusBar` | null | — | no status bar |\n| `Root` | internal root | `width`/`height` | extension: required by the test harness |\n| `NestedRoot` | internal root | — | extension: a Yoga root inside any GTK container slot (navigation pages, custom containers); the slot allocation is the viewport |\n| `IntrinsicRoot` | internal root | — | extension: a content-sized Yoga root for chrome slots (HeaderBar start/end) — reports its content size to GTK |";
939
+ readonly text: "| Export | GTK implementation | Supported | Differences from RN |\n| ------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `View` | GtkBox (RnGtkxViewBox) | `style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins), ref: `measure`/`measureInWindow`/`measureLayout` (`ViewHandle`, RN's argument order; window coordinates come from `gtk_widget_compute_point`, so they are correct inside a scrolled viewport), the responder and touch props (`onStartShouldSetResponder(Capture)`, `onMoveShouldSetResponder(Capture)`, `onResponderGrant/Start/Move/End/Release/Terminate`, `onTouchStart/Move/End/Cancel` + `Capture`) — spread `PanResponder`'s `panHandlers` here | nesting another pointerEvents inside a box-only view is not supported; responder negotiation is capture-then-bubble as in RN, but there is **no transfer yet**: once a view is granted, `onResponderTerminationRequest`/`onResponderReject` are never called and a second view cannot steal (GTK's `CLAIMED` is irrevocable, so a transfer could not be enforced against native widgets — see docs/research/gestures.md). The lock is one per process as in RN; the negotiation PATH stops at the layout root, so native GTK widgets between or above views take no part. Single-pointer only: a mouse is one fabricated touch, `touches` never exceeds one |\n| `Text` | GtkLabel (Pango) | wrap, `numberOfLines` (ellipsize END), `textAlign`, font styles, `onLayout`, `testID` | nested `Text` elements are concatenated without per-span styles; text is always ellipsizable (shrinkable in narrow windows) |\n| `Image` | GtkPicture | `source={{uri}}`/string — local paths, file:// and **http(s)** (Node fetch → disk cache keyed by URL, in-flight de-duplication), `resizeMode` cover/contain/stretch/center, `onLoad`/`onError`; **`.svg` files load like any other image** — `Gdk.Texture.newFromFilename` rasterizes them via librsvg, no extra code needed (for building vector graphics from state instead of a file, see the \"Svg\" section below — a separate import, not part of this table) | no synchronous size from remote images (style sets the size, as in RN); cache is not size-limited yet |\n| `TextInput` | GtkEntry / GtkTextView | controlled/uncontrolled (`value`/`defaultValue`), `onChangeText`, `onSubmitEditing`, `onFocus`/`onBlur`, `placeholder` (own dim overlay in multiline — GtkTextView has none), `secureTextEntry`, `editable`, `keyboardType`, `multiline`, `clearButtonMode` (GtkEntry's built-in clear icon; RN ships this on iOS only) (real GtkTextView: word wrap, internal scroll, Enter inserts a newline and never fires onSubmitEditing — RN semantics) | multiline needs a height in the style (as RN recommends) |\n| `Pressable` | GtkFixed + GestureClick/Motion | `onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}`; the `PressEvent` payload is RN's shape (`locationX/Y` target-relative, `pageX/Y` window-relative, `identifier`, `target`, `force`, monotonic `timestamp`, single-element `touches`/`changedTouches` — a desktop pointer is one fabricated touch) | — |\n| `TouchableOpacity` | on top of Pressable | `activeOpacity` | — |\n| `ScrollView` | GtkScrolledWindow | vertical/`horizontal`, `contentContainerStyle`, `onScroll` (`contentOffset`, `contentSize`, `layoutMeasurement`), `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`) | `animated` in scrollTo is ignored |\n| `FlatList` | windowed core on ScrollView | virtualization (`estimatedItemSize` or `getItemLayout`, **`windowSize`/`initialNumToRender` — the primary scroll-performance knobs**, `maxToRenderPerBatch`/`updateCellsBatchingPeriod`), `data`/`renderItem`/`keyExtractor`/`extraData`, `ItemSeparatorComponent`, `ListHeader/Footer/EmptyComponent`, `onEndReached(-Threshold)`, `onViewableItemsChanged`/`viewabilityConfig` (`ViewToken`), `inverted` (RN chat semantics: opens at `data[0]`, stays pinned on prepend), `refreshing`/`onRefresh`, `horizontal`, `stickyHeaderIndices`, ref: `scrollToIndex`/`scrollToItem`/`scrollToOffset` + ScrollView methods (`FlatListHandle`) | 1000 rows mount windowed in ~120 ms (v1 full mount was 879 ms); `windowSize` defaults to **11**, not RN's 5 — desktop has no mobile memory pressure and a wider window means fewer mount+reflow bursts per scrolled pixel (measured: −21% churn, late frames 10/s → 7.7/s); rows beyond the visible ones are mounted `maxToRenderPerBatch` (10) at a time every `updateCellsBatchingPeriod` (50) ms, so a flick or a long `scrollToOffset` fills its window over several frames instead of stalling one; no pull gesture — `onRefresh` must be app-triggered; an inverted list shorter than its viewport anchors to the top, not the bottom |\n| `SectionList` | on top of FlatList | `sections`, `renderSectionHeader`, sticky section headers by default (`stickySectionHeadersEnabled`) | viewability props are not exposed (section-aware ViewTokens pending) |\n| `Switch` | GtkSwitch | `value`/`onValueChange`, `disabled` | sized by the GTK theme, not iOS metrics |\n| `ActivityIndicator` | GtkSpinner | `animating`, `size` (small/large/number) | no `color` yet |\n| `Modal` | modal GtkWindow (portal) | `visible`, `onRequestClose` (Escape/close button), `title`, `width`/`height`; independently resizable with relayout | desktop semantics: a separate window, not an overlay; `transparent`/`animationType` are no-ops |\n| `Animated.View` | direct widget calls | `opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange), plus the same responder and touch props `View` takes — this is where an idiomatic `PanResponder` drag lands | `rotateX`/`rotateY`/`perspective` (3D), `skewX`/`skewY` and `matrix` are not supported, and the transform origin is always the view's centre (no `transformOrigin`) |\n| `SafeAreaView` | = View | — | no notches on desktop |\n| `StatusBar` | null | — | no status bar |\n| `Root` | internal root | `width`/`height` | extension: required by the test harness |\n| `NestedRoot` | internal root | — | extension: a Yoga root inside any GTK container slot (navigation pages, custom containers); the slot allocation is the viewport |\n| `IntrinsicRoot` | internal root | — | extension: a content-sized Yoga root for chrome slots (HeaderBar start/end) — reports its content size to GTK |";
934
940
  }, {
935
941
  readonly doc: "docs/api.md";
936
942
  readonly heading: "API modules";
937
- readonly text: "| Export | Supported | Differences |\n| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `StyleSheet` | `create`, `flatten`, `compose`, `absoluteFill(Object)`, `hairlineWidth` | — |\n| `PlatformColor` | Adwaita variables: `PlatformColor(\"accent-bg-color\")` → `var(--...)`, `@named` | names are Adwaita, not iOS/Android |\n| `AppRegistry` | `registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,applicationActions,actionAccels,windowActions,windowControllers,breakpoints})`, `getAppKeys` | desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `applicationActions`/`actionAccels` reach the underlying `GtkApplication` (`app.*` actions — what a `Gio.Notification` action button targets); `windowActions`/`windowControllers` reach the window (`win.*` actions, a window-scoped `GtkShortcutController`); `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise) |\n| `Platform` | `OS: \"linux\"`, `Version` (GTK), `select` (linux → native → default), `isTV`, `isTesting` | — |\n| `Dimensions` | `get(\"window\"/\"screen\")`, `addEventListener(\"change\")` | main window only (transient windows are ignored) |\n| `useWindowDimensions` | reactive main-window dimensions | — |\n| `Appearance` | `getColorScheme`, `setColorScheme` (AdwStyleManager), `addChangeListener` | — |\n| `useColorScheme` | reactive theme | — |\n| `AppState` | `currentState` active/background, `addEventListener` | driven by the window's `is-active` |\n| `Alert` | `alert(title, message, buttons, options)` → Adw.AlertDialog | `cancel`/`destructive`/`isPreferred` styles |\n| `Linking` | `openURL`, `canOpenURL` (http/https/mailto/file), `getInitialURL` (null), `addEventListener(\"url\")` | system launcher; no deep-link delivery on desktop yet — \"url\" subscriptions never fire |\n| `InteractionManager` | `runAfterInteractions(task?)` (cancellable, then-able), `createInteractionHandle`/`clearInteractionHandle`, `addListener` | navigation transitions register interactions, so screen work deferred with `runAfterInteractions` waits for the push/pop slide |\n| `DevSettings` | `addMenuItem(title, handler)` (entries in the Dev Menu — Ctrl+Shift+D in `run-linux --dev`, the react-native-windows shortcut), `reload(reason?)` | silent no-ops in release builds, like RN |\n| `I18nManager` | `isRTL` (live: GTK's read of the locale text direction), `doLeftAndRightSwapInRTL`, `getConstants` | `allowRTL`/`forceRTL`/`swapLeftAndRightInRTL` are accepted no-ops (mobile persistence has no desktop store) |\n| `BackHandler` | `addEventListener(\"hardwareBackPress\")`, `exitApp` | no hardware back key on desktop — subscriptions are honored but nothing fires them yet |\n| `Animated` | `Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity) | `useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway |\n| `Easing` | linear/ease/quad/cubic/in/out/inOut/bezier | — |\n| `version` | package version | extension |\n\nStyles (which keys go where and what is unsupported) — [style system table](../packages/react-native-gtkx/src/style/README.md).";
943
+ readonly text: "| Export | Supported | Differences |\n| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `StyleSheet` | `create`, `flatten`, `compose`, `absoluteFill(Object)`, `hairlineWidth` | — |\n| `PlatformColor` | Adwaita variables: `PlatformColor(\"accent-bg-color\")` → `var(--...)`, `@named` | names are Adwaita, not iOS/Android |\n| `AppRegistry` | `registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,actionAccels,breakpoints,applicationActions,windowActions,windowControllers})`, `getAppKeys` | desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `actionAccels` binds accelerators to action names on the `GtkApplication`; `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise). **`applicationActions`/`windowActions`/`windowControllers` are deprecated** — reach for [`<ApplicationActions>`/`<WindowActions>`/`<WindowControllers>`](platform-layer.md#actions-and-shortcuts-declared-in-the-app-tree) instead; they still work unchanged |\n| `Platform` | `OS: \"linux\"`, `Version` (GTK), `select` (linux → native → default), `isTV`, `isTesting` | — |\n| `Dimensions` | `get(\"window\"/\"screen\")`, `addEventListener(\"change\")` | main window only (transient windows are ignored) |\n| `useWindowDimensions` | reactive main-window dimensions | — |\n| `Appearance` | `getColorScheme`, `setColorScheme` (AdwStyleManager), `addChangeListener` | — |\n| `useColorScheme` | reactive theme | — |\n| `AppState` | `currentState` active/background, `addEventListener` | driven by the window's `is-active` |\n| `Alert` | `alert(title, message, buttons, options)` → Adw.AlertDialog | `cancel`/`destructive`/`isPreferred` styles |\n| `Linking` | `openURL`, `canOpenURL` (http/https/mailto/file), `getInitialURL` (null), `addEventListener(\"url\")` | system launcher; no deep-link delivery on desktop yet — \"url\" subscriptions never fire |\n| `InteractionManager` | `runAfterInteractions(task?)` (cancellable, then-able), `createInteractionHandle`/`clearInteractionHandle`, `addListener` | navigation transitions register interactions, so screen work deferred with `runAfterInteractions` waits for the push/pop slide |\n| `DevSettings` | `addMenuItem(title, handler)` (entries in the Dev Menu — Ctrl+Shift+D in `run-linux --dev`, the react-native-windows shortcut), `reload(reason?)` | silent no-ops in release builds, like RN |\n| `I18nManager` | `isRTL` (live: GTK's read of the locale text direction), `doLeftAndRightSwapInRTL`, `getConstants` | `allowRTL`/`forceRTL`/`swapLeftAndRightInRTL` are accepted no-ops (mobile persistence has no desktop store) |\n| `BackHandler` | `addEventListener(\"hardwareBackPress\")`, `exitApp` | no hardware back key on desktop — subscriptions are honored but nothing fires them yet |\n| `PanResponder` | `create(config)` -> `panHandlers` (spread onto a `View`), full `gestureState` (`dx`/`dy`, `vx`/`vy`, `x0`/`y0`, `moveX`/`moveY`, `numberActiveTouches`) — **react-native's own file, vendored unmodified** (MIT, `Libraries/Interaction/PanResponder.js`), running on our reproduction of RN's `touchHistory` store | multi-touch `gestureState` is single-touch here (one pointer), and `onShouldBlockNativeResponder`'s return value is not consumed yet |\n| `Animated` | `Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity), `ValueXY` (`setValue`/`setOffset`/`flattenOffset`/`extractOffset`, `getLayout`, `getTranslateTransform`) — the value a `PanResponder` drag writes to | `useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway; `Animated.event` is not implemented — write the value directly (`pan.setValue({x: g.dx, y: g.dy})`), which is what it would do |\n| `Easing` | linear/ease/quad/cubic/in/out/inOut/bezier | — |\n| `version` | package version | extension |\n\nStyles (which keys go where and what is unsupported) — [style system table](../packages/react-native-gtkx/src/style/README.md). Includes `boxShadow` (RN 0.76) and `outlineColor`/`outlineOffset`/`outlineStyle`/`outlineWidth` (RN 0.77): both are what Adwaita's own theme uses for the `.card`/`.boxed-list` frame and for every focus ring, so they are the difference between a React Native style approximating the platform look and reproducing it — see [research/react-native-first-showcase.md](research/react-native-first-showcase.md).";
938
944
  }, {
939
945
  readonly doc: "docs/api.md";
940
946
  readonly heading: "Key differences from React Native (summary)";
@@ -942,11 +948,11 @@ export declare const DOC_CHUNKS: readonly [{
942
948
  }, {
943
949
  readonly doc: "docs/api.md";
944
950
  readonly heading: "Navigation (`react-native-gtkx/navigation`)";
945
- readonly text: "A [react-navigation](https://reactnavigation.org) stack navigator backed by\n`Adw.NavigationView` — native Adwaita page transitions, the HeaderBar back\nbutton and back gestures stay in sync with react-navigation state (the\nreact-native-windows / native-stack model). Requires the optional peer\n`@react-navigation/native` (v8).\n\n`@react-navigation/native@8` itself peers on `react-native: \"*\"` (unlike\n`@react-navigation/core@8`, which has no react-native peer at all). If your\napp has no `react-native` package anywhere in its tree — a vite+gtkx app\nwith no Metro side, exactly what `examples/gallery` demonstrates —\n`npm install` will print an unmet-peer-dependency warning for it. This is\nharmless: react-native-gtkx never imports anything from the `react-native`\npackage, so nothing actually needs it at runtime; the warning is npm being\nstrict about a peer range upstream declared loosely (`\"*\"` — any version\nsatisfies it, npm just wants the package present at all).\n\n```tsx\nimport { NavigationContainer } from \"@react-navigation/native\"\nimport { createStackNavigator } from \"react-native-gtkx/navigation\"\n\n// Run the app with chrome: \"content\" — the navigator's HeaderBars ARE the\n// window chrome (the default system chrome would add a second titlebar):\n// AppRegistry.runApplication(name, { ..., chrome: \"content\" })\n\nconst Stack = createStackNavigator()\n\nconst App = () => (\n <NavigationContainer>\n <Stack.Navigator>\n <Stack.Screen\n name=\"Home\"\n component={HomeScreen}\n />\n <Stack.Screen\n name=\"Details\"\n component={DetailsScreen}\n options={{ title: \"Details page\" }}\n />\n </Stack.Navigator>\n </NavigationContainer>\n)\n```\n\n- Screen `options`: `title` (HeaderBar title, defaults to the route name),\n `headerShown` (default true).\n- `createSidebarNavigator` — the desktop drawer equivalent on\n `Adw.NavigationSplitView`: a persistent native sidebar (`AdwActionRow`\n per screen, in a GtkListBox with Adwaita `navigation-sidebar` styling)\n selects between parallel screens (TabRouter semantics). Navigator prop\n `sidebarTitle`; screen `options`: `title`, `icon` (Adwaita symbolic icon\n name for the row's prefix), `color` (a CSS color for a colored-dot\n prefix instead of `icon` — the two are mutually exclusive per row,\n `color` wins if both are set), `count` (a badge suffix, hidden when 0 or\n unset). Run the app with `chrome: \"content\"` so the split view's\n HeaderBars are the window chrome (`examples/gallery` is built on it).\n Navigator prop `headerButtons` packs declarative native buttons into the\n content HeaderBar end (`{id, icon, tooltip, onPress}`, `icon` is an\n Adwaita symbolic name) — the gallery's color-scheme toggle uses it.\n Navigator prop `collapseWidth` (sp): below this width the split view\n collapses to the sidebar or the content pane alone, through a native\n `Adw.Breakpoint` wrapping the view in an `AdwBreakpointBin` — NOT a\n `useWindowDimensions` conditional (see docs/platform-layer.md, \"Two ways\n to react to size\"); the property flip happens inside GTK's own\n allocation pass, costing no React render for the resize itself. Unset by\n default — no `AdwBreakpointBin` is mounted at all, so existing consumers\n see no behavior change. Any route becoming active while collapsed\n reveals content (`AdwNavigationSplitView.showContent`, a plain native\n property write, not React state) — a row click OR a programmatic\n `navigate()`/`jumpTo()`; the native back button that then appears\n reverses it. Re-selecting the same, already-active row after that also\n reveals content again — GTK's `row-selected` does not refire for a\n re-click with no selection change, so this is driven by `row-activated`\n (fires on every click) in addition. The reverse direction — the split\n view's own back button, Escape or back gesture hiding content again — is\n observed too: it fires a `sidebarShown` event\n (`navigation.addListener(\"sidebarShown\", …)`) on the currently active\n route, the same event-map protocol `createStackNavigator`'s\n `transitionStart`/`transitionEnd` use. Nothing in react-navigation state\n changes when this fires — TabRouter has no \"closed\" concept, the same\n route stays focused, only the pane did — so it exists purely for an app\n that wants to react (`examples/tasks-nav`'s `ContentScreen` resets its\n own in-screen \"open task\" state on it). Never fired for content being\n revealed (that direction is already an ordinary state change) or when\n `collapseWidth` is unset. Resizing back above `collapseWidth` and then\n back below it again does NOT reset `showContent` or the selection —\n confirmed empirically, not assumed — both simply persist across the\n round trip, the same size-class behavior a mobile master-detail app\n relies on; see docs/research/navigation-extensibility.md for the\n evidence.\n- Sidebar navigator props `minWidth` / `minHeight` (px, default 360×294 —\n GNOME's own adaptive floor): the narrowest size this navigator's UI\n supports, applied to the `AdwBreakpointBin` that `collapseWidth` mounts.\n Ignored when `collapseWidth` is unset, since no bin exists then. Adwaita\n cannot measure a breakpoint bin — what it contains changes with the\n breakpoints — so the bin reports a minimum of ZERO and warns that\n `width-request`/`height-request` must be set. Under `chrome: \"content\"`\n the bin is the window's own child, so that zero IS the window's floor:\n the window resizes straight past what the pane inside can draw, and\n Adwaita clips the pane instead of adapting it (\"AdwNavigationSplitView\n exceeds AdwBreakpointBin width: requested 469 px, 360 px available\" in\n the journal, felt as a list running off the right edge with its trailing\n controls cut away). An app whose content HeaderBar needs more than the\n default must raise it — measure the pane rather than guessing: a\n segmented control as `headerTitle` costs ~110px on its own and, unlike a\n title label, cannot ellipsize. `examples/tasks-nav` passes `480` for\n exactly that reason; the value stays below its `collapseWidth`, so the\n collapsed layout is still fully reachable.\n- Sidebar screen options `headerLeft` / `headerRight` / `headerTitle`:\n `() => ReactNode` — the content HeaderBar's own start/end/title, per\n screen, on top of the one navigator-wide default. This is what lets one\n screen's header change shape with ITS OWN selection (a filter toggle\n group for a list, a back button plus star/trash for an open item):\n call `navigation.setOptions({ headerLeft, headerRight, headerTitle })`\n from inside the screen, in an effect keyed on whatever local state\n decides its shape — no stack involved, and no new navigator API beyond\n the options themselves (`useNavigationBuilder` already re-resolves\n descriptor options on every `setOptions` call). `headerTitle` replaces\n the HeaderBar's title widget outright (unset, the page's own title\n shows automatically, as before). A screen's own `headerButtons`\n (`HeaderButton[]`, same shape as the navigator prop) replaces the\n navigator-level default entirely for that screen. **Caveat, found\n while testing this**: `setOptions` MERGES into the previously resolved\n options rather than replacing them — a call that omits `headerRight`\n does not clear a `headerRight` a PREVIOUS call set, it leaves it in\n place. A screen that flips between shapes must give every one of these\n four keys an explicit value (`undefined` counts as a real overwrite; an\n absent key does not) on every call, not just the ones currently in use.\n- Sidebar navigator prop `sidebarContent`:\n `(props: SidebarContentProps) => ReactNode` — replaces the ENTIRE sidebar\n pane's body, for a sidebar that needs sections, a search field, a footer,\n or anything a flat list of rows cannot express. The sidebar's children\n stop being \"one row per screen\": you draw what you like, and navigation\n is just the `jumpTo` you were handed. `SidebarContentProps` carries\n `routes` (key, name, resolved options, title, `focused`), `focusedIndex`\n and `jumpTo(name)` — use those rather than dispatching yourself, so\n selection cannot drift from navigation state. The pane's AdwHeaderBar and\n `sidebarTitle` still belong to the navigator: this is the body under it,\n not the chrome. Mounted as React Native content (a layout root filling\n the pane); a sidebar built from GTK widgets wraps its own tree in\n `WidgetContent`, the same escape hatch `contentLayout: \"widget\"` is for a\n screen body. Reach for `sidebarRow` first if you only want a different\n ROW — it keeps the navigator's list and everything attached to it.\n- Sidebar screen option `sidebarRow`: `() => ReactNode` — draw the row\n yourself instead of letting `title`/`icon`/`color`/`count` compose one.\n Those four are a convenience, not the ceiling: they build an\n `AdwActionRow`, which brings Adwaita's own row metrics with it, so an app\n wanting a different shape, density or height had nothing to reach for.\n Return anything a `GtkListBoxRow` can hold — React Native content, GTK\n widgets, a differently-configured Adwaita row. The navigator keeps owning\n row BEHAVIOUR (selection, click → `jumpTo`, staying in step with\n navigation state, the collapsed reveal), so a custom row cannot drift out\n of sync with the router; only what is drawn changes. A screen that passes\n none of `icon`/`color`/`count` gets a compact `GtkListBoxRow` + label\n automatically — `AdwActionRow`'s height is right when there IS a prefix\n and a count to lay out and pure cost when there is not.\n- Sidebar screen option `contentLayout`: `\"react-native\"` (default) or\n `\"widget\"` — what the screen's body IS. The default mounts it in a Yoga\n layout root that fills the pane, so `<View style={{ flex: 1 }}>` behaves\n the way it does anywhere else. `\"widget\"` packs the body into the page\n directly, with no layout root in between, for a screen whose body is a\n GTK widget tree (a `GtkScrolledWindow` around an `AdwClamp` around a\n `.boxed-list` `GtkListBox`, say): GTK's own sizing — `vexpand`, a list's\n natural height — then applies normally. **Under the default a widget tree\n collapses instead**, and quietly: every widget becomes a single Yoga LEAF\n measured for its own natural size, so a container renders its first child,\n drops the rest, and reports the ~1px it can shrink to, with no error\n anywhere. `examples/tasks-nav` is built this way. Mixing is per screen,\n not per subtree — a `\"widget\"` screen that wants React Native content\n somewhere inside it wraps that part in `SlotContent` itself.\n- Stack screen options `headerLeft` / `headerRight`: `() => ReactNode` —\n real RN content in the HeaderBar (inputs included), hosted by an\n intrinsic-size root; `headerButtons` render after `headerRight`\n (hn-app's header search filter is the demo).\n- Stack screen option `gestureEnabled: false` disables the native back\n button, Escape and the back gesture for that screen (the page's\n Adwaita `can-pop`); a programmatic `goBack` still pops. `usePreventRemove`\n works through the same mechanism — a prevented route reports\n `can-pop: false`, so no native pop can race react-navigation state; the\n route pops once the app lifts the guard (e.g. after its own\n confirmation dialog).\n- Stack screen option `animation` maps onto `Adw.NavigationView`'s\n `animate-transitions` — GTK has exactly one transition style, not a\n choice of styles like iOS/Android, so the option collapses to a\n boolean: `\"none\"` turns transitions off, any other value (including\n native-stack's own style names, e.g. `\"slide_from_bottom\"`, `\"fade\"`)\n turns them on, with the standard Adwaita transition rather than the\n one asked for. Requesting a specific type still animates — it is not\n silently treated as `\"none\"` — and warns once in development.\n `animate-transitions` is a property of the whole view, not a per-page\n one, so there is no per-screen granularity to offer: the value used is\n read from whichever screen is currently on top of the visible stack,\n recomputed on every navigation. Setting it once via `screenOptions`\n (the same value for every screen) is the reliable way to use this —\n the per-screen case only matters if different screens genuinely\n disagree, and even then only the active one's value is observed.\n Interactive swipe-back gestures always animate regardless of this\n setting — Adwaita's own behavior, not overridable here.\n- The factories are typed: `createStackNavigator<ParamList>()` gives\n typed `Screen` configs and `StackScreenProps<ParamList, Route>` for\n screen components (`SidebarScreenProps` likewise).\n- The stack navigator emits `transitionStart` / `transitionEnd` on a\n screen's `navigation` object, matching `@react-navigation/stack` and\n `@react-navigation/native-stack` exactly: `{ data: { closing: boolean } }`,\n `closing: false` for the screen being pushed in, `closing: true` for the\n screen being popped out. A screen that stays mounted without actually\n entering or leaving (e.g. the screen underneath a push) gets neither\n event, same as upstream. Two things worth knowing before relying on\n timing:\n - **`transitionEnd` is tied to `AdwNavigationPage`'s own `shown`/`hidden`\n signals** — contrary to an earlier version of this page, Adwaita DOES\n expose a transition-finished signal (four of them, in fact: `showing`,\n `shown`, `hiding`, `hidden`, all per-page). `transitionEnd` on the\n entering screen fires on that screen's `shown`; on the leaving screen\n it fires on `hidden`. `transitionDuration` (default 400 ms) is a\n fallback only, used when a page's own signal never arrives — a\n signal-less environment, or a page skipped entirely by a multi-hop\n pop (popping past an intermediate screen never fires anything on it,\n since it was never the one actually on screen during the transition).\n When transitions are not animated, the real signals still fire —\n immediately — so `transitionEnd` is not delayed by the fallback\n window either.\n - **Native pops do not fire these events at all today.** A user-driven\n pop (the Adwaita back button, Escape, the back gesture) is handled by\n the widget itself before this package's code is told about it, so\n there is nothing to hook a `transitionStart` into. Only\n programmatic navigation (`navigate`, `goBack`, `dispatch`, …) fires\n `transitionStart`/`transitionEnd`.\n- The sidebar navigator emits `sidebarShown` (`{ data: undefined }`) on a\n screen's `navigation` object — the collapsed-mode counterpart of a native\n pop, and the one case where a native, user-driven interaction (the split\n view's own back button, Escape, the back gesture) DOES get an event: the\n widget-level property that changes (`showContent`) has no\n react-navigation state behind it at all, so there is no state change for\n an app to observe any other way. Fired on the active route only when\n `showContent` goes from shown back to hidden, and only while\n `collapseWidth` is set; never fired for content being revealed (that\n already shows up as an ordinary focused-route change).\n- The rest of the react-navigation surface — `useNavigation`, `useRoute`,\n `useFocusEffect`, `useIsFocused`, `useNavigationContainerRef`,\n `CommonActions`, `StackActions`, `usePreventRemove`, `NavigationContainer`\n and everything else — comes from `@react-navigation/native` directly, not\n from this package. **Breaking change**: earlier versions re-exported a\n subset of these names from `react-native-gtkx/navigation`; the re-export\n was removed because it was never complete (anything beyond the subset\n still required importing from `@react-navigation/native`, so it was one\n more place to look rather than a convenience). This package's navigation\n entry point now exports exactly its own surface: `createStackNavigator`,\n `createSidebarNavigator`, and the option/prop types around them.\n- Each screen mounts its own layout root inside the page: the page's\n content allocation is that screen's viewport.\n- Differences from `@react-navigation/native-stack`: `headerRight`/custom\n header widgets are not supported yet; deep-link \"url\" events never fire\n on desktop (see `Linking`).";
951
+ readonly text: "A [react-navigation](https://reactnavigation.org) stack navigator backed by\n`Adw.NavigationView` — native Adwaita page transitions, the HeaderBar back\nbutton and back gestures stay in sync with react-navigation state (the\nreact-native-windows / native-stack model). Requires the optional peer\n`@react-navigation/native` (v8).\n\n`@react-navigation/native@8` itself peers on `react-native: \"*\"` (unlike\n`@react-navigation/core@8`, which has no react-native peer at all). If your\napp has no `react-native` package anywhere in its tree — a vite+gtkx app\nwith no Metro side, exactly what `examples/gallery` demonstrates —\n`npm install` will print an unmet-peer-dependency warning for it. This is\nharmless: react-native-gtkx never imports anything from the `react-native`\npackage, so nothing actually needs it at runtime; the warning is npm being\nstrict about a peer range upstream declared loosely (`\"*\"` — any version\nsatisfies it, npm just wants the package present at all).\n\n```tsx\nimport { NavigationContainer } from \"@react-navigation/native\"\nimport { createStackNavigator } from \"react-native-gtkx/navigation\"\n\n// Run the app with chrome: \"content\" — the navigator's HeaderBars ARE the\n// window chrome (the default system chrome would add a second titlebar):\n// AppRegistry.runApplication(name, { ..., chrome: \"content\" })\n\nconst Stack = createStackNavigator()\n\nconst App = () => (\n <NavigationContainer>\n <Stack.Navigator>\n <Stack.Screen\n name=\"Home\"\n component={HomeScreen}\n />\n <Stack.Screen\n name=\"Details\"\n component={DetailsScreen}\n options={{ title: \"Details page\" }}\n />\n </Stack.Navigator>\n </NavigationContainer>\n)\n```\n\n- Screen `options`: `title` (HeaderBar title, defaults to the route name),\n `headerShown` (default true).\n- `createSidebarNavigator` — the desktop drawer equivalent on\n `Adw.NavigationSplitView`: a persistent native sidebar (`AdwActionRow`\n per screen, in a GtkListBox with Adwaita `navigation-sidebar` styling)\n selects between parallel screens (TabRouter semantics). Navigator prop\n `sidebarTitle`; screen `options`: `title`, `icon` (Adwaita symbolic icon\n name for the row's prefix), `color` (a CSS color for a colored-dot\n prefix instead of `icon` — the two are mutually exclusive per row,\n `color` wins if both are set), `count` (a badge suffix, hidden when 0 or\n unset). Run the app with `chrome: \"content\"` so the split view's\n HeaderBars are the window chrome (`examples/gallery` is built on it).\n Navigator prop `headerButtons` packs declarative native buttons into the\n content HeaderBar end (`{id, icon, tooltip, onPress}`, `icon` is an\n Adwaita symbolic name) — the gallery's color-scheme toggle uses it.\n Navigator prop `collapseWidth` (sp): below this width the split view\n collapses to the sidebar or the content pane alone, through a native\n `Adw.Breakpoint` wrapping the view in an `AdwBreakpointBin` — NOT a\n `useWindowDimensions` conditional (see docs/platform-layer.md, \"Two ways\n to react to size\"); the property flip happens inside GTK's own\n allocation pass, costing no React render for the resize itself. Unset by\n default — no `AdwBreakpointBin` is mounted at all, so existing consumers\n see no behavior change. Any route becoming active while collapsed\n reveals content (`AdwNavigationSplitView.showContent`, a plain native\n property write, not React state) — a row click OR a programmatic\n `navigate()`/`jumpTo()`; the native back button that then appears\n reverses it. Re-selecting the same, already-active row after that also\n reveals content again — GTK's `row-selected` does not refire for a\n re-click with no selection change, so this is driven by `row-activated`\n (fires on every click) in addition. The reverse direction — the split\n view's own back button, Escape or back gesture hiding content again — is\n observed too: it fires a `sidebarShown` event\n (`navigation.addListener(\"sidebarShown\", …)`) on the currently active\n route, the same event-map protocol `createStackNavigator`'s\n `transitionStart`/`transitionEnd` use. Nothing in react-navigation state\n changes when this fires — TabRouter has no \"closed\" concept, the same\n route stays focused, only the pane did — so it exists purely for an app\n that wants to react (`examples/tasks-nav`'s `ContentScreen` resets its\n own in-screen \"open task\" state on it). Never fired for content being\n revealed (that direction is already an ordinary state change) or when\n `collapseWidth` is unset. Resizing back above `collapseWidth` and then\n back below it again does NOT reset `showContent` or the selection —\n confirmed empirically, not assumed — both simply persist across the\n round trip, the same size-class behavior a mobile master-detail app\n relies on; see docs/research/navigation-extensibility.md for the\n evidence.\n- **Which rung to reach for.** Three ways to put content in the sidebar,\n cheapest first — the same ladder react-navigation's own `tabBarIcon` →\n `drawerLabel` → `drawerContent` climbs: (1) `title`/`icon`/`color`/`count`\n above — the convenience; composes an `AdwActionRow`. (2) `sidebarRow`\n (screen option, below) — draw one row yourself; the navigator keeps the\n list and everything attached to it (selection, click → `jumpTo`, staying\n in step with navigation state, the collapsed reveal). (3) `sidebarContent`\n (navigator prop, below) — draw the whole pane, routing surface included.\n The reason rungs 2 and 3 exist at all, plainly: **`AdwActionRow` carries\n Adwaita's OWN row metrics, not a default this package picked** — measured\n at roughly 104px per row (with a prefix and/or count laid out) against\n ~40px for a plain title-only row — and nothing passed to\n `title`/`icon`/`color`/`count` changes that height. A screen on rung 1\n has no lever for it; wanting a different height or density means climbing\n to `sidebarRow` or `sidebarContent` instead.\n- Sidebar navigator props `minWidth` / `minHeight` (px, default 360×294 —\n GNOME's own adaptive floor): the narrowest size this navigator's UI\n supports, applied to the `AdwBreakpointBin` that `collapseWidth` mounts.\n Ignored when `collapseWidth` is unset, since no bin exists then. Adwaita\n cannot measure a breakpoint bin — what it contains changes with the\n breakpoints — so the bin reports a minimum of ZERO and warns that\n `width-request`/`height-request` must be set. Under `chrome: \"content\"`\n the bin is the window's own child, so that zero IS the window's floor:\n the window resizes straight past what the pane inside can draw, and\n Adwaita clips the pane instead of adapting it (\"AdwNavigationSplitView\n exceeds AdwBreakpointBin width: requested 469 px, 360 px available\" in\n the journal, felt as a list running off the right edge with its trailing\n controls cut away). An app whose content HeaderBar needs more than the\n default must raise it — measure the pane rather than guessing: a\n segmented control as `headerTitle` costs ~110px on its own and, unlike a\n title label, cannot ellipsize. `examples/tasks-nav` passes `480` for\n exactly that reason; the value stays below its `collapseWidth`, so the\n collapsed layout is still fully reachable.\n- Sidebar screen options `headerLeft` / `headerRight` / `headerTitle`:\n `() => ReactNode` — the content HeaderBar's own start/end/title, per\n screen, on top of the one navigator-wide default. This is what lets one\n screen's header change shape with ITS OWN selection (a filter toggle\n group for a list, a back button plus star/trash for an open item):\n call `navigation.setOptions({ headerLeft, headerRight, headerTitle })`\n from inside the screen, in an effect keyed on whatever local state\n decides its shape — no stack involved, and no new navigator API beyond\n the options themselves (`useNavigationBuilder` already re-resolves\n descriptor options on every `setOptions` call). `headerTitle` replaces\n the HeaderBar's title widget outright (unset, the page's own title\n shows automatically, as before). A screen's own `headerButtons`\n (`HeaderButton[]`, same shape as the navigator prop) replaces the\n navigator-level default entirely for that screen. **Caveat, found\n while testing this**: `setOptions` MERGES into the previously resolved\n options rather than replacing them — a call that omits `headerRight`\n does not clear a `headerRight` a PREVIOUS call set, it leaves it in\n place. A screen that flips between shapes must give every one of these\n four keys an explicit value (`undefined` counts as a real overwrite; an\n absent key does not) on every call, not just the ones currently in use.\n- Sidebar screen option `sidebarRow`: `() => ReactNode` — draw the row\n yourself instead of letting `title`/`icon`/`color`/`count` compose one.\n Those four are a convenience, not the ceiling: they build an\n `AdwActionRow`, which brings Adwaita's own row metrics with it, so an app\n wanting a different shape, density or height had nothing to reach for.\n Return anything a `GtkListBoxRow` can hold — React Native content, GTK\n widgets, a differently-configured Adwaita row. The navigator keeps owning\n row BEHAVIOUR (selection, click → `jumpTo`, staying in step with\n navigation state, the collapsed reveal), so a custom row cannot drift out\n of sync with the router; only what is drawn changes. A screen that passes\n none of `icon`/`color`/`count` gets a compact `GtkListBoxRow` + label\n automatically — `AdwActionRow`'s height is right when there IS a prefix\n and a count to lay out and pure cost when there is not. The next rung up\n is `sidebarContent`, below, for replacing the whole pane rather than one\n row.\n- Sidebar navigator prop `sidebarContent`:\n `(props: SidebarContentProps) => ReactNode` — replaces the ENTIRE sidebar\n pane's body, for a sidebar that needs sections, a search field, a footer,\n or anything a flat list of rows cannot express. The sidebar's children\n stop being \"one row per screen\": you draw what you like, and navigation\n is just the `jumpTo` you were handed. `SidebarContentProps` carries\n `routes` (key, name, resolved options, title, `focused`), `focusedIndex`\n and `jumpTo(name)` — use those rather than dispatching yourself, so\n selection cannot drift from navigation state. The pane's AdwHeaderBar and\n `sidebarTitle` still belong to the navigator: this is the body under it,\n not the chrome. Mounted as React Native content (a layout root filling\n the pane); a sidebar built from GTK widgets wraps its own tree in\n `WidgetContent`, the same escape hatch `contentLayout: \"widget\"` is for a\n screen body. Reach for `sidebarRow` (above) first if you only want a\n different ROW — it keeps the navigator's list and everything attached to\n it; this one hands over the whole pane, routing included. A sidebar with\n a search field above the list and a footer below it, still driven by the\n navigator's own routing:\n\n ```tsx\n <Sidebar.Navigator\n sidebarContent={({ routes, focusedIndex, jumpTo }) => (\n <View style={{ flex: 1 }}>\n <SearchField onSubmit={filterRoutes} />\n <ScrollView style={{ flex: 1 }}>\n {routes.map((route, index) => (\n <Pressable\n key={route.key}\n onPress={() => jumpTo(route.name)}\n >\n <Text\n style={{\n padding: 8,\n fontWeight: index === focusedIndex ? \"700\" : \"400\",\n }}\n >\n {route.title}\n </Text>\n </Pressable>\n ))}\n </ScrollView>\n <StorageUsageFooter />\n </View>\n )}\n >\n <Sidebar.Screen\n name=\"Inbox\"\n component={InboxScreen}\n />\n <Sidebar.Screen\n name=\"Trash\"\n component={TrashScreen}\n />\n </Sidebar.Navigator>\n ```\n\n `route.title` is already resolved (`options.title`, falling back to the\n route name) — no need to read `options.title` yourself. `jumpTo` reveals\n the content pane when collapsed, same as a native row click; the\n navigator, not this callback, decides that.\n\n- Sidebar navigator props `sidebarHeaderLeft` / `sidebarHeaderRight` /\n `sidebarHeaderTitle`: `() => ReactNode` — the SIDEBAR pane's own\n AdwHeaderBar start/end/title, the exact counterparts of the content\n header's `headerLeft`/`headerRight`/`headerTitle`. Until these existed the\n sidebar header was a hard-coded `<AdwHeaderBar />` and `sidebarTitle` (a\n plain string) was the only thing an app could set on it at all, so a\n sidebar's own \"new item\" action — where GNOME puts it, next to the pane\n title — had nowhere to go and ended up on the content header instead\n (`examples/tasks-nav` shipped with two indistinguishable `+` buttons for\n exactly this reason). `sidebarHeaderTitle` replaces the title widget the\n same way a screen's `headerTitle` does; unset, `sidebarTitle` renders as\n before. Content is mounted through the same `HeaderSlotContent` root the\n content header uses, so React Native content lays out as a horizontal,\n content-hugging cluster flush with natively packed buttons — do not\n hand-roll an `IntrinsicContent` here, a bare Yoga root defaults to\n `column` and pushes the window controls onto a second row. These are\n navigator PROPS rather than screen options on purpose: there is one\n sidebar pane shared by every screen, so its chrome sits at the level\n `sidebarTitle`/`sidebarContent` already do, and the `sidebar` prefix marks\n which header a name refers to. There is deliberately no\n `sidebarHeaderButtons` convenience mirroring `headerButtons` — arbitrary\n content is the primitive, and a one-button call site reads no better as a\n `{id, icon, tooltip, onPress}` record than as the `GtkButton` it already\n is; add it only if a real call site is worse without it.\n\n- Sidebar screen option `contentLayout`: `\"react-native\"` (default) or\n `\"widget\"` — what the screen's body IS. The default mounts it in a Yoga\n layout root that fills the pane, so `<View style={{ flex: 1 }}>` behaves\n the way it does anywhere else. `\"widget\"` packs the body into the page\n directly, with no layout root in between, for a screen whose body is a\n GTK widget tree (a `GtkScrolledWindow` around an `AdwClamp` around a\n `.boxed-list` `GtkListBox`, say): GTK's own sizing — `vexpand`, a list's\n natural height — then applies normally. **Under the default a widget tree\n collapses instead**, and quietly: every widget becomes a single Yoga LEAF\n measured for its own natural size, so a container renders its first child,\n drops the rest, and reports the ~1px it can shrink to, with no error\n anywhere. `examples/tasks-nav` is built this way. Mixing is per screen,\n not per subtree — a `\"widget\"` screen that wants React Native content\n somewhere inside it wraps that part in `SlotContent` itself.\n- Stack screen options `headerLeft` / `headerRight`: `() => ReactNode` —\n real RN content in the HeaderBar (inputs included), hosted by an\n intrinsic-size root; `headerButtons` render after `headerRight`\n (hn-app's header search filter is the demo).\n- Stack screen option `gestureEnabled: false` disables the native back\n button, Escape and the back gesture for that screen (the page's\n Adwaita `can-pop`); a programmatic `goBack` still pops. `usePreventRemove`\n works through the same mechanism — a prevented route reports\n `can-pop: false`, so no native pop can race react-navigation state; the\n route pops once the app lifts the guard (e.g. after its own\n confirmation dialog).\n- Stack screen option `animation` maps onto `Adw.NavigationView`'s\n `animate-transitions` — GTK has exactly one transition style, not a\n choice of styles like iOS/Android, so the option collapses to a\n boolean: `\"none\"` turns transitions off, any other value (including\n native-stack's own style names, e.g. `\"slide_from_bottom\"`, `\"fade\"`)\n turns them on, with the standard Adwaita transition rather than the\n one asked for. Requesting a specific type still animates — it is not\n silently treated as `\"none\"` — and warns once in development.\n `animate-transitions` is a property of the whole view, not a per-page\n one, so there is no per-screen granularity to offer: the value used is\n read from whichever screen is currently on top of the visible stack,\n recomputed on every navigation. Setting it once via `screenOptions`\n (the same value for every screen) is the reliable way to use this —\n the per-screen case only matters if different screens genuinely\n disagree, and even then only the active one's value is observed.\n Interactive swipe-back gestures always animate regardless of this\n setting — Adwaita's own behavior, not overridable here.\n- The factories are typed: `createStackNavigator<ParamList>()` gives\n typed `Screen` configs and `StackScreenProps<ParamList, Route>` for\n screen components (`SidebarScreenProps` likewise).\n- The stack navigator emits `transitionStart` / `transitionEnd` on a\n screen's `navigation` object, matching `@react-navigation/stack` and\n `@react-navigation/native-stack` exactly: `{ data: { closing: boolean } }`,\n `closing: false` for the screen being pushed in, `closing: true` for the\n screen being popped out. A screen that stays mounted without actually\n entering or leaving (e.g. the screen underneath a push) gets neither\n event, same as upstream. Two things worth knowing before relying on\n timing:\n - **`transitionEnd` is tied to `AdwNavigationPage`'s own `shown`/`hidden`\n signals** — contrary to an earlier version of this page, Adwaita DOES\n expose a transition-finished signal (four of them, in fact: `showing`,\n `shown`, `hiding`, `hidden`, all per-page). `transitionEnd` on the\n entering screen fires on that screen's `shown`; on the leaving screen\n it fires on `hidden`. `transitionDuration` (default 400 ms) is a\n fallback only, used when a page's own signal never arrives — a\n signal-less environment, or a page skipped entirely by a multi-hop\n pop (popping past an intermediate screen never fires anything on it,\n since it was never the one actually on screen during the transition).\n When transitions are not animated, the real signals still fire —\n immediately — so `transitionEnd` is not delayed by the fallback\n window either.\n - **Native pops do not fire these events at all today.** A user-driven\n pop (the Adwaita back button, Escape, the back gesture) is handled by\n the widget itself before this package's code is told about it, so\n there is nothing to hook a `transitionStart` into. Only\n programmatic navigation (`navigate`, `goBack`, `dispatch`, …) fires\n `transitionStart`/`transitionEnd`.\n- The sidebar navigator emits `sidebarShown` (`{ data: undefined }`) on a\n screen's `navigation` object — the collapsed-mode counterpart of a native\n pop, and the one case where a native, user-driven interaction (the split\n view's own back button, Escape, the back gesture) DOES get an event: the\n widget-level property that changes (`showContent`) has no\n react-navigation state behind it at all, so there is no state change for\n an app to observe any other way. Fired on the active route only when\n `showContent` goes from shown back to hidden, and only while\n `collapseWidth` is set; never fired for content being revealed (that\n already shows up as an ordinary focused-route change).\n- The rest of the react-navigation surface — `useNavigation`, `useRoute`,\n `useFocusEffect`, `useIsFocused`, `useNavigationContainerRef`,\n `CommonActions`, `StackActions`, `usePreventRemove`, `NavigationContainer`\n and everything else — comes from `@react-navigation/native` directly, not\n from this package. **Breaking change**: earlier versions re-exported a\n subset of these names from `react-native-gtkx/navigation`; the re-export\n was removed because it was never complete (anything beyond the subset\n still required importing from `@react-navigation/native`, so it was one\n more place to look rather than a convenience). This package's navigation\n entry point now exports exactly its own surface: `createStackNavigator`,\n `createSidebarNavigator`, and the option/prop types around them.\n- Each screen mounts its own layout root inside the page: the page's\n content allocation is that screen's viewport.\n- Differences from `@react-navigation/native-stack`: `headerRight`/custom\n header widgets are not supported yet; deep-link \"url\" events never fire\n on desktop (see `Linking`).";
946
952
  }, {
947
953
  readonly doc: "docs/api.md";
948
954
  readonly heading: "Svg";
949
- readonly text: "Vector graphics built from state, modeled on\n[react-native-svg](https://github.com/software-mansion/react-native-svg) (the\nde-facto standard RN mirrors) rather than invented from scratch — portable\ncode costs nothing to bring over. Drawing goes through `Gsk.Path`/\n`Gtk.Snapshot` on a single custom widget (`RnGtkxSvgNode`, `registerClass` +\nan overridden `snapshot()` vfunc — the same mechanism `RnGtkxLayout` and\n`RnGtkxViewBox` already use), not a rasterized image: for that, `Image`\nalready loads `.svg` files today (see the `Image` row above).\n\n**Not part of the main `react-native-gtkx` export surface** — unlike every\ncomponent in the table above, `Svg` and everything below are exported only\nfrom `react-native-gtkx/svg`, in the shape of the `react-native-svg` package\nitself. `react-native-svg` is a separate package on every other platform (RN\nhas no built-in `Svg`), so this project mirrors that split instead of adding\n`Svg` to the main entry, which would make code written against it fail to\ncompile anywhere else. See \"`react-native-svg` compatibility\" below for the\nexact import and how the alias resolves it.\n\n```tsx\nimport Svg, { Circle, G, Path, Rect } from \"react-native-svg\"\n\nconst Icon = () => (\n <Svg\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n >\n <Circle\n cx={12}\n cy={12}\n r={10}\n fill=\"#1c71d8\"\n />\n <Path\n d=\"M8 12 l3 3 l5 -6\"\n stroke=\"white\"\n strokeWidth={2}\n fill=\"none\"\n />\n </Svg>\n)\n```\n\n- **`Svg`**: `width`/`height` (or `style`) size it — a Yoga leaf like\n `Image`, sized entirely by style/flex, never by measuring the widget\n (nothing here is intrinsic-sized). `viewBox=\"minX minY width height\"` and\n `preserveAspectRatio` (`xMin/xMid/xMax` × `YMin/YMid/YMax`, `meet`/`slice`,\n `none`; default `xMidYMid meet`) reshape the internal coordinate system\n exactly like real SVG — Yoga never sees them. Content always clips to the\n allocated bounds (no `overflow: visible` opt-out).\n- **`Path`**: `d` is handed straight to `Gsk.Path.parse()`, which understands\n SVG path syntax natively — there is no path parser of our own.\n- **`Rect`** (`x`/`y`/`width`/`height`/`rx`/`ry`), **`Circle`**\n (`cx`/`cy`/`r`), **`Ellipse`** (`cx`/`cy`/`rx`/`ry`), **`Line`**\n (`x1`/`y1`/`x2`/`y2`, stroke-only — no `fill` prop at all, not even\n ignored), **`Polygon`**/**`Polyline`** (`points`, `\"x,y x,y …\"` or\n space-separated, closed/open respectively): each is a small geometry\n helper away from the same `d` syntax, so every shape ends up drawn through\n that one `Gsk.Path.parse()` call.\n- Every shape accepts `fill`/`stroke` (a static CSS color — hex/`rgb()`/\n `hsl()`/named/`transparent`/`none`, or `\"url(#id)\"` referencing a\n gradient; default `fill=\"black\"`, `stroke=\"none\"`, matching SVG),\n `fillRule` (`nonzero` | `evenodd`), `fillOpacity`/`strokeOpacity`/\n `opacity`, `strokeWidth`, `strokeLinecap`/`strokeLinejoin`,\n `strokeDasharray`, `strokeDashoffset`.\n- **`G`** groups children under an `opacity` and/or a `transform` string —\n `translate()`/`scale()`/`rotate()`/`rotate(a,cx,cy)`/`matrix()`, the plain\n SVG transform-list syntax (`matrix()` maps directly onto\n `Gsk.Transform.matrix2d()`); `skewX`/`skewY` and the structured\n `transform={[{translateX:...}]}` array form `Animated.View` accepts are\n not supported here.\n- **Gradients**: `<Defs>` holds `<LinearGradient id x1 y1 x2 y2>` /\n `<RadialGradient id cx cy r>` (fractions 0–1 by default —\n `gradientUnits=\"objectBoundingBox\"`, mapped against the shape's own\n `Gsk.Path.getBounds()`; `gradientUnits=\"userSpaceOnUse\"` uses the\n coordinates as-is instead), each with `<Stop offset stopColor\nstopOpacity>` children (`offset` accepts `0.5` or `\"50%\"`). `Defs` must be\n a direct child of `Svg` (nested `Defs` are not scanned). No\n `gradientTransform`, no `spreadMethod` beyond the default pad behavior.\n **Known limitation**: constructing a `Gsk.ColorStop` currently crashes in\n gtkx-rc2's native addon — verified through three independent construction\n paths (the generated constructor, its property setters, and a bypass that\n skips `ColorStop` entirely), all failing in the same compiled native code,\n so this is not fixable from application code. A gradient reference\n degrades to painting nothing for that fill/stroke (the same safe path as\n an unresolvable `url(#id)`) rather than crashing the app; the coordinate\n math itself is unaffected and unit-tested\n (`packages/react-native-gtkx/tests/unit/svg/gradient-geometry.test.ts`) —\n gradients will render as soon as this is fixed upstream, with no changes\n needed on either side.\n- **Animated**: the numeric props above (shape geometry, `opacity`,\n `strokeWidth`, `strokeDashoffset`) accept an `Animated.Value`/\n interpolation in place of a number. A tick mutates the widget's paint\n state directly and calls `queueDraw()` — the same bypass-React pattern\n `Animated.View` uses for `transform` (`setStoredTransform` +\n `queueAllocate`), just on its own invalidation channel since none of this\n touches Yoga. `G`'s `transform` string and `d`/`points` are not\n Animated-aware (they are strings, not numbers).\n- Not in scope: `<Text>`/`<TSpan>` on a path, `<Mask>`, `<ClipPath>`, SVG\n filters, `<Use>`/`<Symbol>`/`<Pattern>`, and rasterizing arbitrary SVG\n strings at runtime (`SvgXml` — `Image` already covers SVG **files**). None\n of these have a real consumer yet; `Path`/`Rect`/`Circle`/`Ellipse`/\n `Line`/`Polygon`/`Polyline`/`G` cover icons, charts and indicators, the\n overwhelming majority of real usage.";
955
+ readonly text: "Vector graphics built from state, modeled on\n[react-native-svg](https://github.com/software-mansion/react-native-svg) (the\nde-facto standard RN mirrors) rather than invented from scratch — portable\ncode costs nothing to bring over. Drawing goes through `Gsk.Path`/\n`Gtk.Snapshot` on a single custom widget (`RnGtkxSvgNode`, `registerClass` +\nan overridden `snapshot()` vfunc — the same mechanism `RnGtkxLayout` and\n`RnGtkxViewBox` already use), not a rasterized image: for that, `Image`\nalready loads `.svg` files today (see the `Image` row above).\n\n**Not part of the main `react-native-gtkx` export surface** — unlike every\ncomponent in the table above, `Svg` and everything below are exported only\nfrom `react-native-gtkx/svg`, in the shape of the `react-native-svg` package\nitself. `react-native-svg` is a separate package on every other platform (RN\nhas no built-in `Svg`), so this project mirrors that split instead of adding\n`Svg` to the main entry, which would make code written against it fail to\ncompile anywhere else. See \"`react-native-svg` compatibility\" below for the\nexact import and how the alias resolves it.\n\n```tsx\nimport Svg, { Circle, G, Path, Rect } from \"react-native-svg\"\n\nconst Icon = () => (\n <Svg\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n >\n <Circle\n cx={12}\n cy={12}\n r={10}\n fill=\"#1c71d8\"\n />\n <Path\n d=\"M8 12 l3 3 l5 -6\"\n stroke=\"white\"\n strokeWidth={2}\n fill=\"none\"\n />\n </Svg>\n)\n```\n\n- **`Svg`**: `width`/`height` (or `style`) size it — a Yoga leaf like\n `Image`, sized entirely by style/flex, never by measuring the widget\n (nothing here is intrinsic-sized). `viewBox=\"minX minY width height\"` and\n `preserveAspectRatio` (`xMin/xMid/xMax` × `YMin/YMid/YMax`, `meet`/`slice`,\n `none`; default `xMidYMid meet`) reshape the internal coordinate system\n exactly like real SVG — Yoga never sees them. Content always clips to the\n allocated bounds (no `overflow: visible` opt-out).\n- **`Path`**: `d` is handed straight to `Gsk.Path.parse()`, which understands\n SVG path syntax natively — there is no path parser of our own.\n- **`Rect`** (`x`/`y`/`width`/`height`/`rx`/`ry`), **`Circle`**\n (`cx`/`cy`/`r`), **`Ellipse`** (`cx`/`cy`/`rx`/`ry`), **`Line`**\n (`x1`/`y1`/`x2`/`y2`, stroke-only — no `fill` prop at all, not even\n ignored), **`Polygon`**/**`Polyline`** (`points`, `\"x,y x,y …\"` or\n space-separated, closed/open respectively): each is a small geometry\n helper away from the same `d` syntax, so every shape ends up drawn through\n that one `Gsk.Path.parse()` call.\n- Every shape accepts `fill`/`stroke` (a static CSS color — hex/`rgb()`/\n `hsl()`/named/`transparent`/`none`, or `\"url(#id)\"` referencing a\n gradient; default `fill=\"black\"`, `stroke=\"none\"`, matching SVG),\n `fillRule` (`nonzero` | `evenodd`), `fillOpacity`/`strokeOpacity`/\n `opacity`, `strokeWidth`, `strokeLinecap`/`strokeLinejoin`,\n `strokeDasharray`, `strokeDashoffset`.\n- **`G`** groups children under an `opacity` and/or a `transform` string —\n `translate()`/`scale()`/`rotate()`/`rotate(a,cx,cy)`/`matrix()`, the plain\n SVG transform-list syntax (`matrix()` maps directly onto\n `Gsk.Transform.matrix2d()`); `skewX`/`skewY` and the structured\n `transform={[{translateX:...}]}` array form `Animated.View` accepts are\n not supported here.\n- **Gradients**: `<Defs>` holds `<LinearGradient id x1 y1 x2 y2>` /\n `<RadialGradient id cx cy r>` (fractions 0–1 by default —\n `gradientUnits=\"objectBoundingBox\"`, mapped against the shape's own\n `Gsk.Path.getBounds()`; `gradientUnits=\"userSpaceOnUse\"` uses the\n coordinates as-is instead), each with `<Stop offset stopColor\nstopOpacity>` children (`offset` accepts `0.5` or `\"50%\"`). `Defs` must be\n a direct child of `Svg` (nested `Defs` are not scanned). No\n `gradientTransform`, no `spreadMethod` beyond the default pad behavior.\n An unresolvable `url(#id)` paints nothing for that fill/stroke rather\n than throwing.\n- **Animated**: the numeric props above (shape geometry, `opacity`,\n `strokeWidth`, `strokeDashoffset`) accept an `Animated.Value`/\n interpolation in place of a number. A tick mutates the widget's paint\n state directly and calls `queueDraw()` — the same bypass-React pattern\n `Animated.View` uses for `transform` (`setStoredTransform` +\n `queueAllocate`), just on its own invalidation channel since none of this\n touches Yoga. `G`'s `transform` string and `d`/`points` are not\n Animated-aware (they are strings, not numbers).\n- Not in scope: `<Text>`/`<TSpan>` on a path, `<Mask>`, `<ClipPath>`, SVG\n filters, `<Use>`/`<Symbol>`/`<Pattern>`, and rasterizing arbitrary SVG\n strings at runtime (`SvgXml` — `Image` already covers SVG **files**). None\n of these have a real consumer yet; `Path`/`Rect`/`Circle`/`Ellipse`/\n `Line`/`Polygon`/`Polyline`/`G` cover icons, charts and indicators, the\n overwhelming majority of real usage.";
950
956
  }, {
951
957
  readonly doc: "docs/api.md";
952
958
  readonly heading: "`react-native-svg` compatibility (`react-native-gtkx/svg`)";
@@ -962,15 +968,19 @@ export declare const DOC_CHUNKS: readonly [{
962
968
  }, {
963
969
  readonly doc: "docs/platform-layer.md";
964
970
  readonly heading: "React Native content inside GTK slots";
965
- readonly text: "| Export | Sizing | Use for |\n| ------------------ | ---------------------------- | ------------------------------------------------ |\n| `SlotContent` | fills the slot | a page body, a pane, a dialog body |\n| `IntrinsicContent` | sized by its own Yoga layout | an AdwHeaderBar slot, a toolbar area, a list row |";
971
+ readonly text: "| Export | Sizing | Use for |\n| ------------------ | ---------------------------- | ------------------------------------------------ |\n| `SlotContent` | fills the slot | a page body, a pane, a dialog body |\n| `IntrinsicContent` | sized by its own Yoga layout | an AdwHeaderBar slot, a toolbar area, a list row |\n\n`createSidebarNavigator`'s `sidebarRow` screen option (docs/api.md) wraps\nits content in exactly `IntrinsicContent` for this reason — a row is sized\nby what it holds, not stretched to fill the list.\n\n**Every content area inside a widget needs one of them.** A widget hands out\nrectangles two ways: as ordinary CHILDREN (a content area) and as SLOTS —\nproperties that take a widget, `topBar={…}`, `titleWidget={…}`, `sheet={…}`.\nWhich way a given area arrives is gtkx's business and moves between releases\n(rc.3 took the `content`/`child` props off single-child widgets and made that\ncontent a child), and it has never had anything to do with layout. Both are\nGTK's territory: the layout root is cleared on the way in, so a widget lands\nbare (what `WidgetContent` does by hand) and React Native content has to bring\nits own root.\n\n```tsx\n<AdwBottomSheet\n style={{ flex: 1 }}\n sheet={\n <IntrinsicContent>\n <View style={{ padding: 20, gap: 10 }}>…</View>\n </IntrinsicContent>\n }\n bottomBar={\n <IntrinsicContent>\n <View style={{ flexDirection: \"row\", gap: 8 }}>…</View>\n </IntrinsicContent>\n }\n>\n {/* the content area — a child under rc.3, and just as much a boundary */}\n <SlotContent>\n <View style={{ flex: 1, justifyContent: \"center\" }}>…</View>\n </SlotContent>\n</AdwBottomSheet>\n```\n\nForget the wrapper and you get an error naming the widget and where the\ncontent landed, not a wrong-looking window: without a root, content inside a\nwidget would join the ENCLOSING Yoga tree — laid out against the window's\nviewport while GTK hands it the widget's own rectangle.\n`examples/bottom-sheet` is that whole story in one screen.\n\nWhich of the two is yours to choose, and the platform deliberately does not\nguess: `AdwBottomSheet` alone FILLS in its content child but HUGS in both\n`sheet` and `bottomBar`. One widget, three content areas, two answers, with\nnothing in the name or the GIR type to tell them apart — the answer lives in\nthe widget's own layout code. Swapping them is visible immediately:\n`SlotContent` in a bottom bar collapses it to nothing (a filling root reports\na zero minimum, so a size-to-content area is told \"nothing\"), and\n`IntrinsicContent` around a content area leaves `flex: 1` with no viewport to\nfill.\n\nNote the two independent sizes here. `style={{ flex: 1 }}` on the widget is\nthe WIDGET's size in the surrounding React Native layout (a wrapped widget is\na Yoga leaf at its natural size until the style says otherwise); the wrapper\ninside each content area is the CONTENT's size inside the rectangle that\nwidget then hands out.";
972
+ }, {
973
+ readonly doc: "docs/platform-layer.md";
974
+ readonly heading: "Adwaita chrome, written in React Native";
975
+ readonly text: "The opposite direction from everything above: these create no widget an app\ncould not have created itself, because what they encode is a **look**, not a\nbehaviour. `docs/research/react-native-first-showcase.md` measured Adwaita's\n`.boxed-list` out of libadwaita's own compiled stylesheet and found it is a\nrounded, shadowed card whose rows carry a hairline separator, a hover tint\nand a press tint — every one of which is a React Native style prop. What an\napp should not have to do is rediscover the numbers, which are not obvious\n(the frame is a three-part `box-shadow`, not a border; the corner radii live\non the first and last ROW, not on the container) and which move when\nlibadwaita moves.\n\n| Export | What it is |\n| --------------- | ------------------------------------------------------------------------------------------- |\n| `List` | the `.boxed-list` frame — a `View` with the card background, radius and shadow |\n| `ListRow` | `AdwActionRow`'s layout and states (`title`/`subtitle`/`prefix`/`suffix`), on a `Pressable` |\n| `ListSeparator` | the hairline, for a `FlatList`'s `ItemSeparatorComponent` |\n| `rowPosition` | `(index, count)` → `\"first\" | \"middle\" | \"last\" | \"only\"`, since RN has no `:first-child` |\n| `Icon` | a **named** icon from the desktop icon theme |\n\n`Icon` is not `Image`: RN's `Image` takes a file path or URI, because on iOS\nand Android an icon is a bundled asset. Here it is a _name_ resolved against\nthe current icon theme at paint time, which recolours itself with the label\ncolour and follows the user's theme — nothing in `Image`'s contract can\nexpress that. The shape is the one RN apps already use\n(`<Icon name size />`), with the desktop icon theme behind it instead of a\nbundled font.\n\n`ListRow` does **not** yet do keyboard navigation between rows or draw a\nfocus ring. `GtkListBox` implements both as widget behaviour, and RN has no\nfocus model for `View` to hang them on — `Pressable`'s state callback is\n`{pressed, hovered}` with no `focused`. The ring itself is drawable\n(`outlineWidth`/`outlineColor`/`outlineStyle`/`outlineOffset`, see the style\ntable); what is missing is the state, not the paint.";
966
976
  }, {
967
977
  readonly doc: "docs/platform-layer.md";
968
978
  readonly heading: "GTK widgets, driven by React Native";
969
- readonly text: "Every `GtkWidget` subclass gtkx binds — 87 of them at last count, from\n`GtkBox` and `GtkButton` to `GtkColumnView` and `GtkEmojiChooser`. The list is\ngenerated, not hand-picked: `scripts/generate-widget-surface.ts` classifies\ngtkx's full binding by real GObject inheritance (see\n`scripts/widget-surface/classification.json` for the exact list gtkx binds\ntoday) and `src/gtk/widgets.generated.ts` is the committed result. Re-run the\ngenerator after a gtkx upgrade to pick up new widgets — it diffs against its\nown previous output and prints what changed.\n\nThey keep **every prop gtkx binds** and gain `style` and `onLayout`. Position\nand appearance both come from the style prop, exactly like anywhere else in\nReact Native:\n\n```tsx\n<View style={{ flexDirection: \"row\", gap: 8, alignItems: \"center\" }}>\n <GtkEntry\n style={{ flex: 1 }}\n placeholderText=\"Filter\"\n />\n <GtkButton\n style={{ width: 72, backgroundColor: \"#3584e4\", borderRadius: 6 }}\n label=\"Go\"\n />\n</View>\n```\n\nThe entry flexes, the button takes its width and its colour. The layout half\nof the style drives Yoga; the visual half becomes a GTK CSS class **on the\nwidget itself**, so the button really is blue, not a blue box behind a button.\nSet no size and the widget's own natural size wins.\n\n**Outside React Native layout they step aside.** The same `GtkButton` dropped\ninto a `AdwHeaderBar`'s `start` or a `AdwToolbarView`'s `topBar` — where there is no\nYoga tree to join — renders as the bare widget. One symbol, both worlds, no\nflag to remember.";
979
+ readonly text: "Every `GtkWidget` subclass gtkx binds — 86 of them at last count, from\n`GtkBox` and `GtkButton` to `GtkColumnView` and `GtkEmojiChooser`. The list is\ngenerated, not hand-picked: `scripts/generate-widget-surface.ts` classifies\ngtkx's full binding by real GObject inheritance (see\n`scripts/widget-surface/classification.json` for the exact list gtkx binds\ntoday) and `src/gtk/widgets.generated.ts` is the committed result. Re-run the\ngenerator after a gtkx upgrade to pick up new widgets — it diffs against its\nown previous output and prints what changed.\n\nThey keep **every prop gtkx binds** and gain `style` and `onLayout`. Position\nand appearance both come from the style prop, exactly like anywhere else in\nReact Native:\n\n```tsx\n<View style={{ flexDirection: \"row\", gap: 8, alignItems: \"center\" }}>\n <GtkEntry\n style={{ flex: 1 }}\n placeholderText=\"Filter\"\n />\n <GtkButton\n style={{ width: 72, backgroundColor: \"#3584e4\", borderRadius: 6 }}\n label=\"Go\"\n />\n</View>\n```\n\nThe entry flexes, the button takes its width and its colour. The layout half\nof the style drives Yoga; the visual half becomes a GTK CSS class **on the\nwidget itself**, so the button really is blue, not a blue box behind a button.\nSet no size and the widget's own natural size wins.\n\n**Outside React Native layout they step aside.** The same `GtkButton` dropped\ninto a `AdwHeaderBar`'s `start` or a `AdwToolbarView`'s `topBar` — where there is no\nYoga tree to join — renders as the bare widget. One symbol, both worlds, no\nflag to remember.";
970
980
  }, {
971
981
  readonly doc: "docs/platform-layer.md";
972
982
  readonly heading: "Unwrapped by necessity";
973
- readonly text: "Two families of widget are exported **raw** instead of wrapped, because a\nwrapper box around them would be invalid GTK rather than a convenience:\n\n- **toplevels** — `GtkWindow` and everything that derives it: every\n `Gtk*Dialog`, `GtkApplicationWindow`, `GtkAssistant`, `GtkShortcutsWindow`,\n and their Adwaita counterparts (`AdwWindow`, `AdwApplicationWindow`,\n `AdwAboutWindow`, `AdwMessageDialog`, `AdwPreferencesWindow`). A wrapper box\n around a window is not a layout, it is two windows.\n- **child-only widgets** — valid solely as the direct child of one specific\n parent. `GtkListBoxRow` and `GtkFlowBoxChild` (plus everything that derives\n them — every Adwaita preferences row, `AdwActionRow` included) are caught\n mechanically, by real inheritance. `AdwNavigationPage` and\n `AdwPreferencesPage` derive `Gtk.Widget` directly with no shared base to\n catch them mechanically, so they are a two-entry, doc-verified denylist\n instead — see `scripts/widget-surface/classify.ts` for the exact reasoning\n behind each.\n\n`GtkGestureClick` is a third, simpler case: an event controller, not a\nwidget at all, so it was never a candidate for wrapping in the first place.\n\nNothing here is unreachable — every raw export above is still exported,\nby name, from `react-native-gtkx/gtk` or `/adw`, exactly as gtkx binds it.";
983
+ readonly text: "Two families of widget are exported **raw** instead of wrapped, because a\nwrapper box around them would be invalid GTK rather than a convenience:\n\n- **toplevels** — everything that implements `GtkRoot`: `GtkWindow` and\n everything that derives it (every `Gtk*Dialog`, `GtkApplicationWindow`,\n `GtkAssistant`, `GtkShortcutsWindow`, and their Adwaita counterparts\n `AdwWindow`, `AdwApplicationWindow`, `AdwAboutWindow`, `AdwMessageDialog`,\n `AdwPreferencesWindow`) — plus `GtkDragIcon`, which derives `Gtk.Widget`\n directly and is a toplevel all the same. A wrapper box around a window is\n not a layout, it is two windows; and a drag icon inside one is a widget\n GTK cannot present at all. The rule is written against `GtkRoot` (the\n capability: owns its own surface, is presented, never parented) rather\n than against `Gtk.Window` (one familiar instance of it) precisely because\n `GtkDragIcon` is the case a class-shaped rule misses. `GtkPopover` sits on\n the other side of the line — a `GtkNative` but not a `GtkRoot`, and gtkx\n parents it with `gtk_popover_set_parent`, so it stays wrapped. Build a\n drag icon the way GTK does, from the drag itself\n (`Gtk.DragIcon.getForDrag(drag).setChild(…)`), not by mounting one.\n- **child-only widgets** — valid solely as the direct child of one specific\n parent. `GtkListBoxRow` and `GtkFlowBoxChild` (plus everything that derives\n them — every Adwaita preferences row, `AdwActionRow` included) are caught\n mechanically, by real inheritance. `AdwNavigationPage` and\n `AdwPreferencesPage` derive `Gtk.Widget` directly with no shared base to\n catch them mechanically, so they are a two-entry, doc-verified denylist\n instead — see `scripts/widget-surface/classify.ts` for the exact reasoning\n behind each.\n\n`GtkGestureClick` is a third, simpler case: an event controller, not a\nwidget at all, so it was never a candidate for wrapping in the first place.\n\nNothing here is unreachable — every raw export above is still exported,\nby name, from `react-native-gtkx/gtk` or `/adw`, exactly as gtkx binds it.";
974
984
  }, {
975
985
  readonly doc: "docs/platform-layer.md";
976
986
  readonly heading: "Auxiliary objects, not widgets at all";
@@ -978,7 +988,11 @@ export declare const DOC_CHUNKS: readonly [{
978
988
  }, {
979
989
  readonly doc: "docs/platform-layer.md";
980
990
  readonly heading: "The window and application AppRegistry built";
981
- readonly text: "`useParentWindow` (the `Gtk.Window` ancestor), `useApplication` (the\n`Adw.Application` — `.sendNotification(id, notification)` is the common\nreason to reach it) and `quit` (the same function `AppRegistry` wires to a\nwindow's own close button) are re-exported from `react-native-gtkx/gtk`.\nNone of these give you the window or application object ITSELF to build —\n`AppRegistry.runApplication` already did that — they let already-mounted\ncode reach back into it, the same way `useBindSetting` needs a `Gtk.Window`\nto bind a `defaultWidth` property on:\n\n```tsx\nconst window = useParentWindow()\nuseBindSetting({\n schema,\n key: \"window-width\",\n object: window,\n property: \"defaultWidth\",\n})\n```";
991
+ readonly text: "`useParentWindow` (the `Gtk.Window` ancestor), `useApplication` (the\n`Adw.Application` — `.sendNotification(id, notification)` is the common\nreason to reach it) and `quit` (the same function `AppRegistry` wires to a\nwindow's own close button) are re-exported from `react-native-gtkx/gtk`.\nNone of these give you the window or application object ITSELF to build —\n`AppRegistry.runApplication` already did that — they let already-mounted\ncode reach back into it, the same way `useBindSetting` needs a `Gtk.Window`\nto bind a `defaultWidth` property on:\n\n```tsx\nconst window = useParentWindow()\nuseBindSetting({\n schema,\n key: \"window-width\",\n object: window,\n property: \"defaultWidth\",\n})\n```\n\nFor the other half of reaching back into that window — putting things ON it\nrather than reading it — see the next section.";
992
+ }, {
993
+ readonly doc: "docs/platform-layer.md";
994
+ readonly heading: "Actions and shortcuts declared in the app tree";
995
+ readonly text: "`WindowActions`, `ApplicationActions` and `WindowControllers` register their\nchildren on the window (or the application) **from wherever they are written\nin the app tree**. They render nothing where they sit — they are portals in\nReact's own sense: the children stay part of the tree at that position, with\nthe context, state and effects they would have there, while the registration\nlands on the window.\n\n```tsx\nimport { GSimpleAction, WindowActions } from \"react-native-gtkx/gtk\"\n\nconst NewTaskAction = () => {\n const { addTask } = useStore() // an ordinary React context store\n return (\n <WindowActions>\n <GSimpleAction\n name=\"new\"\n onActivate={() => addTask()}\n />\n </WindowActions>\n )\n}\n```\n\nThat is `win.new` — what a HeaderBar button's `actionName`, a `GMenu` item\nand an `actionAccels` entry all target. `ApplicationActions` is the same\ncomponent against the application's action map (`app.*`), and the two are\nNOT interchangeable: a `Gio.Notification`'s action button can only ever\nactivate an application action, and an application action outlives any one\nwindow. Which prefix you get is decided by which component you write.\n`WindowControllers` takes `Gtk.EventController` children — a\n`GtkShortcutController` with `scope={Gtk.ShortcutScope.GLOBAL}` is the whole\nreason it exists.\n\n**Reach for these, not for `runApplication`'s `applicationActions` /\n`windowActions` / `windowControllers` options.** Those options build their\nchildren as props of the window `AppRegistry` creates, which makes them\nSIBLINGS of the app tree: no provider inside the app is above them, so an\naction declared there cannot read a React context — `examples/tasks-nav` had\nto rewrite its store as a module-level external store before Ctrl+N could\nsee any app state at all. The options still work and are not going away\nwithout notice, but they are deprecated, and everything they can express the\ncomponents can express better:\n\n- **context works**, because the declaration is a descendant of its provider;\n- **registration is dynamic** — the action is added when the declaring\n component mounts and removed when it unmounts, so one screen can own its\n own actions instead of the process owning all of them for its lifetime;\n- **it composes** — two unrelated subtrees each declare their own without\n meeting in a single options object.\n\n`actionAccels` is NOT deprecated and stays a `runApplication` option: it is a\nflat name→keys table with no children and nothing to read from context, and\nit is deliberately process-wide (an accelerator naming an action that is not\nregistered right now simply does nothing). A shortcut that should come and\ngo with a screen is a `GtkShortcutController` inside `<WindowControllers>`.\n\n**Two components, not one, on purpose.** Actions land on the window as a\n`Gio.ActionMap` (`addAction`/`removeAction`, keyed by NAME); controllers land\non it as a `Gtk.Widget` (`addController`/`removeController`, keyed by the\ncontroller object). Different children, different GObject interfaces, and —\nsee below — different duplicate semantics. One component sorting its children\nby type would fail silently on a wrong child; two fail at the type level.\n\n**A duplicated action name goes to the FIRST declaration**, and a second one\nis ignored with a development warning naming it. This is not a coin toss\nbetween first and last. `Gio.ActionMap` is name-keyed at both ends:\n`addAction` silently replaces a same-named action, and `removeAction` takes a\nname, not the action object. Under \"last wins\", the first of the two\ndeclarations to unmount would remove whatever currently answers to that\nname — leaving the other one mounted but dead. First-wins is the only order\nin which release always precedes acquire: the loser never registers, and when\nthe winner unmounts (removing its own action, correctly) the claim passes to\nthe next declaration still mounted, which registers in a later commit. If you\nwant a screen to override a shortcut, give it its own name, or move the\ndeclaration somewhere both screens can reach.\n\n**Inside a `Modal`, the enclosing window is the modal's own window**, so\nactions and controllers declared there belong to it and go away with it —\nusually what a dialog wants, and worth knowing when it is not. Under\n`chrome: \"content\"` and inside the navigators nothing changes: the window is\nstill the one `AppRegistry` built, the navigators own widgets inside it and\nnot its action map, and a `HeaderBar` button in a page resolves `win.*`\nthrough the widget hierarchy up to that same window. One consequence worth\nknowing: react-navigation keeps a popped screen mounted until its exit\ntransition ends, so a screen's actions outlive the pop by the length of the\nanimation.";
982
996
  }, {
983
997
  readonly doc: "docs/platform-layer.md";
984
998
  readonly heading: "GSettings";
@@ -994,7 +1008,7 @@ export declare const DOC_CHUNKS: readonly [{
994
1008
  }, {
995
1009
  readonly doc: "docs/platform-layer.md";
996
1010
  readonly heading: "Navigation without a router";
997
- readonly text: "The stack is an array of tags. Change the array, the widget animates.\n\n```tsx\nimport { useState } from \"react\"\nimport { Pressable, Text, View } from \"react-native\"\nimport {\n AdwHeaderBar,\n AdwToolbarView,\n NavigationStack,\n NavigationStackPage,\n SlotContent,\n} from \"react-native-gtkx/gtk` and `react-native-gtkx/adw\"\n\nconst App = () => {\n const [stack, setStack] = useState([\"home\"])\n\n return (\n <NavigationStack\n stack={stack}\n // The Adwaita back button, Escape, the back gesture and the\n // back-history menu all arrive here. Follow them in your own state.\n onPopped={(tag) => setStack((s) => s.filter((entry) => entry !== tag))}\n >\n <NavigationStackPage\n tag=\"home\"\n title=\"Home\"\n >\n <AdwToolbarView topBar={<AdwHeaderBar />}>\n <SlotContent>\n <Pressable onPress={() => setStack((s) => [...s, \"detail\"])}>\n <Text>Open detail</Text>\n </Pressable>\n </SlotContent>\n </AdwToolbarView>\n </NavigationStackPage>\n\n <NavigationStackPage\n tag=\"detail\"\n title=\"Detail\"\n >\n <AdwToolbarView topBar={<AdwHeaderBar />}>\n <SlotContent>\n <View />\n </SlotContent>\n </AdwToolbarView>\n </NavigationStackPage>\n </NavigationStack>\n )\n}\n```\n\nA runnable version is `examples/adwaita-primitives` — three levels deep, with\nReact Native content in the header bar and a raw `GtkButton` beside it.";
1011
+ readonly text: "The stack is an array of tags. Change the array, the widget animates.\n\n```tsx\nimport { useState } from \"react\"\nimport { Pressable, Text, View } from \"react-native\"\nimport { AdwHeaderBar, AdwToolbarView } from \"react-native-gtkx/adw\"\nimport {\n NavigationStack,\n NavigationStackPage,\n SlotContent,\n} from \"react-native-gtkx/common\"\n\nconst App = () => {\n const [stack, setStack] = useState([\"home\"])\n\n return (\n <NavigationStack\n stack={stack}\n // The Adwaita back button, Escape, the back gesture and the\n // back-history menu all arrive here. Follow them in your own state.\n onPopped={(tag) => setStack((s) => s.filter((entry) => entry !== tag))}\n >\n <NavigationStackPage\n tag=\"home\"\n title=\"Home\"\n >\n <AdwToolbarView topBar={<AdwHeaderBar />}>\n <SlotContent>\n <Pressable onPress={() => setStack((s) => [...s, \"detail\"])}>\n <Text>Open detail</Text>\n </Pressable>\n </SlotContent>\n </AdwToolbarView>\n </NavigationStackPage>\n\n <NavigationStackPage\n tag=\"detail\"\n title=\"Detail\"\n >\n <AdwToolbarView topBar={<AdwHeaderBar />}>\n <SlotContent>\n <View />\n </SlotContent>\n </AdwToolbarView>\n </NavigationStackPage>\n </NavigationStack>\n )\n}\n```\n\nA runnable version is `examples/adwaita-primitives` — three levels deep, with\nReact Native content in the header bar and a raw `GtkButton` beside it.";
998
1012
  }, {
999
1013
  readonly doc: "docs/platform-layer.md";
1000
1014
  readonly heading: "`NavigationStack` props";
@@ -1014,7 +1028,7 @@ export declare const DOC_CHUNKS: readonly [{
1014
1028
  }, {
1015
1029
  readonly doc: "docs/platform-layer.md";
1016
1030
  readonly heading: "Wrapping a widget we do not export";
1017
- readonly text: "The generated surface above covers every current `Gtk.Widget`/`Adw.Widget`\nsubclass gtkx binds, but \"current\" is doing work in that sentence: a gtkx\nrelease can add a widget before this package's generator has been re-run for\nit, and non-widget GI classes (an event controller, a filter, an adjustment)\nwere never candidates for the widget surface in the first place even though\na handful of them are occasionally worth putting inside RN layout too.\n`wrapReactNative` is how you reach either without waiting on us — it is\ngeneric, so the widget's own props keep their types:\n\n```tsx\nimport { GtkPopover } from \"@gtkx/jsx/gtk\"\nimport { wrapReactNative } from \"react-native-gtkx/gtk` and `react-native-gtkx/adw\"\n\nconst Popover = wrapReactNative(GtkPopover)\n// <Popover style={{ width: 240 }} autohide … /> — `autohide` still typed\n```\n\n(`GtkPopover` here is already part of the generated surface — this is the\nsame mechanism `src/gtk/widgets.generated.ts` uses under the hood, just\napplied by hand. It stays useful the day gtkx binds something this package\nhas not regenerated for yet.)\n\nTwo lower-level forms exist for cases the wrapper does not fit:\n\n- `<Widget style={…}>` — wrap an element you already have in hand;\n- `useWidgetLayout(ref, { style })` — attach layout to a widget whose ref you\n own, with no wrapper component at all. Returns the GTK CSS class from the\n style's visual half, for you to pass to `cssClasses`.";
1031
+ readonly text: "The generated surface above covers every current `Gtk.Widget`/`Adw.Widget`\nsubclass gtkx binds, but \"current\" is doing work in that sentence: a gtkx\nrelease can add a widget before this package's generator has been re-run for\nit, and non-widget GI classes (an event controller, a filter, an adjustment)\nwere never candidates for the widget surface in the first place even though\na handful of them are occasionally worth putting inside RN layout too.\n`wrapReactNative` is how you reach either without waiting on us — it is\ngeneric, so the widget's own props keep their types:\n\n```tsx\nimport { GtkPopover } from \"@gtkx/jsx/gtk\"\nimport { wrapReactNative } from \"react-native-gtkx/common\"\n\nconst Popover = wrapReactNative(GtkPopover)\n// <Popover style={{ width: 240 }} autohide … /> — `autohide` still typed\n```\n\n(`GtkPopover` here is already part of the generated surface — this is the\nsame mechanism `src/gtk/widgets.generated.ts` uses under the hood, just\napplied by hand. It stays useful the day gtkx binds something this package\nhas not regenerated for yet.)\n\nTwo lower-level forms exist for cases the wrapper does not fit:\n\n- `<Widget style={…}>` — wrap an element you already have in hand;\n- `useWidgetLayout(ref, { style })` — attach layout to a widget whose ref you\n own, with no wrapper component at all. Returns the GTK CSS class from the\n style's visual half, for you to pass to `cssClasses`.";
1018
1032
  }, {
1019
1033
  readonly doc: "docs/platform-layer.md";
1020
1034
  readonly heading: "The escape hatch";
@@ -1054,7 +1068,7 @@ export declare const DOC_CHUNKS: readonly [{
1054
1068
  }, {
1055
1069
  readonly doc: "docs/getting-started.md";
1056
1070
  readonly heading: "Shipping an app";
1057
- readonly text: "The two paths get you from source to something installable differently,\nbecause they take different positions on what stays out of the bundle.\n\n**vite path** (`gtkx build`): everything except the native GTK addon is\ninlined into one file. `dist/bundle.js` + `dist/gtkx.node` (plus\n`dist/gschemas.compiled` if the app declares a GSettings schema — the\nbundle's own banner points `GSETTINGS_SCHEMA_DIR` at its own directory) is\nthe whole runtime: copy those anywhere with Node ≥24, GTK4 ≥4.20 and\nlibadwaita ≥1.8, and `node bundle.js` runs it. No `node_modules` involved.\n\n**Metro path** (`react-native build-linux`): the release counterpart to\n`run-linux` that iOS, Android and react-native-windows already have and\nthis platform did not until now — it bundles with Metro and stops, instead\nof bundling and immediately running like `run-linux` does:\n\n```bash\nnpx react-native build-linux # writes dist/main.jsbundle\n```\n\nThis is **not** self-contained, unlike the vite path. Metro deliberately\nkeeps `@gtkx/*`, `react` and `yoga-layout` out of the bundle — they have to\nbe the exact instances the Node+GTK host loads, not a second copy Metro\ninlines (see `packages/react-native-gtkx/src/metro/index.ts`,\n`HOST_MODULE_EXTERNALS`, for why). So running `dist/main.jsbundle` needs,\non top of Node ≥24/GTK4/libadwaita, a real `node_modules` with\n`react-native-gtkx` installed and the app's `gtkx.config.ts` present at the\nworking directory:\n\n```bash\nnode node_modules/react-native-gtkx/dist/runner/host.js dist/main.jsbundle\n```\n\n(run from the app root — the config loader reads `gtkx.config.ts` from the\ncurrent directory, exactly like `run-linux` itself). Any ordinary\n`npm install` of the app already has that `node_modules`; the difference\nfrom the vite path only matters when packaging for a machine that never\nran one see `scripts/build-deb.ts`'s Metro branch, which builds that\nclosure itself: a fresh, isolated install of the locally-packed\n`react-native-gtkx` plus `gtkx codegen`, never a copy of a monorepo's own\nhoisted `node_modules` (which would prove nothing about what a real install\nneeds).\n\nThat is the **default** artifact, and it is the only one that carries the\n`node_modules` caveat. `--standalone` below removes it entirely: the same\nMetro build, emitted as one self-contained file that runs on a system Node\nwith nothing installed beside it — the vite path's shape, on the Metro\npath.";
1071
+ readonly text: "The two paths get you from source to something installable differently,\nbecause they take different positions on what stays out of the bundle.\n\n**vite path** (`gtkx build`): everything except the native GTK addon is\ninlined into one file. `dist/bundle.js` + `dist/gtkx.node` (plus\n`dist/gschemas.compiled` if the app declares a GSettings schema — the\nbundle's own banner points `GSETTINGS_SCHEMA_DIR` at its own directory) is\nthe whole runtime: copy those anywhere with Node ≥24, GTK4 ≥4.20 and\nlibadwaita ≥1.8, and `node bundle.js` runs it. No `node_modules` involved.\n\n**Metro path** (`react-native build-linux`): the release counterpart to\n`run-linux` that iOS, Android and react-native-windows already have and\nthis platform did not until now — it bundles with Metro and stops, instead\nof bundling and immediately running like `run-linux` does:\n\n```bash\nnpx react-native build-linux # writes dist/main.jsbundle\n```\n\nThis is **not** self-contained, unlike the vite path. Metro deliberately\nkeeps `@gtkx/*`, `react` and `yoga-layout` out of the bundle — they have to\nbe the exact instances the Node+GTK host loads, not a second copy Metro\ninlines (see `packages/react-native-gtkx/src/metro/index.ts`,\n`HOST_MODULE_EXTERNALS`, for why). So running `dist/main.jsbundle` needs,\non top of Node ≥24/GTK4/libadwaita, a real `node_modules` with\n`react-native-gtkx` installed and the app's `gtkx.config.ts` present at the\nworking directory:\n\n```bash\nnode node_modules/react-native-gtkx/dist/runner/host.js dist/main.jsbundle\n```\n\n(run from the app root — the config loader reads `gtkx.config.ts` from the\ncurrent directory, exactly like `run-linux` itself). Any ordinary\n`npm install` of the app already has that `node_modules`, so this is a fine\nway to run a release bundle from a checkout. It is a bad thing to _ship_:\npackaging it means shipping the closure too, and that closure is not the\nhandful of runtime modules it sounds like. Measured on the release that did\nexactly this (v0.2.0-alpha.1's `hn-app` `.deb`): **10,515 files, 206 MiB\ninstalled** to run a 369 KB bundle, because `react-native-gtkx`'s install\ndrags its build toolchain along — `typescript`, `@swc`, `rolldown`,\n`@babel`, `lightningcss`, all of it landing under `/opt` on a user's\nmachine. `scripts/build-deb.ts` no longer packages this shape at all.\n\nThat is the **default** artifact, and it is the only one that carries the\n`node_modules` caveat. `--standalone` below removes it entirely: the same\nMetro build, emitted as one self-contained file that runs on a system Node\nwith nothing installed beside it — the vite path's shape, on the Metro\npath. It is what the release `.deb` ships.";
1058
1072
  }, {
1059
1073
  readonly doc: "docs/getting-started.md";
1060
1074
  readonly heading: "One file (Metro path)";
@@ -1066,7 +1080,7 @@ export declare const DOC_CHUNKS: readonly [{
1066
1080
  }, {
1067
1081
  readonly doc: "docs/getting-started.md";
1068
1082
  readonly heading: "Tests";
1069
- readonly text: "Unit logic is plain vitest — no special setup, runs anywhere. Component\ntests render real GTK widgets under a headless Wayland compositor, and\nreact-native-gtkx ships the whole recipe as two subpaths so a consumer app\ndoes not have to rediscover it:\n\n- `react-native-gtkx/vitest` — `reactNativeGtkxTest()`, a ready Vitest\n project config: the headless-compositor plugin, the `react-native` alias\n and Metro-style platform extensions, an inline-deps default for RN\n libraries that import `react-native` themselves (`@react-navigation`),\n and the React act-environment setup;\n- `react-native-gtkx/testing` — re-exports `@gtkx/testing`'s\n render/screen/userEvent/fireEvent surface (already RN-shaped: `getByText`\n finds a `Text`, `userEvent.click` walks up to a `Pressable`'s gesture\n controller — no wrapper needed) plus `renderHookWithWindow`, for hooks\n that read the active window (`useWindowDimensions` and similar) —\n `renderHook` alone mounts into a windowless container.\n\nMinimal `vitest.config.ts`:\n\n```ts\nimport { reactNativeGtkxTest } from \"react-native-gtkx/vitest\"\nimport { defineConfig } from \"vitest/config\"\n\nexport default defineConfig(reactNativeGtkxTest())\n```\n\nThe default test glob is `**/*.gtk.test.{ts,tsx}`; override `include` (and\n`name`, `headless`, `platform`, `inlineDeps`, `setupFiles`,\n`fileParallelism`) through `reactNativeGtkxTest`'s options. For a project\nthat also has portable unit tests, use the result as one entry of\n`test.projects` instead of the whole config — `vitest.config.ts` at this\nrepo's root is the reference (`process.platform === \"linux\"` guards the\ngtk project so `npm test` still works on a non-Linux dev machine, running\nonly the unit project there).\n\n```tsx\nimport { Root } from \"react-native\"\nimport { render, screen } from \"react-native-gtkx/testing\"\nimport { expect, it } from \"vitest\"\nimport { App } from \"../src/App\"\n\nit(\"renders the greeting\", async () => {\n // react-native-gtkx components need a layout root — AppRegistry.runApplication()\n // in the real app, <Root> in a test.\n await render(\n <Root\n width={800}\n height={600}\n >\n <App />\n </Root>,\n )\n expect(screen.getByText(\"Hello, GNOME!\")).toBeTruthy()\n})\n```\n\nRequirements: a headless Wayland compositor and D-Bus on PATH — the same\nsystem packages CI installs, `sway xwayland dbus` (Ubuntu:\n`apt install sway xwayland dbus`). A missing compositor fails a test run\nwith a readable error (`Cannot find the \"sway\" executable on PATH`) rather\nthan hanging. `gtkx codegen` must already have generated the project's\n`@gtkx/gi` bindings before the first test run — a bare `vitest run` does\nnot trigger codegen itself, unlike `gtkx dev`/`gtkx build`; the template's\nown `package.json` wires this as a `pretest` script.\n\n`packages/react-native-gtkx/tests/gtk/` is this repo's own suite, built on\nthe same `@gtkx/testing` surface directly (it tests source, not the\npublished package) — a good place to see more query and `userEvent`\npatterns in context. Query roles with `Gtk.AccessibleRole` enums (see\ndocs/gtkx-rc2-notes.md for the live workarounds still baked into that\nrecipe).";
1083
+ readonly text: "Unit logic is plain vitest — no special setup, runs anywhere. Component\ntests render real GTK widgets under a headless Wayland compositor, and\nreact-native-gtkx ships the whole recipe as two subpaths so a consumer app\ndoes not have to rediscover it:\n\n- `react-native-gtkx/vitest` — `reactNativeGtkxTest()`, a ready Vitest\n project config: the headless-compositor plugin, the `react-native` alias\n and Metro-style platform extensions, an inline-deps default for RN\n libraries that import `react-native` themselves (`@react-navigation`),\n and the React act-environment setup;\n- `react-native-gtkx/testing` — re-exports `@gtkx/testing`'s\n render/screen/userEvent/fireEvent surface (already RN-shaped: `getByText`\n finds a `Text`, `userEvent.click` walks up to a `Pressable`'s gesture\n controller — no wrapper needed) plus `renderHookWithWindow`, for hooks\n that read the active window (`useWindowDimensions` and similar) —\n `renderHook` alone mounts into a windowless container.\n\nMinimal `vitest.config.ts`:\n\n```ts\nimport { reactNativeGtkxTest } from \"react-native-gtkx/vitest\"\nimport { defineConfig } from \"vitest/config\"\n\nexport default defineConfig(reactNativeGtkxTest())\n```\n\nThe default test glob is `**/*.gtk.test.{ts,tsx}`; override `include` (and\n`name`, `headless`, `platform`, `inlineDeps`, `setupFiles`,\n`fileParallelism`) through `reactNativeGtkxTest`'s options. For a project\nthat also has portable unit tests, use the result as one entry of\n`test.projects` instead of the whole config — `vitest.config.ts` at this\nrepo's root is the reference (`process.platform === \"linux\"` guards the\ngtk project so `npm test` still works on a non-Linux dev machine, running\nonly the unit project there).\n\n```tsx\nimport { Root } from \"react-native\"\nimport { render, screen } from \"react-native-gtkx/testing\"\nimport { expect, it } from \"vitest\"\nimport { App } from \"../src/App\"\n\nit(\"renders the greeting\", async () => {\n // react-native-gtkx components need a layout root — AppRegistry.runApplication()\n // in the real app, <Root> in a test.\n await render(\n <Root\n width={800}\n height={600}\n >\n <App />\n </Root>,\n )\n expect(screen.getByText(\"Hello, GNOME!\")).toBeTruthy()\n})\n```\n\nRequirements: a headless Wayland compositor and D-Bus on PATH — the same\nsystem packages CI installs, `sway xwayland dbus` (Ubuntu:\n`apt install sway xwayland dbus`). A missing compositor fails a test run\nwith a readable error (`Cannot find the \"sway\" executable on PATH`) rather\nthan hanging. `gtkx codegen` must already have generated the project's\n`@gtkx/gi` bindings before the first test run — a bare `vitest run` does\nnot trigger codegen itself, unlike `gtkx dev`/`gtkx build`; the template's\nown `package.json` wires this as a `pretest` script.\n\n`packages/react-native-gtkx/tests/gtk/` is this repo's own suite, built on\nthe same `@gtkx/testing` surface directly (it tests source, not the\npublished package) — a good place to see more query and `userEvent`\npatterns in context. Query roles with `Gtk.AccessibleRole` enums (see\ndocs/gtkx-rc3-notes.md for the live workarounds still baked into that\nrecipe).";
1070
1084
  }, {
1071
1085
  readonly doc: "docs/getting-started.md";
1072
1086
  readonly heading: "MCP server for agents";
@@ -1074,27 +1088,35 @@ export declare const DOC_CHUNKS: readonly [{
1074
1088
  }, {
1075
1089
  readonly doc: "docs/getting-started.md";
1076
1090
  readonly heading: "Next steps";
1077
- readonly text: "- [docs/api.md](api.md) — the entire v1 surface and differences from RN;\n- [CONTRIBUTING.md](../CONTRIBUTING.md) — developing the library itself (from macOS — via the UTM VM);\n- [docs/gtkx-rc2-notes.md](gtkx-rc2-notes.md) — the gtkx rc.2 baseline: workarounds, what it fixed, quirks that stay.";
1091
+ readonly text: "- [docs/api.md](api.md) — the entire v1 surface and differences from RN;\n- [CONTRIBUTING.md](../CONTRIBUTING.md) — developing the library itself (from macOS — via the UTM VM);\n- [docs/gtkx-rc3-notes.md](gtkx-rc3-notes.md) — the gtkx rc.3 baseline: workarounds, what it fixed, quirks that stay.";
1078
1092
  }, {
1079
- readonly doc: "docs/gtkx-rc2-notes.md";
1093
+ readonly doc: "docs/gtkx-rc3-notes.md";
1080
1094
  readonly heading: "Live workarounds";
1081
- readonly text: "| Name | What rc.2 does | Our workaround | Removal condition |\n| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |\n| `use-signal-stale-handler` | `useSignal` routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) — `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it — confirmed upstream, gtkx-org/gtkx#467) — a fetch-fed FlatList empties itself on the first scroll | `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's | A stable React 19.3 (React fixed the refresh on the 19.3 line; no stable gtkx 0.34.x yet) |\n| `runtime-dedupe` | Two bundled copies of the gtkx runtime still double-init GLib and abort (`g_log_set_writer_func` called twice); nothing guards against it | `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits | Idempotent runtime init upstream, or an error that names the duplicate |\n| `renderhook-no-window` | `renderHook` still mounts into a bare `Gtk.Box`, so window-dependent APIs have no toplevel to read | Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) | `renderHook` mounts into the same harness window `render` uses |\n| `graphene-rect-nested-boxed-props` | `new Graphene.Rect({ origin: new Graphene.Point(...), size: new Graphene.Size(...) })` hits the same native \"Expected an Object for Boxed field write type, got Object\" as the `gsk-colorstop-boxed-write` row below a boxed struct's constructor writing another boxed value into one of its own fields | `gtkx/bridge/svg-node.ts` builds the clip rect through `Graphene.Rect.alloc().init(x, y, w, h)` instead a working escape hatch `Gsk.ColorStop` does not have | Upstream fixes boxed-struct fields that are themselves another boxed type |\n| `gsk-colorstop-boxed-write` | Constructing a `Gsk.ColorStop` (an inline `{ float offset; GdkRGBA color; }` boxed struct) crashes in the native addon writing the `color` field \"Expected an Object for Boxed field write type, got Object\". Verified through three independent paths (constructor props, the property setter, and skipping `ColorStop` for a plain `{offset, color}` object, which fails differently with \"No native handle associated with Object\" the array marshaling genuinely needs a native-backed instance per element) | `gtkx/bridge/svg-node.ts`'s `makeColorStop` catches the throw and returns `null`; a gradient with zero constructible stops paints nothing for that fill/stroke instead of crashing (the same path as an unresolved `url(#missing)` reference) — SVG `<LinearGradient>`/`<RadialGradient>` ship with this degradation, not cut from the API | Upstream fixes boxed-struct fields that are themselves another boxed type (nested embed, not a pointer) |";
1095
+ readonly text: "| Name | What rc.3 does | Our workaround | Removal condition |\n| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| `use-signal-stale-handler` | `useSignal` routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) — `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it) — a fetch-fed FlatList empties itself on the first scroll. **Unchanged in rc.3, deliberately** — see below | `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's | A stable React 19.3 (React fixed the refresh on the 19.3 line) |\n| `runtime-dedupe` | Two bundled copies of the gtkx runtime still double-init GLib and abort (`g_log_set_writer_func` called twice); nothing guards against it | `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits | Idempotent runtime init upstream, or an error that names the duplicate |\n| `prop-portal` | `createPortal(children, container)` can only target a container's DEFAULT slot (\"children\"). Every other slot an object exposes declaratively a window's `Gio.ActionMap` (`actions`), a widget's `controllers`, an `AdwApplicationWindow`'s `breakpoints` is reached only by passing an element-valued PROP, which the reconciler routes through an internal `\"gtkx:prop\"` element. That element is exported from neither `@gtkx/react` nor its `/internal` subpath, so there is no supported way to portal into a named slot | `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` — the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it | gtkx exports a slot-aware portal (or the prop element) from a public entry point |\n| `renderhook-no-window` | `renderHook` still mounts into a bare `Gtk.Box` rc.3's `render-hook.js` is byte-identical to rc.2's so window-dependent APIs have no toplevel to read | Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) | `renderHook` mounts into the same harness window `render` uses |";
1096
+ }, {
1097
+ readonly doc: "docs/gtkx-rc3-notes.md";
1098
+ readonly heading: "`use-signal-stale-handler` is a decision upstream made, not an oversight";
1099
+ readonly text: "We shipped the fix as a PR (gtkx-org/gtkx#469) and it was **closed unmerged\non purpose**. @eugeniodepalo: _\"closing this in favour of waiting for\nupstream… Since React fixes this properly on 19.3 for all fiber tags, I'd\nrather take the version bump than carry a workaround I'd revert.\"_ So this\nrow does not move on any gtkx release — only a stable React 19.3 retires it,\nand the hazard is wider than `useSignal`: any hook built on `useEffectEvent`\ninherits it until then.";
1082
1100
  }, {
1083
- readonly doc: "docs/gtkx-rc2-notes.md";
1084
- readonly heading: "Fixed in rc.2 (rc.1 history, one line each)";
1085
- readonly text: "- **`vitest-compositor`** — rc.1 defaulted the headless display to weston and\n took sway through an option; rc.2's default IS sway, so `vitest.config.ts`\n calls the plugin with no arguments.\n- **`no-virtual-seat`** rc.1 had no input seat under sway, so windows never\n activated and `userEvent` was impossible; rc.2 starts a virtual seat for sway\n (`needsVirtualSeat: true`), a rendered toplevel now reports `is-active: true`,\n and coordinate-level input is on the table.\n- **`fixed-layout-child`** — rc.1's declarative `<GtkFixedLayoutChild>` created\n a detached object (Gtk-CRITICAL, positions never applied); moot for us since\n containers moved to our own `RnGtkxLayout` manager and GtkFixed left the\n codebase entirely.\n- **`controllers-as-children`** rc.1 silently ignored controllers passed as\n JSX; rc.2 has a `controllers` slot on `GtkWidget`. Pressable and TextInput\n still attach theirs imperatively on purpose (wired once per widget, handlers\n read from a ref) a choice now, not a workaround.";
1101
+ readonly doc: "docs/gtkx-rc3-notes.md";
1102
+ readonly heading: "Fixed in rc.3";
1103
+ readonly text: "- **`gsk-colorstop-boxed-write`** — constructing a `Gsk.ColorStop` threw\n `Expected an Object for Boxed field write type, got Object` in the native\n addon, so SVG `<LinearGradient>`/`<RadialGradient>` had zero constructible\n stops and painted nothing. **We fixed this upstream ourselves**\n (gtkx-org/gtkx#473, closing gtkx-org/gtkx#472): a record field write\n converts through `toNative` now, the counterpart of the `fromNative` its\n getter already used. `makeColorStop`'s try/catch and the null-filtering it\n forced through `collectStops`/`appendGradient` are gone; gradients paint for\n real, asserted on the stop colors that actually reach Gsk in\n `tests/gtk/components/svg.gtk.test.tsx`.\n- **`graphene-rect-nested-boxed-props`** — the same native bug reached through\n `new Graphene.Rect({ origin, size })`; we had been building the SVG clip\n rect through `Graphene.Rect.alloc().init(x, y, w, h)`, the escape hatch\n `Gsk.ColorStop` did not have. Same upstream fix, so `svg-node.ts` uses the\n plain constructor again.\n- **The codegen freshness lie** `npm install` prunes `node_modules/.gtkx`,\n and rc.2's `@gtkx/cli` could report \"bindings up to date\" over a store that\n was not there. Fixed upstream in gtkx-org/gtkx#470 (also ours): both stores'\n manifests and self-links are checked now. Separately, `src/runner` calls the\n programmatic `@gtkx/codegen` API rather than the CLI, so a library\n generating bindings on a consumer's behalf has no cwd or stamp to misread.";
1086
1104
  }, {
1087
- readonly doc: "docs/gtkx-rc2-notes.md";
1088
- readonly heading: "New in the rc.2 era";
1089
- readonly text: "Two regressions/gaps first seen on rc.2, both with reproductions and both\nwritten up for upstream in [docs/upstream-gtkx.md](upstream-gtkx.md):\n\n- **The `useSignal` freeze.** Not a `useSignal` bug and not about tree depth:\n `react-reconciler@0.33.0` refreshes `useEffectEvent` in\n `commitBeforeMutationEffects` only for `case 0` (FunctionComponent) —\n `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through\n unrefreshed, so any `useEffectEvent` inside a `memo`/`forwardRef` component\n is pinned to its mount closure permanently (confirmed upstream,\n gtkx-org/gtkx#467). It reproduced for us because our `ScrollView` is a\n `forwardRef` with the `useSignal` calls inside it; simple, shallow\n components refresh correctly, which is why it survives casual testing. The\n visible symptom was a virtualized list that blanked on the first scroll.\n Repro: `tests/gtk/components/list-late-data.gtk.test.tsx`, plus the contract\n test in `tests/gtk/bridge.smoke.test.tsx`.\n- **The codegen freshness lie resolved.** `npm install` prunes\n `node_modules/.gtkx` (npm sees `@gtkx/gi`/`@gtkx/jsx` as extraneous), and on\n rc.2 `@gtkx/cli`'s codegen could report \"bindings up to date\" over a store\n that was not there; fixed upstream in gtkx-org/gtkx#470 (the freshness check\n now verifies both stores' manifests and self-links, not just one). Separately,\n we were never supposed to be exposed to this: `@gtkx/cli` is meant for apps,\n not libraries generating bindings on a consumer's behalf, so `src/runner`\n now calls the programmatic `@gtkx/codegen` API directly (see\n `docs/upstream-gtkx.md` bug 2) no CLI subprocess, no cwd, no stamp to\n misread. `rm -rf node_modules/.gtkx` before `npm run codegen` at the repo\n root is still the right sequence for our own monorepo tooling, which still\n runs the CLI.";
1105
+ readonly doc: "docs/gtkx-rc3-notes.md";
1106
+ readonly heading: "Fixed in rc.2 (history, one line each)";
1107
+ readonly text: "- **`vitest-compositor`** rc.1 defaulted the headless display to weston;\n rc.2's default IS sway, so `vitest.config.ts` calls the plugin with no\n arguments.\n- **`no-virtual-seat`** rc.1 had no input seat under sway, so windows never\n activated and `userEvent` was impossible; rc.2 starts a virtual seat.\n- **`fixed-layout-child`** — rc.1's declarative `<GtkFixedLayoutChild>`\n created a detached object; moot since containers moved to our own\n `RnGtkxLayout` manager and GtkFixed left the codebase.\n- **`controllers-as-children`**rc.1 silently ignored controllers passed as\n JSX; rc.2 has a `controllers` slot on `GtkWidget`. Pressable and TextInput\n still attach theirs imperatively on purpose a choice now, not a\n workaround.";
1090
1108
  }, {
1091
- readonly doc: "docs/gtkx-rc2-notes.md";
1109
+ readonly doc: "docs/gtkx-rc3-notes.md";
1110
+ readonly heading: "Behaviour rc.3 changed under us";
1111
+ readonly text: "None of these needed a code change, but they change what the stack does\nbeneath us and are worth knowing before debugging something odd:\n\n- **Blockable signals are no longer suppressed for a whole React commit.**\n rc.2 suppressed `onChanged`/`onToggled`/`onNotify*`/`onSelectionChanged`\n from `prepareForCommit` until after `resetAfterCommit`; rc.3 wraps each\n framework write individually, so an emission the framework did not cause —\n one raised from a `useLayoutEffect`, or aimed at another `createRoot` tree —\n now reaches its handler. Our navigators lean on exactly this class of\n signal; the full suite is unchanged (825 passed + 1 expected fail, same as\n on rc.2), so nothing of ours was relying on the over-broad suppression.\n- **`render`'s harness window is undecorated.** rc.2 gave it a `Gtk.HeaderBar`\n whose window handle, center box and title label sat in the accessibility\n tree competing with the widgets under test. Role queries now see only what\n the test rendered.\n- **A widget with `accessibleLabelledBy` reports the relation as its\n accessible name**, ahead of its own text — the precedence ARIA defines.\n Changes what `getByRole(role, { name })` matches.\n- **`toHaveTextContent` no longer falls back to the accessible name** (we have\n no uses of it); **`toHaveDisplayValue` throws** on a widget without one\n instead of comparing to `null`; **checked state is tri-state**.\n- **Records are constructible only when their bytes can be copied**, a new\n restriction that could have taken `Gsk.ColorStop` away with one hand while\n the fix above gave it with the other. Verified on the VM that neither\n `Gsk.ColorStop` nor `Graphene.Rect` is caught by it — both construct from\n props — which is what makes the two deletions above real rather than paper.\n- **Single-child widgets lost their `content`/`child` props** (pass the widget\n as a child instead). No JSX in this repo used either, and typecheck against\n the regenerated bindings is clean, so this cost us nothing — but an app\n built on the raw gtkx surface will feel it.";
1112
+ }, {
1113
+ readonly doc: "docs/gtkx-rc3-notes.md";
1092
1114
  readonly heading: "Non-workarounds (quirks that stay)";
1093
- readonly text: "- 64-bit FFI values arrive as BigInt → `toNumber()` at the boundary\n (`gtkx/bridge/measure.ts`);\n- signal names are kebab-case (\"value-changed\"); signals do not pass the\n emitter (get the widget from a ref);\n- role queries in tests use the `Gtk.AccessibleRole` enum, not strings;\n- `npm install` prunes the codegen store (`node_modules/.gtkx` is not in the\n lockfile) → run `npm run codegen` after installing — npm behavior, not gtkx;\n- measuring unmapped widgets yields 0 (offscreen Label probes are the\n exception) → re-measure on the `map` signal + re-commit measured leaves on\n every flush (`layout/node.ts`);\n- mixed-session setups only: running an app on a bare compositor (headless\n sway) while `XDG_RUNTIME_DIR` points at a full GNOME session can segfault in\n a GTK signal handler when the GNOME settings portal pushes updates into the\n app (`g_cclosure_marshal_VOID__OBJECTv` via the FFI emit path); cutting\n `DBUS_SESSION_BUS_ADDRESS` avoids it, which is why the headless scripts do.\n Normal desktop and container runs are unaffected. Retested on rc.2 (gallery\n under headless sway with the real session bus attached): the app ran clean\n and SIGTERM teardown exited 143, so the exit-time segfault we saw on rc.1 no\n longer reproduces; the portal-push crash needs a live settings change to\n trigger and stays on the list unconfirmed.";
1115
+ readonly text: "- 64-bit FFI values arrive as BigInt → `toNumber()` at the boundary\n (`gtkx/bridge/measure.ts`);\n- signal names are kebab-case (\"value-changed\"); signals do not pass the\n emitter (get the widget from a ref);\n- role queries in tests use the `Gtk.AccessibleRole` enum, not strings;\n- `npm install` prunes the codegen store (`node_modules/.gtkx` is not in the\n lockfile) → run `npm run codegen` after installing — npm behavior, not gtkx;\n- measuring unmapped widgets yields 0 (offscreen Label probes are the\n exception) → re-measure on the `map` signal + re-commit measured leaves on\n every flush (`layout/node.ts`);\n- mixed-session setups only: running an app on a bare compositor (headless\n sway) while `XDG_RUNTIME_DIR` points at a full GNOME session can segfault in\n a GTK signal handler when the GNOME settings portal pushes updates into the\n app (`g_cclosure_marshal_VOID__OBJECTv` via the FFI emit path); cutting\n `DBUS_SESSION_BUS_ADDRESS` avoids it, which is why the headless scripts do.\n Normal desktop and container runs are unaffected. The exit-time segfault\n seen on rc.1 no longer reproduces; the portal-push crash needs a live\n settings change to trigger and stays on the list unconfirmed.";
1094
1116
  }, {
1095
- readonly doc: "docs/gtkx-rc2-notes.md";
1117
+ readonly doc: "docs/gtkx-rc3-notes.md";
1096
1118
  readonly heading: "Procedure when the next release ships";
1097
- readonly text: "1. Update the `@gtkx/*` pins (root, spike, examples, template), then\n `npm install && rm -rf node_modules/.gtkx && npm run codegen`;\n2. Run everything on Linux: `npm run typecheck && npm test`, `build:dist`,\n `check:package`, plus the headless example proofs;\n3. Walk the live-workaround table: for each row check the removal condition,\n delete the tag and the row together when it is met, and move the entry into\n the history section above;\n4. Re-tag whatever survives (`RC2-WORKAROUND` → the new release) and update\n `docs/upstream-gtkx.md` if an ask was answered.";
1119
+ readonly text: "1. Update the `@gtkx/*` pins (root, spike, examples, template), then\n `npm install && rm -rf node_modules/.gtkx && npm run codegen`;\n2. Run everything on Linux: `npm run typecheck && npm test`, `build:dist`,\n `check:package`, plus the headless example proofs;\n3. Walk the live-workaround table: for each row check the removal condition,\n delete the tag and the row together when it is met, and move the entry into\n the history section above — **with a probe that proves the fix on the real\n runtime**, not just the release notes claiming it;\n4. Re-tag whatever survives (`RC3-WORKAROUND` → the new release), rename this\n file to match the new pin, and update `docs/upstream-gtkx.md` if an ask was\n answered.";
1098
1120
  }, {
1099
1121
  readonly doc: "docs/research/navigation-extensibility.md";
1100
1122
  readonly heading: "1. The two layers";
@@ -1116,29 +1138,25 @@ export declare const DOC_CHUNKS: readonly [{
1116
1138
  readonly heading: "5. The desktop-RN landscape";
1117
1139
  readonly text: "No other desktop React Native platform has native navigation integration:\n\n- **react-native-screens** lists Windows support, but it is a thin\n old-architecture module; native-stack on modern react-native-windows\n fails, because screens has no new-architecture Windows implementation and\n RNW 0.82 removed the old one. Microsoft's own react-native-gallery falls\n back to the JS drawer.\n- **react-native-macos** is not supported by react-native-screens at all —\n AppKit has no navigation-stack primitive to bind to.\n\nOur path — a real `Adw.NavigationView` driven from a custom navigator, with\nreact-navigation state as the source of truth and native pops reported back\ninto it — is structurally the iOS native-stack / `UINavigationController`\nmodel, which neither desktop platform reached. GTK's advantage is that the\nprimitive exists at all: back button, Escape, back gesture, history menu and\ntransitions ship with the widget.\n\nThe trade: a JS stack can render anything into its fake header, while our\nchrome is real and had to be opened up deliberately — which is what the\nintrinsic-size root does.\n\nSources: react-native-screens README and discussions #1575 / #2541, RNW\ndiscussions #14273 / issue #4152 / new-architecture docs / 0.82 release\npost, microsoft/react-native-gallery, reactnavigation.org native-stack docs.";
1118
1140
  }, {
1119
- readonly doc: "docs/gtkx-rc2-notes.md";
1120
- readonly heading: "RC2-WORKAROUND(use-signal-stale-handler)";
1121
- readonly text: "`useSignal` routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) — `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it — confirmed upstream, gtkx-org/gtkx#467) — a fetch-fed FlatList empties itself on the first scroll — our workaround: `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's — removed when: A stable React 19.3 (React fixed the refresh on the 19.3 line; no stable gtkx 0.34.x yet)";
1141
+ readonly doc: "docs/gtkx-rc3-notes.md";
1142
+ readonly heading: "RC3-WORKAROUND(use-signal-stale-handler)";
1143
+ readonly text: "`useSignal` routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) — `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it) — a fetch-fed FlatList empties itself on the first scroll. **Unchanged in rc.3, deliberately** see below — our workaround: `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's — removed when: A stable React 19.3 (React fixed the refresh on the 19.3 line)";
1122
1144
  }, {
1123
- readonly doc: "docs/gtkx-rc2-notes.md";
1124
- readonly heading: "RC2-WORKAROUND(runtime-dedupe)";
1145
+ readonly doc: "docs/gtkx-rc3-notes.md";
1146
+ readonly heading: "RC3-WORKAROUND(runtime-dedupe)";
1125
1147
  readonly text: "Two bundled copies of the gtkx runtime still double-init GLib and abort (`g_log_set_writer_func` called twice); nothing guards against it — our workaround: `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits — removed when: Idempotent runtime init upstream, or an error that names the duplicate";
1126
1148
  }, {
1127
- readonly doc: "docs/gtkx-rc2-notes.md";
1128
- readonly heading: "RC2-WORKAROUND(renderhook-no-window)";
1129
- readonly text: "`renderHook` still mounts into a bare `Gtk.Box`, so window-dependent APIs have no toplevel to read our workaround: Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) — removed when: `renderHook` mounts into the same harness window `render` uses";
1130
- }, {
1131
- readonly doc: "docs/gtkx-rc2-notes.md";
1132
- readonly heading: "RC2-WORKAROUND(graphene-rect-nested-boxed-props)";
1133
- readonly text: "`new Graphene.Rect({ origin: new Graphene.Point(...), size: new Graphene.Size(...) })` hits the same native \"Expected an Object for Boxed field write type, got Object\" as the `gsk-colorstop-boxed-write` row below — a boxed struct's constructor writing another boxed value into one of its own fields — our workaround: `gtkx/bridge/svg-node.ts` builds the clip rect through `Graphene.Rect.alloc().init(x, y, w, h)` instead — a working escape hatch `Gsk.ColorStop` does not have — removed when: Upstream fixes boxed-struct fields that are themselves another boxed type";
1149
+ readonly doc: "docs/gtkx-rc3-notes.md";
1150
+ readonly heading: "RC3-WORKAROUND(prop-portal)";
1151
+ readonly text: "`createPortal(children, container)` can only target a container's DEFAULT slot (\"children\"). Every other slot an object exposes declaratively — a window's `Gio.ActionMap` (`actions`), a widget's `controllers`, an `AdwApplicationWindow`'s `breakpoints` — is reached only by passing an element-valued PROP, which the reconciler routes through an internal `\"gtkx:prop\"` element. That element is exported from neither `@gtkx/react` nor its `/internal` subpath, so there is no supported way to portal into a named slot — our workaround: `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` — the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it — removed when: gtkx exports a slot-aware portal (or the prop element) from a public entry point";
1134
1152
  }, {
1135
- readonly doc: "docs/gtkx-rc2-notes.md";
1136
- readonly heading: "RC2-WORKAROUND(gsk-colorstop-boxed-write)";
1137
- readonly text: "Constructing a `Gsk.ColorStop` (an inline `{ float offset; GdkRGBA color; }` boxed struct) crashes in the native addon writing the `color` field \"Expected an Object for Boxed field write type, got Object\". Verified through three independent paths (constructor props, the property setter, and skipping `ColorStop` for a plain `{offset, color}` object, which fails differently with \"No native handle associated with Object\" the array marshaling genuinely needs a native-backed instance per element) our workaround: `gtkx/bridge/svg-node.ts`'s `makeColorStop` catches the throw and returns `null`; a gradient with zero constructible stops paints nothing for that fill/stroke instead of crashing (the same path as an unresolved `url(#missing)` reference) — SVG `<LinearGradient>`/`<RadialGradient>` ship with this degradation, not cut from the API — removed when: Upstream fixes boxed-struct fields that are themselves another boxed type (nested embed, not a pointer)";
1153
+ readonly doc: "docs/gtkx-rc3-notes.md";
1154
+ readonly heading: "RC3-WORKAROUND(renderhook-no-window)";
1155
+ readonly text: "`renderHook` still mounts into a bare `Gtk.Box` — rc.3's `render-hook.js` is byte-identical to rc.2's so window-dependent APIs have no toplevel to readour workaround: Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) — removed when: `renderHook` mounts into the same harness window `render` uses";
1138
1156
  }, {
1139
1157
  readonly doc: "docs/api.md";
1140
1158
  readonly heading: "View";
1141
- readonly text: "View — GTK implementation: GtkBox (RnGtkxViewBox). Supported: `style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins). Differences from RN: nesting another pointerEvents inside a box-only view is not supported";
1159
+ readonly text: "View — GTK implementation: GtkBox (RnGtkxViewBox). Supported: `style`, `onLayout`, `testID`, children, `pointerEvents` (auto/none/box-none/box-only — mapped onto GTK picking: can-target + a contains() vfunc override; also honored from `style.pointerEvents`, the prop wins), ref: `measure`/`measureInWindow`/`measureLayout` (`ViewHandle`, RN's argument order; window coordinates come from `gtk_widget_compute_point`, so they are correct inside a scrolled viewport), the responder and touch props (`onStartShouldSetResponder(Capture)`, `onMoveShouldSetResponder(Capture)`, `onResponderGrant/Start/Move/End/Release/Terminate`, `onTouchStart/Move/End/Cancel` + `Capture`) — spread `PanResponder`'s `panHandlers` here. Differences from RN: nesting another pointerEvents inside a box-only view is not supported; responder negotiation is capture-then-bubble as in RN, but there is **no transfer yet**: once a view is granted, `onResponderTerminationRequest`/`onResponderReject` are never called and a second view cannot steal (GTK's `CLAIMED` is irrevocable, so a transfer could not be enforced against native widgets — see docs/research/gestures.md). The lock is one per process as in RN; the negotiation PATH stops at the layout root, so native GTK widgets between or above views take no part. Single-pointer only: a mouse is one fabricated touch, `touches` never exceeds one";
1142
1160
  }, {
1143
1161
  readonly doc: "docs/api.md";
1144
1162
  readonly heading: "Text";
@@ -1154,7 +1172,7 @@ export declare const DOC_CHUNKS: readonly [{
1154
1172
  }, {
1155
1173
  readonly doc: "docs/api.md";
1156
1174
  readonly heading: "Pressable";
1157
- readonly text: "Pressable — GTK implementation: GtkFixed + GestureClick/Motion. Supported: `onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}`. Differences from RN: —";
1175
+ readonly text: "Pressable — GTK implementation: GtkFixed + GestureClick/Motion. Supported: `onPress(In/Out)`, `onLongPress` (`delayLongPress`), `onHoverIn/Out`, `disabled`, function-form `style`/`children` receiving `{pressed, hovered}`; the `PressEvent` payload is RN's shape (`locationX/Y` target-relative, `pageX/Y` window-relative, `identifier`, `target`, `force`, monotonic `timestamp`, single-element `touches`/`changedTouches` — a desktop pointer is one fabricated touch). Differences from RN: —";
1158
1176
  }, {
1159
1177
  readonly doc: "docs/api.md";
1160
1178
  readonly heading: "TouchableOpacity";
@@ -1162,7 +1180,7 @@ export declare const DOC_CHUNKS: readonly [{
1162
1180
  }, {
1163
1181
  readonly doc: "docs/api.md";
1164
1182
  readonly heading: "ScrollView";
1165
- readonly text: "ScrollView — GTK implementation: GtkScrolledWindow. Supported: vertical/`horizontal`, `contentContainerStyle`, `onScroll`, `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`). Differences from RN: `animated` in scrollTo is ignored";
1183
+ readonly text: "ScrollView — GTK implementation: GtkScrolledWindow. Supported: vertical/`horizontal`, `contentContainerStyle`, `onScroll` (`contentOffset`, `contentSize`, `layoutMeasurement`), `onContentSizeChange`, `stickyHeaderIndices` (RN model: the REAL child is translated and painted on top — no duplicate), ref: `scrollTo`/`scrollToEnd` (`ScrollViewHandle`). Differences from RN: `animated` in scrollTo is ignored";
1166
1184
  }, {
1167
1185
  readonly doc: "docs/api.md";
1168
1186
  readonly heading: "FlatList";
@@ -1186,7 +1204,7 @@ export declare const DOC_CHUNKS: readonly [{
1186
1204
  }, {
1187
1205
  readonly doc: "docs/api.md";
1188
1206
  readonly heading: "Animated.View";
1189
- readonly text: "Animated.View — GTK implementation: direct widget calls. Supported: `opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange). Differences from RN: `rotateX`/`rotateY`/`perspective` (3D), `skewX`/`skewY` and `matrix` are not supported, and the transform origin is always the view's centre (no `transformOrigin`)";
1207
+ readonly text: "Animated.View — GTK implementation: direct widget calls. Supported: `opacity` and the whole `transform` array — `translateX/Y`, `scale`, `scaleX`, `scaleY`, `rotate`/`rotateZ` — driven by Animated nodes, bypassing React (an angle comes from `interpolate` with a `deg`/`rad` outputRange), plus the same responder and touch props `View` takes — this is where an idiomatic `PanResponder` drag lands. Differences from RN: `rotateX`/`rotateY`/`perspective` (3D), `skewX`/`skewY` and `matrix` are not supported, and the transform origin is always the view's centre (no `transformOrigin`)";
1190
1208
  }, {
1191
1209
  readonly doc: "docs/api.md";
1192
1210
  readonly heading: "SafeAreaView";
@@ -1218,7 +1236,7 @@ export declare const DOC_CHUNKS: readonly [{
1218
1236
  }, {
1219
1237
  readonly doc: "docs/api.md";
1220
1238
  readonly heading: "AppRegistry";
1221
- readonly text: "AppRegistry — Supported: `registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,applicationActions,actionAccels,windowActions,windowControllers,breakpoints})`, `getAppKeys`. Differences: desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `applicationActions`/`actionAccels` reach the underlying `GtkApplication` (`app.*` actions — what a `Gio.Notification` action button targets); `windowActions`/`windowControllers` reach the window (`win.*` actions, a window-scoped `GtkShortcutController`); `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise)";
1239
+ readonly text: "AppRegistry — Supported: `registerComponent`, `runApplication(appKey, {title,width,height,initialProps,chrome,actionAccels,breakpoints,applicationActions,windowActions,windowControllers})`, `getAppKeys`. Differences: desktop window parameters; `chrome: \"content\"` uses an AdwApplicationWindow with no window titlebar — the app's HeaderBars (navigation) become the chrome. `actionAccels` binds accelerators to action names on the `GtkApplication`; `breakpoints` reaches `AdwApplicationWindow`'s own prop and only does anything under `chrome: \"content\"` (a dev warning fires otherwise). **`applicationActions`/`windowActions`/`windowControllers` are deprecated** — reach for [`<ApplicationActions>`/`<WindowActions>`/`<WindowControllers>`](platform-layer.md#actions-and-shortcuts-declared-in-the-app-tree) instead; they still work unchanged";
1222
1240
  }, {
1223
1241
  readonly doc: "docs/api.md";
1224
1242
  readonly heading: "Platform";
@@ -1267,10 +1285,14 @@ export declare const DOC_CHUNKS: readonly [{
1267
1285
  readonly doc: "docs/api.md";
1268
1286
  readonly heading: "BackHandler";
1269
1287
  readonly text: "BackHandler — Supported: `addEventListener(\"hardwareBackPress\")`, `exitApp`. Differences: no hardware back key on desktop — subscriptions are honored but nothing fires them yet";
1288
+ }, {
1289
+ readonly doc: "docs/api.md";
1290
+ readonly heading: "PanResponder";
1291
+ readonly text: "PanResponder — Supported: `create(config)` -> `panHandlers` (spread onto a `View`), full `gestureState` (`dx`/`dy`, `vx`/`vy`, `x0`/`y0`, `moveX`/`moveY`, `numberActiveTouches`) — **react-native's own file, vendored unmodified** (MIT, `Libraries/Interaction/PanResponder.js`), running on our reproduction of RN's `touchHistory` store. Differences: multi-touch `gestureState` is single-touch here (one pointer), and `onShouldBlockNativeResponder`'s return value is not consumed yet";
1270
1292
  }, {
1271
1293
  readonly doc: "docs/api.md";
1272
1294
  readonly heading: "Animated";
1273
- readonly text: "Animated — Supported: `Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity). Differences: `useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway";
1295
+ readonly text: "Animated — Supported: `Value`, `timing`, `spring`, `sequence`, `parallel`, `delay`, `loop`, `interpolate` (numbers and deg/rad strings, clamp/extend/identity), `ValueXY` (`setValue`/`setOffset`/`flattenOffset`/`extractOffset`, `getLayout`, `getTranslateTransform`) — the value a `PanResponder` drag writes to. Differences: `useNativeDriver` is ignored (with a warning); the direct path is native-speed anyway; `Animated.event` is not implemented — write the value directly (`pan.setValue({x: g.dx, y: g.dy})`), which is what it would do";
1274
1296
  }, {
1275
1297
  readonly doc: "docs/api.md";
1276
1298
  readonly heading: "Easing";