@symbiote-native/react 3.0.2 → 3.0.4

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.
package/README.md CHANGED
@@ -22,43 +22,75 @@ adapter, not the core.
22
22
 
23
23
  ## Install
24
24
 
25
+ ```bash
26
+ npx @symbiote-native/cli new my-app --framework react
27
+ ```
28
+
29
+ One command, nothing to wire by hand: scaffolds the Metro config, the entry seam below, and
30
+ `@symbiote-native/react`/`react-native`/`react` as your app's own dependencies.
31
+
32
+ <details>
33
+ <summary>Manual install (no generator — an existing app, or you want to wire it yourself)</summary>
34
+
25
35
  ```bash
26
36
  npm install @symbiote-native/react react-native react
27
37
  ```
28
38
 
29
39
  `react-native` and `react` stay your app's own top-level dependencies — SymbioteNative doesn't hide
30
- them, it only replaces the JS renderer that drives them. `npx @symbiote-native/cli new --framework react`
31
- scaffolds the Metro config and the `index.js` entry seam below for a new app; wiring them into an
32
- existing one still follows [`examples/react`](../../examples/react) rather than a generator.
40
+ them, it only replaces the JS renderer that drives them. Follow [`examples/react`](../../examples/react)
41
+ for the Metro config and the entry seam below; there is no wiring script for an existing app.
42
+
43
+ </details>
33
44
 
34
45
  ---
35
46
 
36
47
  ## Use it
37
48
 
38
- The app is ordinary React — the native primitives are plain intrinsic tags, no import needed:
49
+ The app is ordinary React — the native primitives are plain intrinsic tags, no import needed.
50
+ Styling is a CSS class against a plain `.css` file, the convention every example app here
51
+ follows (`StyleSheet.create` still works, but isn't what any example uses):
39
52
 
40
53
  ```jsx
41
54
  import { useState } from 'react';
55
+ import './App.css';
42
56
 
43
57
  export default function App() {
44
58
  const [count, setCount] = useState(0);
45
59
  return (
46
- <view style={{ padding: 24 }}>
60
+ <safe-area-view className="screen">
47
61
  <text>Taps: {count}</text>
48
62
  <pressable onPress={() => setCount(c => c + 1)}>
49
63
  <text>Tap me</text>
50
64
  </pressable>
51
- </view>
65
+ </safe-area-view>
52
66
  );
53
67
  }
54
68
  ```
55
69
 
70
+ ```css
71
+ /* App.css */
72
+ .screen {
73
+ flex: 1;
74
+ align-items: center;
75
+ justify-content: center;
76
+ padding: 24px;
77
+ }
78
+ ```
79
+
80
+ <details>
81
+ <summary>Native entry point (index.js) — already scaffolded by <code>npx @symbiote-native/cli new</code></summary>
82
+
56
83
  The zero-config entry wires the RN-backed host seams (colors, images, device events,
57
84
  third-party ViewConfigs) and registers the app in one call — this is what
58
85
  [`examples/react`](../../examples/react) actually uses:
59
86
 
60
87
  ```js
61
88
  // index.js
89
+
90
+ // Registers host behaviors (Image, Pressable, Switch, ...) that /bootstrap alone doesn't
91
+ // reach; deleting this breaks them silently (Metro's production inlineRequires makes a
92
+ // side-effect-only barrel import go lazy, see register.ts).
93
+ import '@symbiote-native/react';
62
94
  import { registerApp } from '@symbiote-native/react/bootstrap';
63
95
  import App from './App';
64
96
  import { name as appName } from './app.json';
@@ -91,6 +123,8 @@ AppRegistry.registerRunnable(appName, ({ rootTag }) => {
91
123
  });
92
124
  ```
93
125
 
126
+ </details>
127
+
94
128
  Either way, the full canary ([`App.tsx`](../../examples/react/App.tsx)) exercises every block of
95
129
  the surface below.
96
130
 
@@ -141,8 +175,9 @@ the future scaffolder).
141
175
 
142
176
  ## Run it
143
177
 
144
- [`examples/react`](../../examples/react) is a stock React Native 0.86 app. Requires Node ≥ 22 and
145
- the [RN environment setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode, CocoaPods):
178
+ [`examples/react`](../../examples/react) is a stock React Native 0.86 app. Requires Node ≥ 22.13
179
+ (react-native 0.86's own `package.json#engines`) and the [RN environment
180
+ setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode, CocoaPods):
146
181
 
147
182
  ```bash
148
183
  cd examples/react
@@ -68,7 +68,7 @@ export const KeyboardAvoidingView = props => {
68
68
  for (const subscription of subscriptions)
69
69
  subscription.remove();
70
70
  };
71
- }, [behavior, keyboardVerticalOffset]);
71
+ }, [behavior, keyboardVerticalOffset, enabled]);
72
72
  const handleLayout = (event) => {
73
73
  const frame = readLayoutFrame(event.nativeEvent.layout);
74
74
  if (frame !== undefined) {
@@ -146,7 +146,7 @@ export function VirtualizedList(props) {
146
146
  }
147
147
  dlog(`VirtualizedList scrollTo offset=${clamped} pending-ref (horizontal=${isHorizontal})`);
148
148
  setCommandedOffset(target);
149
- }, []);
149
+ }, [scrollHandle]);
150
150
  // dispatch and runEffects are mutually recursive (a schedule-refill / fire-viewable effect
151
151
  // dispatches a follow-up action), so runEffects reaches dispatch through a ref.
152
152
  const dispatchRef = useRef(() => { });
@@ -436,21 +436,13 @@ export function VirtualizedList(props) {
436
436
  index: planCell.index,
437
437
  separators: makeSeparators(planCell.index),
438
438
  });
439
- // RN renders the separator INSIDE the cell's own measuring wrapper
440
- // (VirtualizedListCellRenderer.js:218-221), and that placement is load-bearing rather than
441
- // cosmetic. As a SIBLING it is an extra flex child, so the chrome between two cells becomes
442
- // gap + separator + gap while a spacer collapsing that region replaces it with one gap — the
443
- // leading spacer then lands every cell below it short by (separator + gap), and the content
444
- // visibly jumps by that amount each time the window's first index moves. Measured at exactly
445
- // 17px on device 2026-08-19 (a 1px divider under a 16px container gap); see
446
- // .claude/rules/list-geometry-feedback-loop.md. Inside the wrapper it is part of the cell's
447
- // measured length instead, and every cell stays exactly one child.
439
+ // The separator lives INSIDE the cell's own measuring wrapper, not as a sibling: a sibling
440
+ // is an extra flex child, so a leading-spacer collapse would land every cell below it short
441
+ // by (separator + gap). See .claude/rules/list-geometry-feedback-loop.md.
448
442
  const separator =
449
- // RN gates the separator on the last index of the DATA, not of the WINDOW
450
- // (VirtualizedList.js:793 `const end = getItemCount(data) - 1`), and now that the separator
451
- // lives INSIDE the measuring wrapper that distinction is load-bearing: gating on the window
452
- // would make a cell's own measured height change as the window slides past it. Device-measured
453
- // 2026-08-19 as a run of cells all shifting by exactly the divider's 1px.
443
+ // Gated on the last index of the DATA, not the WINDOW: since the separator lives inside
444
+ // the measuring wrapper, gating on the window would make a cell's own measured height
445
+ // change as the window slides past it.
454
446
  planCell.index < count - 1
455
447
  ? renderSeparatorElement(ItemSeparatorComponent, item, getItem(data, planCell.index + 1), separatorOverridesRef.current.get(planCell.index))
456
448
  : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/react",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "SymbioteNative's React adapter — a react-reconciler host config (mutation mode) driving real native iOS/Android views through stock React Native Fabric.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,20 +57,20 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "react-reconciler": "^0.33.0",
60
- "@symbiote-native/components": "3.1.0",
61
- "@symbiote-native/css-parser": "0.5.0"
60
+ "@symbiote-native/components": "3.1.2",
61
+ "@symbiote-native/css-parser": "0.5.2"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "react": ">=19.0.0",
65
65
  "react-native": ">=0.86",
66
- "@symbiote-native/engine": "^1.3.0"
66
+ "@symbiote-native/engine": "^1.3.1"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/react": "^19.2.0",
70
70
  "@types/react-reconciler": "^0.32.2",
71
71
  "react": "19.2.3",
72
- "@symbiote-native/engine": "1.3.0",
73
- "@symbiote-native/test-utils": "0.4.2"
72
+ "@symbiote-native/engine": "1.3.1",
73
+ "@symbiote-native/test-utils": "0.4.4"
74
74
  },
75
75
  "scripts": {
76
76
  "typecheck": "tsc --build",