feat(core,methods): add react-native framework adapter (#117)#137
feat(core,methods): add react-native framework adapter (#117)#137espetro wants to merge 10 commits into
Conversation
|
@espetro is attempting to deploy a commit to the Open Circle Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds a React Native framework adapter with reactive signals, batching, dependency tracking, and untracking. It extends framework typing and configures React Native builds and exports for the core and methods packages. It introduces typed React Native form hooks, headless components, validation behavior, native focus handling, tests, package tooling, documentation, and an Expo login playground with browser and iOS QA scripts. Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/framework/index.react-native.ts`:
- Line 56: The createSignal function is missing the zero-argument overload that
exists in the base framework contract. In both index.react-native.ts and
index.react.ts, add an overload declaration for createSignal that accepts no
parameters and returns Signal<T>, in addition to the existing overload that
takes an initial value parameter. This ensures both adapters match the complete
API contract defined in the base framework, allowing users to call
createSignal() with no arguments as well as with an initial value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3797d810-21ca-48d1-99fc-c874f64b76c3
📒 Files selected for processing (6)
packages/core/package.jsonpackages/core/src/framework/index.react-native.tspackages/core/src/framework/index.tspackages/core/tsdown.config.tspackages/methods/package.jsonpackages/methods/tsdown.config.ts
| * @returns The created signal. | ||
| */ | ||
| // @__NO_SIDE_EFFECTS__ | ||
| export function createSignal<T>(value: T): Signal<T> { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify createSignal signature consistency across framework entrypoints and call sites.
fd -i 'index*.ts' packages/core/src/framework
rg -nP --type=ts '^export function createSignal<.*>\(\): Signal<.*>;' packages/core/src/framework -C2
rg -nP --type=ts '^export function createSignal<.*>\(value: .*' packages/core/src/framework -C2
rg -nP --type=ts '\bcreateSignal\s*(<[^>]+>)?\s*\(\s*\)' packages -C2Repository: open-circle/formisch
Length of output: 2059
createSignal is missing the zero-arg overload from the base framework API.
The base contract in packages/core/src/framework/index.ts declares two overloads for createSignal—one without arguments and one with an initial value. At line 56 in the react-native adapter, only the required-argument overload is exposed, creating an API mismatch. The same issue exists in index.react.ts.
Suggested fix
// `@__NO_SIDE_EFFECTS__`
+export function createSignal<T>(): Signal<T | undefined>;
export function createSignal<T>(value: T): Signal<T>;
-export function createSignal<T>(value: T): Signal<T | undefined> {
+export function createSignal<T>(value?: T): Signal<T | undefined> {
const subscribers = new Set<Listener>();
return {
get value() {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/framework/index.react-native.ts` at line 56, The
createSignal function is missing the zero-argument overload that exists in the
base framework contract. In both index.react-native.ts and index.react.ts, add
an overload declaration for createSignal that accepts no parameters and returns
Signal<T>, in addition to the existing overload that takes an initial value
parameter. This ensures both adapters match the complete API contract defined in
the base framework, allowing users to call createSignal() with no arguments as
well as with an initial value.
There was a problem hiding this comment.
1 issue found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/src/framework/index.react-native.ts">
<violation number="1" location="packages/core/src/framework/index.react-native.ts:7">
P2: React Native adapter duplicates the entire React adapter signal implementation. The two files differ only by the `framework` constant, creating a maintenance burden and drift risk for any future signal-layer changes.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| @@ -0,0 +1,131 @@ | |||
| import type { Signal } from '../types/signal/index.ts'; | |||
There was a problem hiding this comment.
P2: React Native adapter duplicates the entire React adapter signal implementation. The two files differ only by the framework constant, creating a maintenance burden and drift risk for any future signal-layer changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/framework/index.react-native.ts, line 7:
<comment>React Native adapter duplicates the entire React adapter signal implementation. The two files differ only by the `framework` constant, creating a maintenance burden and drift risk for any future signal-layer changes.</comment>
<file context>
@@ -0,0 +1,131 @@
+/**
+ * The current framework being used.
+ */
+export const framework: Framework = 'react-native';
+
+/**
</file context>
fabian-hiller
left a comment
There was a problem hiding this comment.
Thank you for this PR! It would be great to offer Formisch for React Native within the next weeks.
I think we should do what you marked as "Out of scope" in the PR description as part of this PR. Since React Native is a special case it would be great to confirm that what we have in mind make sense and works with React Native. A bigger PR is ok in such a case. I just want to prevent merging something that can not be verified and tested.
Maybe I am wrong but there might be a chance that the only change we need is setting the FieldElement type to e.g. React Native's TextInput, Switch, and so on. FieldElement is only used in InternalBaseStore and getElementInput. Since getElementInput is not used anywhere else within the core and methods package, it can probably be ignored.
After that, we need a new frameworks/react-native and playgrounds/react-native folder with React Native specific code.
| /** | ||
| * The current framework being used. | ||
| */ | ||
| export const framework: Framework = 'react-native'; |
There was a problem hiding this comment.
If this file is fully identically to index.react.ts we could try re-exporting the functions of index.react.ts so that we only have to maintain one implementation. Just a spontaneous idea we should at least test and consider. But this is not a blocker.
There was a problem hiding this comment.
I'll check it, nevertheless take into account that the development teams at react and react-native don't sync on their releases, and this could create issues in the future. For instance, the React team announced the v19 release in Dec. 2024 and it took the React Native team until Feb. 2025 to ship it. That's 3 months worth of potential bugs (now with AI-assisted development it'd be much less of course, but it still implies some risk)
|
Hey @fabian-hiller before I proceed with the changes, I want to get your approval on one topic: when looking how to decouple core types from DOM types, I noticed the easiest fix was to change InternalBaseStore.elements and initialElements from Thus, instead I'd like to suggest a minimal structural base type: At
|
eea4c1c to
d34ca7a
Compare
Extends the `Framework` union with 'react-native' and adds a `index.react-native.ts` adapter identical to the React one (`createSignal`, `batch`, `untrack`, `createId`) with only the `framework` constant changed. This is the signal-layer foundation for a future `@formisch/react-native` package; browser-specific overrides (`FieldElement`, `getElementInput`) are intentionally out of scope here. Refs: open-circle#117
Adds 'react-native' to the per-framework build matrix in both
`packages/core/tsdown.config.ts` and `packages/methods/tsdown.config.ts`,
and exposes `./react-native` in both packages' export maps. The
`rewriteFrameworkImports` tsdown plugin rewires
`@formisch/core` -> `@formisch/core/react-native` in the methods
sources for the RN target, so no plugin work is required.
Produces `dist/index.react-native.{js,d.ts}` in both packages.
Refs: open-circle#117
327a47e to
14cbf5a
Compare
React Native has no `document.activeElement` to confirm which registered element actually received focus, so the variant simply calls `.focus()` on the first registered element and returns whether one was found.
Mirrors @formisch/react's useForm/useFieldArray/useSignals near-verbatim,
and rewrites useField for React Native: props are `{ ref, onFocus, onBlur,
onChangeText }` instead of DOM's `{ ref, autoFocus, onFocus, onChange,
onBlur, name }`, and the unmount cleanup tracks the registered `TextInput`
instance directly since native elements have no `isConnected` check.
React Native has no native form element or submit event, so `<Form />`
renders a plain `View` and submission is wired explicitly via
`handleSubmit(form, onSubmit)` on a button's `onPress`.
Uses react-native-web + jsdom so TextInput renders as a real DOM input, letting @testing-library/react's fireEvent work transparently. Submission tests wire handleSubmit to a Pressable's onPress instead of the DOM fireEvent.submit pattern used by react's tests, since RN has no native form/submit event.
Also links the new package from the sibling frameworks' READMEs.
Minimal Expo app (email/password login form) exercising @formisch/react-native. Runs on web via react-native-web for quick verification, or on device/simulator via the Expo dev server.
Gives QA tooling (agent-device, playwright-cli) stable selectors to target instead of ambiguous text/label matching -- the accessible label "Login" is shared by the heading and the submit button, which caused a mistargeted tap during manual verification.
Scripts the exact golden-path check (empty-submit validation, valid-submit success) run manually against Expo Go on an iOS Simulator during review, so it's a repeatable pnpm script rather than one-off commands. Documents both this and the web playwright-cli flow in the playground README.
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
@fabian-hiller update on your question about whether this covers the framework and playground layers too — it now does. Added on top of the original signal-layer foundation:
Verified end-to-end on both web ( Also caught and fixed a real RN-specific bug along the way: React nulls a ref callback on unmount before running the effect cleanup that relies on it, which was leaving stale Ready for another look whenever you have time. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
frameworks/react-native/package.json (1)
76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
react-nativepeer dependency is unconstrained ("*").Using a wildcard peer range on
react-nativemeans any future major/breaking release of React Native satisfies it, which offers no compatibility signal to consumers or package managers. Consider a realistic minimum range (e.g. aligned with thereact-nativedevDependency^0.86.0) instead of"*".♻️ Suggested fix
"peerDependencies": { "react": "^18.0.0 || ^19.0.0", - "react-native": "*", + "react-native": ">=0.70.0", "typescript": ">=5", "valibot": "^1.4.1" },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frameworks/react-native/package.json` around lines 76 - 81, Replace the wildcard react-native peer dependency in the peerDependencies object with a realistic supported version range, aligned with the package’s react-native development dependency such as ^0.86.0. Leave the other peer dependency declarations unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frameworks/react-native/package.json`:
- Around line 76-81: Replace the wildcard react-native peer dependency in the
peerDependencies object with a realistic supported version range, aligned with
the package’s react-native development dependency such as ^0.86.0. Leave the
other peer dependency declarations unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cad88b42-f572-4396-803c-06e2ab286504
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (60)
frameworks/preact/README.mdframeworks/qwik/README.mdframeworks/react-native/.gitignoreframeworks/react-native/CHANGELOG.mdframeworks/react-native/README.mdframeworks/react-native/eslint.config.jsframeworks/react-native/package.jsonframeworks/react-native/src/components/Field/Field.test.tsxframeworks/react-native/src/components/Field/Field.tsxframeworks/react-native/src/components/Field/index.tsframeworks/react-native/src/components/FieldArray/FieldArray.test.tsxframeworks/react-native/src/components/FieldArray/FieldArray.tsxframeworks/react-native/src/components/FieldArray/index.tsframeworks/react-native/src/components/Form/Form.test.tsxframeworks/react-native/src/components/Form/Form.tsxframeworks/react-native/src/components/Form/index.tsframeworks/react-native/src/components/index.tsframeworks/react-native/src/hooks/index.tsframeworks/react-native/src/hooks/useField/index.tsframeworks/react-native/src/hooks/useField/useField.test-d.tsframeworks/react-native/src/hooks/useField/useField.test.tsxframeworks/react-native/src/hooks/useField/useField.tsframeworks/react-native/src/hooks/useFieldArray/index.tsframeworks/react-native/src/hooks/useFieldArray/useFieldArray.test-d.tsframeworks/react-native/src/hooks/useFieldArray/useFieldArray.test.tsxframeworks/react-native/src/hooks/useFieldArray/useFieldArray.tsframeworks/react-native/src/hooks/useForm/index.tsframeworks/react-native/src/hooks/useForm/useForm.test-d.tsframeworks/react-native/src/hooks/useForm/useForm.test.tsxframeworks/react-native/src/hooks/useForm/useForm.tsframeworks/react-native/src/hooks/useSignals/index.tsframeworks/react-native/src/hooks/useSignals/useSignals.test.tsxframeworks/react-native/src/hooks/useSignals/useSignals.tsframeworks/react-native/src/index.tsframeworks/react-native/src/types/field.tsframeworks/react-native/src/types/form.tsframeworks/react-native/src/types/index.tsframeworks/react-native/src/vitest/setup.tsframeworks/react-native/tsconfig.jsonframeworks/react-native/tsdown.config.tsframeworks/react-native/vite.config.tsframeworks/react-native/vitest.config.tsframeworks/react/README.mdframeworks/solid/README.mdframeworks/svelte/README.mdframeworks/vue/README.mdpackages/core/src/field/focusFieldElement/focusFieldElement.react-native.test.tspackages/core/src/field/focusFieldElement/focusFieldElement.react-native.tsplaygrounds/react-native/.gitignoreplaygrounds/react-native/App.tsxplaygrounds/react-native/README.mdplaygrounds/react-native/app.jsonplaygrounds/react-native/babel.config.jsplaygrounds/react-native/eslint.config.jsplaygrounds/react-native/index.tsplaygrounds/react-native/metro.config.jsplaygrounds/react-native/package.jsonplaygrounds/react-native/scripts/qa-ios.shplaygrounds/react-native/tsconfig.jsonpnpm-workspace.yaml
- Fail loudly on a real boot error instead of swallowing it with `|| true`; only the already-booted case is tolerated. - Scope process cleanup to this run's own Expo/Metro process group instead of an unscoped `pkill -f "expo start --ios"` that could kill unrelated Expo projects on the machine. - Stop relying on `expo start --ios`, which can auto-launch a different simulator than the one agent-device attaches to; start Metro headless and deep-link the resolved device UDID directly via `simctl openurl`. - Wait on `simctl bootstatus` before deep-linking: `simctl boot` returns before SpringBoard is actually ready, which was failing the deep link with "Could not connect to the server". - Dismiss the on-screen keyboard (which fully covers the submit button) via the keyboard's own return key before the final submit tap.
There was a problem hiding this comment.
2 issues found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="playgrounds/react-native/scripts/qa-ios.sh">
<violation number="1" location="playgrounds/react-native/scripts/qa-ios.sh:31">
P2: An unknown or unavailable `QA_IOS_DEVICE` exits during the command substitution, so the intended `No available iOS Simulator...` diagnostic is never printed. Handling the assignment inside `if ! ...` (or otherwise neutralizing its status) lets the existing check report the actual cause.</violation>
<violation number="2" location="playgrounds/react-native/scripts/qa-ios.sh:81">
P2: The deep link targets the resolved UDID, but the subsequent `agent-device open` still selects by the non-unique `DEVICE` name. When multiple available simulators share that name, QA can attach to a different simulator than the one running the deep-linked app; using `--udid "$DEVICE_UDID"` keeps both steps on the same target.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| DEVICE_UDID="$(xcrun simctl list devices -j | node -e ' | ||
| const data = JSON.parse(require("fs").readFileSync(0, "utf8")); | ||
| const name = process.argv[1]; | ||
| for (const runtime of Object.values(data.devices)) { | ||
| for (const device of runtime) { | ||
| if (device.name === name && device.isAvailable) { | ||
| console.log(device.udid); | ||
| process.exit(0); | ||
| } | ||
| } | ||
| } | ||
| process.exit(1); |
There was a problem hiding this comment.
P2: An unknown or unavailable QA_IOS_DEVICE exits during the command substitution, so the intended No available iOS Simulator... diagnostic is never printed. Handling the assignment inside if ! ... (or otherwise neutralizing its status) lets the existing check report the actual cause.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At playgrounds/react-native/scripts/qa-ios.sh, line 31:
<comment>An unknown or unavailable `QA_IOS_DEVICE` exits during the command substitution, so the intended `No available iOS Simulator...` diagnostic is never printed. Handling the assignment inside `if ! ...` (or otherwise neutralizing its status) lets the existing check report the actual cause.</comment>
<file context>
@@ -16,24 +20,73 @@ EXPO_LOG="$(mktemp -t formisch-qa-ios-expo).log"
-echo "==> Booting $DEVICE"
-xcrun simctl boot "$DEVICE" >/dev/null 2>&1 || true
-open -a Simulator
+DEVICE_UDID="$(xcrun simctl list devices -j | node -e '
+ const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
+ const name = process.argv[1];
</file context>
| DEVICE_UDID="$(xcrun simctl list devices -j | node -e ' | |
| const data = JSON.parse(require("fs").readFileSync(0, "utf8")); | |
| const name = process.argv[1]; | |
| for (const runtime of Object.values(data.devices)) { | |
| for (const device of runtime) { | |
| if (device.name === name && device.isAvailable) { | |
| console.log(device.udid); | |
| process.exit(0); | |
| } | |
| } | |
| } | |
| process.exit(1); | |
| if ! DEVICE_UDID="$(xcrun simctl list devices -j | node -e ' | |
| const data = JSON.parse(require("fs").readFileSync(0, "utf8")); | |
| const name = process.argv[1]; | |
| for (const runtime of Object.values(data.devices)) { | |
| for (const device of runtime) { | |
| if (device.name === name && device.isAvailable) { | |
| console.log(device.udid); | |
| process.exit(0); | |
| } | |
| } | |
| } | |
| process.exit(1); | |
| ' "$DEVICE")"; then | |
| DEVICE_UDID="" | |
| fi |
| } | ||
|
|
||
| echo "==> Opening on $DEVICE ($DEVICE_UDID) in Expo Go" | ||
| xcrun simctl openurl "$DEVICE_UDID" "exp://127.0.0.1:8081" |
There was a problem hiding this comment.
P2: The deep link targets the resolved UDID, but the subsequent agent-device open still selects by the non-unique DEVICE name. When multiple available simulators share that name, QA can attach to a different simulator than the one running the deep-linked app; using --udid "$DEVICE_UDID" keeps both steps on the same target.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At playgrounds/react-native/scripts/qa-ios.sh, line 81:
<comment>The deep link targets the resolved UDID, but the subsequent `agent-device open` still selects by the non-unique `DEVICE` name. When multiple available simulators share that name, QA can attach to a different simulator than the one running the deep-linked app; using `--udid "$DEVICE_UDID"` keeps both steps on the same target.</comment>
<file context>
@@ -16,24 +20,73 @@ EXPO_LOG="$(mktemp -t formisch-qa-ios-expo).log"
-echo "==> Starting Expo (iOS)"
-(cd "$PLAYGROUND_DIR" && pnpm exec expo start --ios >"$EXPO_LOG" 2>&1 &)
+echo "==> Opening on $DEVICE ($DEVICE_UDID) in Expo Go"
+xcrun simctl openurl "$DEVICE_UDID" "exp://127.0.0.1:8081"
echo "==> Waiting for bundle"
</file context>
Summary
Adds the React Native framework adapter to
@formisch/coreand wires up a React Native build target in both@formisch/coreand@formisch/methods, per #117. This is the signal-layer foundation only — purely additive, no behaviour changes.Changes
packages/core/src/framework/index.ts— add'react-native'to theFrameworkunionpackages/core/src/framework/index.react-native.ts(NEW) — adapter identical toindex.react.tswithframework = 'react-native'packages/core/package.json— expose./react-nativeexportpackages/core/tsdown.config.ts— add'react-native'to the build matrix (added during impl; see note below)packages/methods/tsdown.config.ts— add'react-native'to the build matrixpackages/methods/package.json— expose./react-nativeexportWhy
React Native shares React's rendering core, so the signal/batch/untrack primitives transfer verbatim. The existing
rewriteFrameworkImportstsdown plugin (confirmed by @fabian-hiller in #117) auto-rewires@formisch/core→@formisch/core/react-nativefor the RN build target, so no plugin work is needed.Update: framework package and playground now included
@fabian-hiller asked whether this PR covers the framework and playground layers too — it now does. Added on top of the signal-layer foundation above:
frameworks/react-native(@formisch/react-native) — mirrorsframeworks/react'suseForm/useFieldArray/useSignals/Form/Field/FieldArrayalmost verbatim.useFieldis RN-specific: props are{ ref, onFocus, onBlur, onChangeText }(no DOMname/autoFocus), and the ref-callback/unmount cleanup tracks the last registeredTextInputinstance directly, since RN has noelement.isConnectedto check.Formrenders a plainView— RN has no native form/submit event, so submission is wired explicitly viahandleSubmit(form, onSubmit)on a button'sonPress.packages/core/src/field/focusFieldElement/focusFieldElement.react-native.ts— RN variant; RN also has nodocument.activeElement, so it just calls.focus()on the first registered element and reports whether one was found.react-native-web+ jsdom soTextInputrenders as a real DOM input and@testing-library/react'sfireEventworks unmodified.playgrounds/react-native— minimal Expo app (email/password login form) exercising the new package.frameworks/react-native/README.md+CHANGELOG.md, and the six sibling framework READMEs now link to it.A genuine RN-only bug was caught and fixed during this work: React calls a ref callback with
nullduring the commit phase, before running the correspondinguseEffectcleanup on unmount. The originaluseFieldimplementation wroteinstanceRef.current = instanceunconditionally (including thenullcall), so by the time the effect cleanup ran to filter the stale element out ofinternalFieldStore.elements,instanceRef.currentwas alreadynulland the filter was a no-op — leaving stale/detachedTextInputs in the array and causingfocusFieldElementto try (and silently fail) to focus them instead of a live remounted field afterreset(). Fixed by only writing toinstanceRef.currentwheninstanceis truthy.QA tooling entrypoints
Both
App.tsx's form fields and the submit button now carry stabletestIDs(
field-email,field-password,submit-button) instead of relying ontext/label matching — this actually mattered: the accessible label "Login" is
shared by the page heading and the submit button, which caused a mistargeted
tap the first time the golden path was driven through
agent-device.@playwright/cli(playwright-cli) drives a real browseragainst
pnpm -C playgrounds/react-native web.agent-devicedrives Expo Go on a booted simulator, scripted (not just ad hoc) via:
playgrounds/react-native/scripts/qa-ios.shand the playground README.Verification
pnpm -C packages/core build/pnpm -C packages/methods build/pnpm -C frameworks/react-native build→ all producedist/index.react-native.{js,d.ts}✅pnpm lint(full monorepo, all 20 workspace projects) → clean ✅pnpm -C frameworks/react-native test→ 54 tests pass across 10 files, 0 type errors ✅pnpm -C packages/core test/pnpm -C packages/methods test→ no regressions ✅react-native-web, viaplaywright-cli): empty submit shows field-level validation errors and focuses the first invalid field; valid submit callsonSubmitand renders the success message.agent-device/qa:ios): same two flows confirmed — empty submit surfaces "Please enter your email."/"Please enter your password." with auto-focus, valid submit shows "Logged in as user@example.com".Commits
feat(core): add react-native framework adapter—packages/core/src/framework/index.ts+ newindex.react-native.tsfeat(core,methods): expose react-native build output— 4 package.json + tsdown.config.ts filesfeat(core): add react-native focusFieldElement variantfeat(react-native): add @formisch/react-native framework packagetest(react-native): add unit and type testsdocs(react-native): add README and CHANGELOGfeat(playground): add react-native (expo) playgroundfeat(playground): add testIDs to react-native playground fieldstest(playground): add agent-device iOS QA script and docsCloses #117
Summary by cubic
Adds a React Native framework adapter and React Native build targets for
@formisch/coreand@formisch/methods. This is the signal-layer foundation only with no behavior changes.'react-native'to theFrameworkunion and a newpackages/core/src/framework/index.react-native.tsadapter (same as React, withframework = 'react-native'andcreateSignal,batch,untrack,createId)../react-nativeexports and added React Native to the tsdown build matrix in both packages, producingdist/index.react-native.{js,d.ts}as required by Add React Native framework adapter #117.Written for commit eea4c1c. Summary will update on new commits.
Summary by CodeRabbit
@formisch/react-nativewithForm,Field,FieldArray, plus hooks:useForm,useField, anduseFieldArray.