Skip to content

feat(core,methods): add react-native framework adapter (#117)#137

Open
espetro wants to merge 10 commits into
open-circle:mainfrom
espetro:feature/117-react-native-framework-adapter
Open

feat(core,methods): add react-native framework adapter (#117)#137
espetro wants to merge 10 commits into
open-circle:mainfrom
espetro:feature/117-react-native-framework-adapter

Conversation

@espetro

@espetro espetro commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Adds the React Native framework adapter to @formisch/core and wires up a React Native build target in both @formisch/core and @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 the Framework union
  • packages/core/src/framework/index.react-native.ts (NEW) — adapter identical to index.react.ts with framework = 'react-native'
  • packages/core/package.json — expose ./react-native export
  • packages/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 matrix
  • packages/methods/package.json — expose ./react-native export

Note on the 6th file: the original plan only listed 5 files. The maintainer's #117 comment — "Both will then build and output a React Native specific version" — makes the core build-matrix update mandatory; without it pnpm -C packages/core build cannot produce dist/index.react-native.{js,d.ts} and the new @formisch/core/react-native export would 404. The edit is mechanical, identical in pattern to the methods one, and folded into commit 2.

Why

React Native shares React's rendering core, so the signal/batch/untrack primitives transfer verbatim. The existing rewriteFrameworkImports tsdown plugin (confirmed by @fabian-hiller in #117) auto-rewires @formisch/core@formisch/core/react-native for 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) — mirrors frameworks/react's useForm/useFieldArray/useSignals/Form/Field/FieldArray almost verbatim. useField is RN-specific: props are { ref, onFocus, onBlur, onChangeText } (no DOM name/autoFocus), and the ref-callback/unmount cleanup tracks the last registered TextInput instance directly, since RN has no element.isConnected to check. Form renders a plain View — RN has no native form/submit event, so submission is wired explicitly via handleSubmit(form, onSubmit) on a button's onPress.
  • packages/core/src/field/focusFieldElement/focusFieldElement.react-native.ts — RN variant; RN also has no document.activeElement, so it just calls .focus() on the first registered element and reports whether one was found.
  • Unit + type tests for all of the above, run against react-native-web + jsdom so TextInput renders as a real DOM input and @testing-library/react's fireEvent works unmodified.
  • playgrounds/react-native — minimal Expo app (email/password login form) exercising the new package.
  • READMEs updated: new 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 null during the commit phase, before running the corresponding useEffect cleanup on unmount. The original useField implementation wrote instanceRef.current = instance unconditionally (including the null call), so by the time the effect cleanup ran to filter the stale element out of internalFieldStore.elements, instanceRef.current was already null and the filter was a no-op — leaving stale/detached TextInputs in the array and causing focusFieldElement to try (and silently fail) to focus them instead of a live remounted field after reset(). Fixed by only writing to instanceRef.current when instance is truthy.

QA tooling entrypoints

Both App.tsx's form fields and the submit button now carry stable testIDs
(field-email, field-password, submit-button) instead of relying on
text/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.

  • Web@playwright/cli (playwright-cli) drives a real browser
    against pnpm -C playgrounds/react-native web.
  • iOS Simulatoragent-device
    drives Expo Go on a booted simulator, scripted (not just ad hoc) via:
    pnpm -C playgrounds/react-native qa:ios:doctor   # once per machine
    pnpm -C playgrounds/react-native qa:ios          # boots a sim, starts Expo, runs the golden path
    See playgrounds/react-native/scripts/qa-ios.sh and the playground README.

Verification

  • pnpm -C packages/core build / pnpm -C packages/methods build / pnpm -C frameworks/react-native build → all produce dist/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 ✅
  • Playground verified end-to-end via the QA tooling above:
    • Web (react-native-web, via playwright-cli): empty submit shows field-level validation errors and focuses the first invalid field; valid submit calls onSubmit and renders the success message.
    • iOS Simulator (Expo Go, iPhone 17 Pro, via 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

  1. feat(core): add react-native framework adapterpackages/core/src/framework/index.ts + new index.react-native.ts
  2. feat(core,methods): expose react-native build output — 4 package.json + tsdown.config.ts files
  3. feat(core): add react-native focusFieldElement variant
  4. feat(react-native): add @formisch/react-native framework package
  5. test(react-native): add unit and type tests
  6. docs(react-native): add README and CHANGELOG
  7. feat(playground): add react-native (expo) playground
  8. feat(playground): add testIDs to react-native playground fields
  9. test(playground): add agent-device iOS QA script and docs

Closes #117


Summary by cubic

Adds a React Native framework adapter and React Native build targets for @formisch/core and @formisch/methods. This is the signal-layer foundation only with no behavior changes.

  • New Features
    • Added 'react-native' to the Framework union and a new packages/core/src/framework/index.react-native.ts adapter (same as React, with framework = 'react-native' and createSignal, batch, untrack, createId).
    • Exposed ./react-native exports and added React Native to the tsdown build matrix in both packages, producing dist/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.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added full React Native framework support, including reactive signals with batching and dependency tracking.
    • Introduced React Native entry points and published framework subpath exports for runtime and types.
    • Added @formisch/react-native with Form, Field, FieldArray, plus hooks: useForm, useField, and useFieldArray.
    • Included a React Native playground app with validation and submission examples.
  • Documentation
    • Updated framework READMEs to include React Native and added a React Native-specific README/changelog.
  • Tests
    • Added React Native test coverage for the core focus helper and the new hooks/components.

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

@espetro is attempting to deploy a commit to the Open Circle Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This 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

  • open-circle/formisch#104: Adds a similar framework adapter pattern with framework typing, package exports, and tsdown configuration updates.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR adds a full frameworks/react-native package, playground, docs, and tests that go beyond #117's adapter-only scope. Split the React Native package, playground, docs, and tests into a follow-up PR, and keep this PR to the core adapter plus methods build/export changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 34.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The core adapter, Framework union, react-native export, and methods build updates match #117's requirements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding React Native framework support in core and methods.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between be7c39a and 4b82b39.

📒 Files selected for processing (6)
  • packages/core/package.json
  • packages/core/src/framework/index.react-native.ts
  • packages/core/src/framework/index.ts
  • packages/core/tsdown.config.ts
  • packages/methods/package.json
  • packages/methods/tsdown.config.ts

* @returns The created signal.
*/
// @__NO_SIDE_EFFECTS__
export function createSignal<T>(value: T): Signal<T> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -C2

Repository: 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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 fabian-hiller left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@espetro espetro Jun 15, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

@fabian-hiller fabian-hiller self-assigned this Jun 14, 2026
@espetro

espetro commented Jun 15, 2026

Copy link
Copy Markdown
Author

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 FieldElement[] to object[] at packages/core/src/types/field/field.ts, however that's not helpful at all from a typesafe perspective.

Thus, instead I'd like to suggest a minimal structural base type:

At packages/core/src/types/field/field.ts

export interface FieldElementBase {
  readonly isConnected?: boolean;
}

InternalBaseStore.elements / .initialElements become FieldElementBase[]. The DOM FieldElement union is untouched; it still satisfies FieldElementBase structurally because all three HTML element types inherit isConnected: boolean from Node. No existing code breaks.

At frameworks/react/src/hooks/useField/useField.ts

The cleanup effect currently reads:

internalFieldStore.elements = internalFieldStore.elements.filter(
  (element) => element.isConnected
);

Because elements is now FieldElementBase[] (where isConnected is boolean | undefined), the filter needs a cast to keep TypeScript happy:

internalFieldStore.elements = internalFieldStore.elements.filter(
  (element) => (element as FieldElement).isConnected
);

That's the only change to the React adapter.

Other frameworks

All other frameworks (Preact, Qwik, Solid, Svelte, Vue) reference FieldElement only in their own framework-layer types, not in InternalBaseStore. They are unaffected.

Why not extends HTMLElement?

HTMLSelectElement does not extend HTMLInputElement, and forcing a common DOM supertype would drag web-only APIs into the core contract. The FieldElementBase approach (inspired by react-hook-form's CustomElement<T> pattern) keeps the core contract minimal: the only thing the core actually reads from entries in elements is isConnected (for cleanup filtering).


That's the full footprint outside the frameworks/react-native/ and playgrounds/react-native/ directories.

@espetro
espetro force-pushed the feature/117-react-native-framework-adapter branch from eea4c1c to d34ca7a Compare June 28, 2026 12:07
espetro added 2 commits July 20, 2026 11:17
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
@espetro
espetro force-pushed the feature/117-react-native-framework-adapter branch from 327a47e to 14cbf5a Compare July 20, 2026 09:18
espetro added 5 commits July 20, 2026 12:07
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.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 20, 2026
espetro added 2 commits July 20, 2026 13:25
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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread playgrounds/react-native/scripts/qa-ios.sh Outdated
Comment thread playgrounds/react-native/scripts/qa-ios.sh Outdated
Comment thread playgrounds/react-native/scripts/qa-ios.sh Outdated
@espetro

espetro commented Jul 20, 2026

Copy link
Copy Markdown
Author

@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:

  • frameworks/react-native (@formisch/react-native) — mirrors frameworks/react closely; useField/Form adapted for RN (no DOM name/autoFocus, no native submit event, ref-based unmount cleanup instead of element.isConnected)
  • packages/core/src/field/focusFieldElement/focusFieldElement.react-native.ts
  • Unit + type tests (54 tests, 10 files), run against react-native-web + jsdom
  • playgrounds/react-native — a minimal Expo login-form app
  • READMEs updated (new package + linked from the 6 sibling frameworks)

Verified end-to-end on both web (react-native-web via playwright-cli) and an iOS Simulator (Expo Go via agent-device) — empty submit surfaces validation errors, valid submit succeeds. The iOS check is now a repeatable script (pnpm -C playgrounds/react-native qa:ios) rather than a one-off, see the "QA tooling entrypoints" section in the PR description.

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 TextInput instances in the field's element list and breaking focus-after-reset. Details in the feat(react-native): add @formisch/react-native framework package commit message.

Ready for another look whenever you have time.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frameworks/react-native/package.json (1)

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

react-native peer dependency is unconstrained ("*").

Using a wildcard peer range on react-native means 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 the react-native devDependency ^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

📥 Commits

Reviewing files that changed from the base of the PR and between 14cbf5a and 41b0a08.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (60)
  • frameworks/preact/README.md
  • frameworks/qwik/README.md
  • frameworks/react-native/.gitignore
  • frameworks/react-native/CHANGELOG.md
  • frameworks/react-native/README.md
  • frameworks/react-native/eslint.config.js
  • frameworks/react-native/package.json
  • frameworks/react-native/src/components/Field/Field.test.tsx
  • frameworks/react-native/src/components/Field/Field.tsx
  • frameworks/react-native/src/components/Field/index.ts
  • frameworks/react-native/src/components/FieldArray/FieldArray.test.tsx
  • frameworks/react-native/src/components/FieldArray/FieldArray.tsx
  • frameworks/react-native/src/components/FieldArray/index.ts
  • frameworks/react-native/src/components/Form/Form.test.tsx
  • frameworks/react-native/src/components/Form/Form.tsx
  • frameworks/react-native/src/components/Form/index.ts
  • frameworks/react-native/src/components/index.ts
  • frameworks/react-native/src/hooks/index.ts
  • frameworks/react-native/src/hooks/useField/index.ts
  • frameworks/react-native/src/hooks/useField/useField.test-d.ts
  • frameworks/react-native/src/hooks/useField/useField.test.tsx
  • frameworks/react-native/src/hooks/useField/useField.ts
  • frameworks/react-native/src/hooks/useFieldArray/index.ts
  • frameworks/react-native/src/hooks/useFieldArray/useFieldArray.test-d.ts
  • frameworks/react-native/src/hooks/useFieldArray/useFieldArray.test.tsx
  • frameworks/react-native/src/hooks/useFieldArray/useFieldArray.ts
  • frameworks/react-native/src/hooks/useForm/index.ts
  • frameworks/react-native/src/hooks/useForm/useForm.test-d.ts
  • frameworks/react-native/src/hooks/useForm/useForm.test.tsx
  • frameworks/react-native/src/hooks/useForm/useForm.ts
  • frameworks/react-native/src/hooks/useSignals/index.ts
  • frameworks/react-native/src/hooks/useSignals/useSignals.test.tsx
  • frameworks/react-native/src/hooks/useSignals/useSignals.ts
  • frameworks/react-native/src/index.ts
  • frameworks/react-native/src/types/field.ts
  • frameworks/react-native/src/types/form.ts
  • frameworks/react-native/src/types/index.ts
  • frameworks/react-native/src/vitest/setup.ts
  • frameworks/react-native/tsconfig.json
  • frameworks/react-native/tsdown.config.ts
  • frameworks/react-native/vite.config.ts
  • frameworks/react-native/vitest.config.ts
  • frameworks/react/README.md
  • frameworks/solid/README.md
  • frameworks/svelte/README.md
  • frameworks/vue/README.md
  • packages/core/src/field/focusFieldElement/focusFieldElement.react-native.test.ts
  • packages/core/src/field/focusFieldElement/focusFieldElement.react-native.ts
  • playgrounds/react-native/.gitignore
  • playgrounds/react-native/App.tsx
  • playgrounds/react-native/README.md
  • playgrounds/react-native/app.json
  • playgrounds/react-native/babel.config.js
  • playgrounds/react-native/eslint.config.js
  • playgrounds/react-native/index.ts
  • playgrounds/react-native/metro.config.js
  • playgrounds/react-native/package.json
  • playgrounds/react-native/scripts/qa-ios.sh
  • playgrounds/react-native/tsconfig.json
  • pnpm-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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +31 to +42
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add React Native framework adapter

2 participants