<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Valentine Tikhomirov</title>
    <description>The latest articles on DEV Community by Valentine Tikhomirov (@zemant91).</description>
    <link>https://dev.to/zemant91</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4139040%2F982c25cb-8eab-4f82-b3b2-2a4033b06111.jpg</url>
      <title>DEV Community: Valentine Tikhomirov</title>
      <link>https://dev.to/zemant91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zemant91"/>
    <language>en</language>
    <item>
      <title>Your iPhone in Parallax, and agent taps 6 to 20 times faster</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Fri, 06 Nov 2026 10:47:25 +0000</pubDate>
      <link>https://dev.to/zemant91/your-iphone-in-parallax-and-agent-taps-6-to-20-times-faster-37bg</link>
      <guid>https://dev.to/zemant91/your-iphone-in-parallax-and-agent-taps-6-to-20-times-faster-37bg</guid>
      <description>&lt;p&gt;&lt;em&gt;A Galaxy S24 and an iPhone 13 in one Parallax window, running the same test app on one Metro. The agent taps on the S24; the iPhone is&lt;br&gt;
watch only, and a pick on its picture opens &lt;code&gt;src/CompatScreen.tsx:16&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Parallax is a desktop app and an MCP server that give you and your coding agent the same live view of a running React Native app: the&lt;br&gt;
screen, the component tree, logs, network and state. Three releases went out this week. Here is what changed, how one of the changes&lt;br&gt;
works, and what is still missing.&lt;/p&gt;
&lt;h3&gt;
  
  
  What shipped
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;beta.8: a real iPhone in the window, watch only.&lt;/strong&gt; Plug the phone in with a data cable, and the window shows its live screen next to
the app's component tree, logs, network requests and state. You can point at an element on the picture and see its component in the
Inspector. The agent can take screenshots of the phone. It can't tap on it yet. Together with beta.7's Android phones, this means an
Android phone and an iPhone can run side by side in one window, as in the clip above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beta.7: agent taps 6 to 20 times faster.&lt;/strong&gt; A tap went from 3.3 s to 0.5 s on the iOS simulator, and from 14 s to 0.6–0.8 s on a
Galaxy S24. The same release added physical Android phones, and in strict approval mode a third button on input cards, "Always allow
taps, swipes and typing". Reloads, launches and other commands still ask.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beta.6: faster taps with the window closed.&lt;/strong&gt; When the app isn't open, the agent's iOS taps now go through the screen helper: 5.1 s to
3.3 s on the simulator.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Reading the screen from React instead of the system
&lt;/h3&gt;

&lt;p&gt;Every agent tap reads the screen twice. Before the tap, the tool needs to find the element and check that the app is in front. After&lt;br&gt;
the tap, it tells the agent what changed. Until beta.7 both reads were the platform's accessibility dump: AXe on iOS, &lt;code&gt;uiautomator&lt;/code&gt; on&lt;br&gt;
Android.&lt;/p&gt;

&lt;p&gt;Those dumps are slow. On a still screen they took 0.6 to 3 s. On an Android screen with an animation, &lt;code&gt;uiautomator&lt;/code&gt; waits for the screen&lt;br&gt;
to go idle, and on our busy test screen it gave up after 11 s. One tap took 14 s.&lt;/p&gt;

&lt;p&gt;A React Native app already knows what is on its screen. Parallax reads the app's React tree through Metro, and from it builds the same&lt;br&gt;
list the dump gives: each view with a testID, a label, text, or a text input, with its frame measured by the app itself. That takes 5 to&lt;br&gt;
100 ms. The testIDs are the same, and 33 of 34 frames we compared matched the dump's to within a point.&lt;/p&gt;

&lt;p&gt;So when the app is connected and in front, both reads now come from React. A tap is 0.50 to 0.53 s on the iOS simulator and 0.60 to&lt;br&gt;
0.84 s on the S24, measured over 5 taps by id and 5 by point on each. The first tap of a session still takes about 4.9 s, because it opens&lt;br&gt;
the input session and reads the dump once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzfdmni990vgrhbf0qywf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzfdmni990vgrhbf0qywf.gif" alt="agent-taps" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The agent's taps on the iOS simulator: count+ three times, then the perf lab opened and closed. The "Last action" line under the picture&lt;br&gt;
updates after each tap.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The dump stays for what React can't see: system alerts and permission prompts, the keyboard, another app in front, an app that isn't&lt;br&gt;
connected. If the app loses its connection to Metro, every read falls back to the dump, and taps are as slow as before.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why the iPhone is watch only, and what is next
&lt;/h3&gt;

&lt;p&gt;Showing an iPhone and controlling one are different problems.&lt;/p&gt;

&lt;p&gt;Showing it is the cheap half. macOS can treat a plugged-in iPhone as a video source. Our capture helper, a small piece of native code of&lt;br&gt;
our own, streams that picture to the window. The app's data comes through Metro as on a simulator, so the Inspector, logs, network and&lt;br&gt;
state work unchanged. Nothing is installed on the phone, and you need no Apple account. macOS asks once for camera access, because that&lt;br&gt;
is how it classes the iPhone's picture.&lt;/p&gt;

&lt;p&gt;Pointing works on the iPhone's picture as on a simulator's, because a pick reads React, not input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft28pwtfyokchn5ujlo7u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft28pwtfyokchn5ujlo7u.gif" alt="picks" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On the simulator: Select element, a click on "fetch", and the Inspector opens &lt;code&gt;src/CompatScreen.tsx:16&lt;/code&gt;. The pick on the iPhone in the&lt;br&gt;
first clip works the same way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Controlling it is the expensive half. Sending a tap to a real iPhone needs a signed test runner on the phone. That is the next plan, and&lt;br&gt;
until it lands, the agent's tap, swipe and type tools on an iPhone answer that this needs a later version. You tap on the phone itself.&lt;/p&gt;

&lt;p&gt;One setup note. With React Native 0.86 and the prebuilt React Core, an app on a device ignores &lt;code&gt;RCT_METRO_PORT&lt;/code&gt; and connects to port 8081.&lt;br&gt;
If your Metro runs on another port, start the app from Xcode with this launch argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-RCT_jsLocation &amp;lt;your Mac's IP&amp;gt;:&amp;lt;port&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parallax doesn't install or launch your app for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The limits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;macOS 26 or later, on Apple silicon only.&lt;/li&gt;
&lt;li&gt;The iPhone is watch only: the agent gets screenshots, not taps. You need a data cable (over Wi-Fi there is no picture), the phone must
trust the Mac, and Developer Mode must be on.&lt;/li&gt;
&lt;li&gt;The faster taps need the React Native app connected through Metro and in front. Otherwise the system dump is used, as before.&lt;/li&gt;
&lt;li&gt;Our numbers come from one test app, on one iPhone simulator and one Galaxy S24.&lt;/li&gt;
&lt;li&gt;It's a beta and free while it lasts. Things will break.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The desktop app is in closed beta: ask for an invite at &lt;a href="https://parallax-rn.dev/download" rel="noopener noreferrer"&gt;https://parallax-rn.dev/download&lt;/a&gt;. Corrections to anything above are welcome.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ios</category>
      <category>android</category>
      <category>ai</category>
    </item>
    <item>
      <title>Giving an AI agent eyes on a running React Native app</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Tue, 03 Nov 2026 20:02:35 +0000</pubDate>
      <link>https://dev.to/zemant91/giving-an-ai-agent-eyes-on-a-running-react-native-app-jfe</link>
      <guid>https://dev.to/zemant91/giving-an-ai-agent-eyes-on-a-running-react-native-app-jfe</guid>
      <description>&lt;p&gt;Over the last few months I've posted small findings about AI agents, MCP and React Native internals. They were all side effects of building one tool. This post is about the tool: &lt;strong&gt;Parallax&lt;/strong&gt;, and the problems that shaped it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;An AI agent working on a React Native app sees source files and a terminal. It doesn't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the screen.&lt;/strong&gt; "Fix the padding on that button" means nothing without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;which component is which.&lt;/strong&gt; The rendered tree is mostly library wrappers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;runtime errors.&lt;/strong&gt; In 90 seconds of an app running, the simulator's native log had 3,498 events, and 0 of them came from the app, React Native or Hermes. JS errors live in the JS runtime, not in os_log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the agent guesses, and you end up copying logs and describing screenshots for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdu9nj2fpx4nwtzftd0hg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdu9nj2fpx4nwtzftd0hg.gif" alt="parallax-demostration" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Parallax is
&lt;/h3&gt;

&lt;p&gt;Two halves that share one live connection to the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An MCP server for the agent&lt;/strong&gt; (Claude Code and Codex). Tools for the screen tree with &lt;code&gt;file:line&lt;/code&gt;, console logs, errors, screenshots, taps, swipes, typing, reload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A desktop window for the human.&lt;/strong&gt; The live simulator screen with real input, &lt;em&gt;Select element&lt;/em&gt; → an inspector with the component, props and source line → &lt;em&gt;Send to agent&lt;/em&gt;, approval cards for the agent's device actions, two devices side by side.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Setup is one command per agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @parallax-rn/mcp@&amp;lt;version&amp;gt; setup claude
npx &lt;span class="nt"&gt;-y&lt;/span&gt; @parallax-rn/mcp@&amp;lt;version&amp;gt; setup codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows a diff of what it will change and asks before writing anything. &lt;code&gt;setup ... --undo&lt;/code&gt; removes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Things that turned out harder than expected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Element → source line.&lt;/strong&gt; React 19 removed &lt;code&gt;_debugSource&lt;/code&gt;. The location now lives in &lt;code&gt;_debugStack&lt;/code&gt;, which you symbolicate through Metro. The catch: for elements inside &lt;code&gt;.map(...)&lt;/code&gt;, the line points to the start of the callback, not the JSX tag (8 of 22 points in my test, with the tag 14–24 lines lower). Parallax searches forward for &lt;code&gt;&amp;lt;ComponentName&lt;/code&gt; up to 40 lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tree is mostly noise.&lt;/strong&gt; On a real screen: 209 rows, 126 of them wrappers (&lt;code&gt;View_withRef&lt;/code&gt; 57, SVG parts 45, &lt;code&gt;PressabilityDebugView&lt;/code&gt; 23). The window folds them unless a row has a testID, label, text or handler, leaving 83. The agent still gets the full tree: hiding things from the model is a different decision than hiding them from a human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unhandled rejections hide.&lt;/strong&gt; In older RN they're a &lt;code&gt;console.warn&lt;/code&gt;; in RN 0.87 a &lt;code&gt;console.error&lt;/code&gt; via &lt;code&gt;ExceptionsManager&lt;/code&gt;. In both cases the global &lt;code&gt;ErrorUtils&lt;/code&gt; handler never sees them, so a tool that only hooks there misses all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A tap is fast, checking it is slow.&lt;/strong&gt; &lt;code&gt;device_tap&lt;/code&gt; takes 1.3–2.3 s end to end, but the tap itself is 56 ms. The rest is reading the accessibility tree and waiting for the screen to settle. If you want a faster agent loop, speed up verification, not input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One &lt;code&gt;simctl list&lt;/code&gt; doubled click latency.&lt;/strong&gt; Adding a second simulator put a full &lt;code&gt;xcrun simctl list&lt;/code&gt; (~100 ms) on the pick path. Click → inspector went from 77 ms to 156 ms p50, and nobody noticed for a whole step because that check measured a different number. Now every number has one definition and a threshold in the script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why there are approvals
&lt;/h3&gt;

&lt;p&gt;While testing, an agent crashed the simulator's &lt;code&gt;backboardd&lt;/code&gt; three times on purpose "to confirm the diagnosis". It wasn't malicious, just thorough. Since then the agent's device actions can go through approval cards in the window, with a strict mode if you want every action confirmed. Secret-looking props (&lt;code&gt;authToken&lt;/code&gt;, &lt;code&gt;apiKey&lt;/code&gt;) are masked in the inspector, matched by camelCase/snake_case words so &lt;code&gt;passenger&lt;/code&gt; and &lt;code&gt;authorName&lt;/code&gt; stay readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Click on screen → inspector ready&lt;/td&gt;
&lt;td&gt;77 ms p50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component pick at a point&lt;/td&gt;
&lt;td&gt;~4–5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live stream&lt;/td&gt;
&lt;td&gt;44–52 fps (screenshots: 2.3 fps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP cold start via npx&lt;/td&gt;
&lt;td&gt;4.3 s → ~1.5 s (one bundled file)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool descriptions in context&lt;/td&gt;
&lt;td&gt;~2,500 tokens, guarded by a test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What it is and isn't (yet)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;macOS; iOS simulator and Android emulator. No real devices yet.&lt;/li&gt;
&lt;li&gt;Bare React Native first; Expo works.&lt;/li&gt;
&lt;li&gt;Runs locally. No account, nothing about your app leaves your machine.&lt;/li&gt;
&lt;li&gt;Free beta. The agent tools stay free.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;The beta is out and free: &lt;a href="https://parallax-rn.dev/" rel="noopener noreferrer"&gt;https://parallax-rn.dev/&lt;/a&gt; (install guide: &lt;a href="https://parallax-rn.dev/docs/getting-started/install" rel="noopener noreferrer"&gt;https://parallax-rn.dev/docs/getting-started/install&lt;/a&gt;). The app is also a direct download from GitHub: &lt;a href="https://github.com/parallax-rn/parallax-releases/releases/tag/v0.1.0-beta.2" rel="noopener noreferrer"&gt;https://github.com/parallax-rn/parallax-releases/releases/tag/v0.1.0-beta.2&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If you work on React Native with Claude Code or Codex, I want to know where it breaks for you, and the site has a short feedback form for that.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>devtool</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Input said OK, nothing happened: Xcode 27, Device Hub and a flag that eats your taps</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:35:34 +0000</pubDate>
      <link>https://dev.to/zemant91/input-said-ok-nothing-happened-xcode-27-device-hub-and-a-flag-that-eats-your-taps-5e38</link>
      <guid>https://dev.to/zemant91/input-said-ok-nothing-happened-xcode-27-device-hub-and-a-flag-that-eats-your-taps-5e38</guid>
      <description>&lt;p&gt;If you drive the iOS simulator from scripts, test tools or AI agents, this one can cost you an afternoon: every tap is acknowledged, every command returns &lt;code&gt;ok&lt;/code&gt;, and nothing happens on screen.&lt;/p&gt;

&lt;p&gt;I hit it while building a tool that lets an agent and me work on the same running React Native app. Here's what's going on, how to tell you're in it, and how to get out.&lt;/p&gt;

&lt;p&gt;All of this is on Xcode 27.0 with iOS 26.3 simulators, on one Mac. If you're on a different setup, your mileage may vary, and I'd love to hear if you see it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the change: no more Simulator.app
&lt;/h2&gt;

&lt;p&gt;Xcode 27 doesn't ship a separate Simulator.app anymore. You look at simulators through &lt;strong&gt;Device Hub&lt;/strong&gt; inside Xcode.&lt;/p&gt;

&lt;p&gt;Two side effects worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Closing the viewer can shut the device down.&lt;/strong&gt; If you were relying on "the window is open, so the simulator is running", that's no longer a safe assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't need a viewer at all.&lt;/strong&gt; &lt;code&gt;xcrun simctl boot &amp;lt;udid&amp;gt;&lt;/code&gt; brings the simulator up without any window, and anything that streams the screen or sends input to it keeps working. For automation this is actually nicer: nothing to keep open, nothing for a human to close by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bug: input that says yes and does nothing
&lt;/h2&gt;

&lt;p&gt;One day input to one simulator simply stopped working. Not failing: &lt;strong&gt;stopped working while reporting success.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I measured it on a local test page that logs every pointer event:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 taps and one typed word sent to the simulator;&lt;/li&gt;
&lt;li&gt;every single one acknowledged by the input layer in 52–113 ms, no errors;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 events reached the page.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Home and App Switcher commands also answered &lt;code&gt;ok&lt;/code&gt;. The screen didn't move.&lt;/p&gt;

&lt;p&gt;A second simulator on the same machine, same Xcode, same everything, worked fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause: a flag Device Hub leaves behind
&lt;/h2&gt;

&lt;p&gt;The difference between the two simulators turned out to be one Darwin notification state inside the simulator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com.apple.coredevice.dtuhidd.active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;On the broken simulator it was &lt;strong&gt;1&lt;/strong&gt;, while no Device Hub process was running at all.&lt;/li&gt;
&lt;li&gt;On the healthy one it was &lt;strong&gt;0&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not a defaults key or a plist, it's a notify state held by the simulator's &lt;code&gt;backboardd&lt;/code&gt;. As far as I can tell, it means "Device Hub attached to this simulator's input during this boot". While Device Hub is attached, input goes through its channel. When Device Hub goes away, the state stays at 1, and input from any other source is quietly ignored.&lt;/p&gt;

&lt;p&gt;Just &lt;em&gt;opening&lt;/em&gt; Device Hub and showing the simulator didn't set it in my tests; it stayed 0. Interacting with it from Device Hub seems to be what does. The name isn't documented anywhere I could find (I first saw it in the source of an open-source simulator tool), so treat the mechanism as my best reading of the evidence, not Apple's description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The state survives closing Device Hub. It survives closing Xcode. It goes away only when the simulator reboots.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After a reboot: flag 0, input reaches the app again.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check yours
&lt;/h2&gt;

&lt;p&gt;Two read-only commands. First, the state inside a booted simulator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun simctl spawn &amp;lt;udid&amp;gt; notifyutil &lt;span class="nt"&gt;-g&lt;/span&gt; com.apple.coredevice.dtuhidd.active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints &lt;code&gt;com.apple.coredevice.dtuhidd.active 0&lt;/code&gt; or &lt;code&gt;... 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, whether Device Hub is actually running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pgrep &lt;span class="nt"&gt;-x&lt;/span&gt; DeviceHub &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Device Hub running"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"no Device Hub process"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read the pair:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Device Hub process&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Free, input from your tools works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;running&lt;/td&gt;
&lt;td&gt;Device Hub is attached right now, expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;not running&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Stale: input will be silently dropped&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;notifyutil -g&lt;/code&gt; only. &lt;code&gt;-s&lt;/code&gt; &lt;em&gt;sets&lt;/em&gt; the state, which you don't want to do on a whim. The simulator has to be booted for &lt;code&gt;simctl spawn&lt;/code&gt; to work.&lt;/p&gt;

&lt;p&gt;The symptom is also easy to recognise once you know it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input commands succeed with no errors.&lt;/li&gt;
&lt;li&gt;Nothing on screen reacts, including system gestures like Home.&lt;/li&gt;
&lt;li&gt;Another simulator on the same machine works.&lt;/li&gt;
&lt;li&gt;You (or a teammate, or a tool) used that simulator from Device Hub at some point since it was booted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're in the stale state, reboot the simulator before you debug anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun simctl shutdown &amp;lt;udid&amp;gt;
xcrun simctl boot &amp;lt;udid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this is worse for agents than for people
&lt;/h2&gt;

&lt;p&gt;A person notices within seconds that taps don't do anything. An automated check often doesn't, for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The acknowledgement is a lie.&lt;/strong&gt; If your tool treats "input accepted" as "input delivered", every step passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The screen changed" can lie too.&lt;/strong&gt; The simulator's video stream keeps sending frames even when nothing moves, so "a frame arrived after the tap" proves nothing. My own heuristic that looks at frame sizes to decide whether a tap landed said "confirmed" once while in this state, because a page happened to load at the same moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So an agent can walk through a whole flow, report success, and never have touched the app.&lt;/p&gt;

&lt;p&gt;What I ended up doing in my tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detect the stale flag (key set, no Device Hub process) and show the human a one-line note under the picture;&lt;/li&gt;
&lt;li&gt;mark input as &lt;strong&gt;unverified&lt;/strong&gt; instead of confirmed while the simulator is in that state;&lt;/li&gt;
&lt;li&gt;add a warning to the agent's tool replies: &lt;em&gt;input is likely blocked by a flag Device Hub left on this simulator; ask the user to reboot the simulator.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one matters most. The agent can't fix this itself, but it can stop pretending everything worked and ask the human for the one action that does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On Xcode 27, a simulator doesn't need a window.&lt;/strong&gt; &lt;code&gt;simctl boot&lt;/code&gt; is enough for streaming and input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device Hub can leave a state that silently swallows input until the simulator reboots.&lt;/strong&gt; One &lt;code&gt;notifyutil -g&lt;/code&gt; plus one &lt;code&gt;pgrep&lt;/code&gt; tells you if you're in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Input accepted" is not "input delivered".&lt;/strong&gt; If you automate the simulator, verify against what the app actually received.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the tool can't fix something, it should say so.&lt;/strong&gt; An honest warning to the agent beats a green check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have you run into this, or something like it, on Xcode 27? I'd like to know whether it's my setup or more common than it looks.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>reactnative</category>
      <category>testing</category>
    </item>
    <item>
      <title>My harness taught the agent how to build. Now it needs to see.</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Thu, 08 Oct 2026 20:55:51 +0000</pubDate>
      <link>https://dev.to/zemant91/my-harness-taught-the-agent-how-to-build-now-it-needs-to-see-2jpg</link>
      <guid>https://dev.to/zemant91/my-harness-taught-the-agent-how-to-build-now-it-needs-to-see-2jpg</guid>
      <description>&lt;p&gt;At the end of my &lt;a href="https://dev.to/zemant91/my-agent-built-a-feature-then-tapped-through-it-on-the-simulator-4ke6"&gt;last post&lt;/a&gt; I said the next step was letting my pipeline iterate on its own review findings. I didn't get there. I ran into something more basic first: the part of the harness that lets the agent use the app.&lt;/p&gt;

&lt;p&gt;That part, &lt;code&gt;rn-app-driver&lt;/code&gt;, reads the React component tree and taps the simulator so the agent can check its own work. It did its job. But the more I used it, the more it felt like the wrong shape for the problem. So I pulled it out of the harness and started building a separate tool around it.&lt;/p&gt;

&lt;p&gt;This post is about why, and about a few things an agent simply can't learn from reading your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the driver stopped fitting into a skill
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Only the agent could see it.&lt;/strong&gt; The driver ran inside the agent's session. While it was tapping through my app, I was looking at a terminal. I only found out what it actually did by reading the log afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It only worked in one agent.&lt;/strong&gt; It was built as a Claude Code skill. I also use Codex, and I'd like the same capability to work with whatever agent comes next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety by word list doesn't hold.&lt;/strong&gt; After the last post, two readers pointed out the same weak spot: the driver decided whether an action was risky by its label. "Delete" and "Save" stopped for confirmation, but a button labelled "Done" could change data and sail right through. They were right. The real fix isn't a better word list. It's putting the decision in front of a human who can see the screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A component name isn't a location.&lt;/strong&gt; The driver told the agent "this is &lt;code&gt;HomeActions/log-attack&lt;/code&gt;". What the agent actually needs next is "this is &lt;code&gt;HomeActions.tsx:42&lt;/code&gt;", so it can open the right file instead of searching for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things an agent can't see from your code
&lt;/h2&gt;

&lt;p&gt;While building this, I kept measuring what the running app tells you that the source doesn't. Three findings stood out. All measured on one Mac, iOS simulator, RN 0.87 with React 19.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. JS errors leave no trace in the iOS system log.&lt;/strong&gt; I recorded 90 seconds of normal app use: 3,498 native log events. Every single one came from Apple's frameworks. Zero from the app, React Native or Hermes, including during deliberate JS errors. If your agent "checks the device logs", it's checking the wrong place for anything that happens in JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. In React 19, component locations can be off by 20 lines.&lt;/strong&gt; React 19 removed &lt;code&gt;_debugSource&lt;/code&gt;, the old way to map an element back to its file and line. The replacement points elements rendered inside &lt;code&gt;.map(...)&lt;/code&gt; at the start of the callback, not at the tag. In a real app that was 8 out of 22 points I tested, with the actual tag 14–24 lines further down. An agent trusting that line edits the wrong place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. "The screen changed after the tap" proves nothing.&lt;/strong&gt; The simulator's video stream keeps sending around 53 frames per second even when nothing on screen moves. So "a new frame arrived" is not evidence that your tap landed. What did work: comparing frame &lt;em&gt;size&lt;/em&gt; against the idle background. A real UI reaction produced frames of 4–29 KB, idle ones were well under 2 KB.&lt;/p&gt;

&lt;p&gt;None of this is visible in a diff. All of it matters when an agent claims "I checked, it works".&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm building
&lt;/h2&gt;

&lt;p&gt;A desktop app for React Native development where the agent and I look at the same running app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For the agent:&lt;/strong&gt; the component tree with real &lt;code&gt;file:line&lt;/code&gt; locations, logs and errors from the app, the simulator screen, taps, gestures and typing. It connects over MCP, so it works with Claude Code and Codex today, and with any MCP-capable agent in principle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For me:&lt;/strong&gt; the live simulator, what the agent is doing on it, and an approval card for risky actions. The agent asks, I see the screen and the action, I decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One rule I won't bend:&lt;/strong&gt; the tool never types into my terminal for me. It can paste text, but Enter is always mine. I learned this the hard way when a test typed a message plus Enter into a terminal running an agent in auto mode, and kicked off a model turn nobody asked for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first stage, the part the agent talks to, already works and has been tested with both Claude Code and Codex. The desktop app is in progress: live iOS simulator at around 48 fps, and clicking anywhere on the screen finds the component under that point in about 5 ms.&lt;/p&gt;

&lt;p&gt;The harness isn't going anywhere. It stays the "how to build" part: skills, review agents, pipelines. The new tool becomes its eyes and hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest result
&lt;/h2&gt;

&lt;p&gt;To check that the tool actually helps, I planted a bug in my gym app and asked both agents to find it. They found it in 6 out of 6 runs. Then I realised they found it by reading the code, without needing the tool at all.&lt;/p&gt;

&lt;p&gt;That's the lesson I keep coming back to. If you want to show that runtime tooling helps an agent, you have to test it on bugs that aren't visible in the source: wrong state, timing, a layout that only breaks on device. Otherwise you're measuring how well the model reads code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'll write up the most surprising findings separately, with numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;streaming the iOS simulator into an Electron app at ~50 fps (screenshots topped out at 2.3 fps);&lt;/li&gt;
&lt;li&gt;why an environment variable from your terminal never reaches your MCP server in Codex;&lt;/li&gt;
&lt;li&gt;a trap for anyone writing MCP servers: what Claude Code actually shows the model when a tool returns both text and structured content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a question for you: how do you check what your agent did in the app right now? Do you open the simulator yourself after every run, or trust the summary?&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>devtools</category>
    </item>
    <item>
      <title>My agent built a feature, then tapped through it on the simulator</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Mon, 28 Sep 2026 16:48:08 +0000</pubDate>
      <link>https://dev.to/zemant91/my-agent-built-a-feature-then-tapped-through-it-on-the-simulator-4ke6</link>
      <guid>https://dev.to/zemant91/my-agent-built-a-feature-then-tapped-through-it-on-the-simulator-4ke6</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/zemant91/i-stopped-prompting-and-started-building-a-harness-2hg2"&gt;previous post&lt;/a&gt; I tested a single skill from my React Native harness: a refactoring agent. This time I ran the whole thing end to end for the first time. One confirmation from me, then the agent planned a feature, built it, tested it, tapped through it on the iOS simulator, and ran a review pass on its own code.&lt;/p&gt;

&lt;p&gt;Here's how it's wired and what actually happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  One checkpoint, then hands-off
&lt;/h2&gt;

&lt;p&gt;The skill is called &lt;code&gt;feature-pipeline&lt;/code&gt;. Before touching code, it sends me a single message with the proposed scope, the design direction, and how data, state and errors will be handled. I approve once, and from then on it doesn't stop to ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the UI and logic.&lt;/li&gt;
&lt;li&gt;Test it.&lt;/li&gt;
&lt;li&gt;Verify on device: drive the running app on the simulator.&lt;/li&gt;
&lt;li&gt;Review: call the harness's other agents as real subagents (refactoring, test coverage, architecture review, plus instrumentation agents if the feature needs them).&lt;/li&gt;
&lt;li&gt;Report: one consolidated list of what was done and what's still open.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It never merges to &lt;code&gt;main&lt;/code&gt; and never touches deployment or store submission. Those are hard boundaries, not settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm most excited about: how the agent sees the app
&lt;/h2&gt;

&lt;p&gt;Step 3 uses another skill, &lt;code&gt;rn-app-driver&lt;/code&gt;. Most tools that let an agent use a mobile app work from screenshots: take a picture, guess where the button is, tap coordinates. That's slow and expensive in context.&lt;/p&gt;

&lt;p&gt;The driver reads the app's React component tree straight from Hermes instead. It takes around 10 ms and 1–3k characters, and looks like this (from my headache tracker):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screen: Home
@Home/scroll ScrollView [0,62,402,723]
  "Saturday" "September 26"
  HomeActions
    @HomeActions/log-attack Pressable "Log attack" icon:Plus [24,561,354,84]
TabBar
  @TabBar/history Pressable "History" icon:TabHistory [101,796,100,44]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;@id&lt;/code&gt; is something the agent can act on, with stable ids across runs and a real frame in points. Taps are real touch events on the simulator (via AXe), aimed at those frames, so the agent never guesses coordinates. Screenshots are still there for visual checks, just not for navigation.&lt;/p&gt;

&lt;p&gt;A few rules make it usable rather than scary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One action per command. Each action waits for the UI to settle and prints the new screen, and the agent checks it before deciding the next step. No planning five taps ahead on screens it hasn't seen.&lt;/li&gt;
&lt;li&gt;A safety policy. Anything whose label looks like it changes data (save, delete, log, edit, and so on) stops with an error until I explicitly confirm that specific action.&lt;/li&gt;
&lt;li&gt;Everything is logged. Each session writes an action log, and a walked flow can be saved as a test case and replayed later. The replay exits with 0 or 1, so it can eventually become a regression gate in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The run
&lt;/h2&gt;

&lt;p&gt;The target was a new "Exercises list" screen in my gym rep counter app, another side project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build: about 5–7 minutes from approval to working code.&lt;/li&gt;
&lt;li&gt;On-device check: a few more minutes, and it took several runs. Verifying a screen like this needs the app in exactly the right state with the right data, and getting there reliably is the hard part.&lt;/li&gt;
&lt;li&gt;Review agents: found only code smells, nothing structural.&lt;/li&gt;
&lt;li&gt;Result: a report with recommendations prioritized by importance. No PR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one was my decision, not a failure. With this much access, a run can end in many ways, and for now I want the irreversible steps (committing, pushing, opening a PR) to stay with a human. A full cycle with builds and pushes is on the list to test later, deliberately and separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;The speed and autonomy, honestly. Scoping was sensible, it picked the right agents, and it really did use the app on the simulator instead of just reading its own code back.&lt;/p&gt;

&lt;p&gt;But it made mistakes, and the interesting part is that it pointed them out itself in the final report as things worth fixing. Which raises the obvious question: if the agent already knows what's wrong, why stop after one pass?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Read the structure, not the pixels. For navigating an app, the component tree beats screenshots on speed, cost and reliability. Screenshots are for checking how things look.&lt;/li&gt;
&lt;li&gt;State is the real problem of on-device testing. Tapping is easy. Reliably getting the app into the exact state a check needs is not.&lt;/li&gt;
&lt;li&gt;An agent that lists its own mistakes deserves a second pass. Running the implement-and-review cycle several times, not once, is my next priority.&lt;/li&gt;
&lt;li&gt;Keep the irreversible steps human, for now. Autonomy up to a tested, reviewed result is great. What happens with that result is still my call.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Let the pipeline iterate on its own recommendations for a few cycles and compare the result with a single pass.&lt;/li&gt;
&lt;li&gt;Start saving walked flows as replayable test cases and keeping the logs, so the next posts can show real traces and numbers instead of summaries.&lt;/li&gt;
&lt;li&gt;Test the full cycle, including commits and pushes, on a project where that's safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're letting agents drive your app on a simulator: how do you deal with test data and state? That's the part I'd most like to compare notes on.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>testing</category>
      <category>mobile</category>
    </item>
    <item>
      <title>I stopped prompting and started building a harness</title>
      <dc:creator>Valentine Tikhomirov</dc:creator>
      <pubDate>Wed, 23 Sep 2026 11:37:27 +0000</pubDate>
      <link>https://dev.to/zemant91/i-stopped-prompting-and-started-building-a-harness-2hg2</link>
      <guid>https://dev.to/zemant91/i-stopped-prompting-and-started-building-a-harness-2hg2</guid>
      <description>&lt;p&gt;For the last few months I've been using AI agents in almost every part of my React Native work. At some point I noticed I was writing the same instructions over and over: how I want projects set up, what a good refactoring looks like, what to check before shipping. Every new session started from zero.&lt;/p&gt;

&lt;p&gt;So I started turning those instructions into a harness: a set of skills and agents that encode how I actually work. It's still in alpha, I use it daily on my own projects, and here's how it's built and what I've learned so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a plugin and not a folder of prompts
&lt;/h2&gt;

&lt;p&gt;My first version was just files copied into &lt;code&gt;~/.claude&lt;/code&gt;. Now the harness is a proper Claude Code plugin in its own git repo. Every skill and agent is called with an &lt;code&gt;rnmh:&lt;/code&gt; prefix, so it's clear what comes from the harness and what doesn't.&lt;/p&gt;

&lt;p&gt;The biggest change wasn't organizational, though. Once each task had its own dedicated skill, the agent's answers became noticeably more structured, and the results got better with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside right now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project bootstrap.&lt;/strong&gt; Deliberately minimal: it fixes only strict TypeScript and the folder organization I choose, and asks about everything else per project. An opinionated template ages badly; a short interview doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring skill + agent.&lt;/strong&gt; Built on the catalog from Martin Fowler's &lt;em&gt;Refactoring&lt;/em&gt; (2nd edition), grouped by its chapters. The key decision: it applies refactorings instead of just listing code smells.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-project consistency agent.&lt;/strong&gt; I work on several RN projects in parallel. This agent compares them for duplicated code and naming drift, without assuming there's a shared library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design-to-code, architecture review, testing and test coverage&lt;/strong&gt; round out the core set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnostics, release checklist, security review and RN upgrades&lt;/strong&gt; are the newest additions. They're written, but I haven't battle-tested them on a real project yet, so they're next in line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First real test
&lt;/h2&gt;

&lt;p&gt;I ran the refactoring agent on my own side project, a headache tracker that's currently in alpha. The home screen had grown into a classic &lt;em&gt;Long Function&lt;/em&gt;: one &lt;code&gt;Home()&lt;/code&gt; component of ~330 lines with 5 &lt;code&gt;useState&lt;/code&gt;, 5 &lt;code&gt;useEffect&lt;/code&gt;, 3 async handlers and a three-way JSX branch, all inline.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;openAttack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOpenAttack&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Attack&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRecent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Attack&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setNow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;systemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loadError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoadError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;reduceMotion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setReduceMotion&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;AccessibilityInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isReduceMotionEnabled&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setReduceMotion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AccessibilityInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reduceMotionChanged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;setReduceMotion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...4 more effects, 3 async handlers, ~150 lines of inline JSX&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reduceMotion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReduceMotion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...same effects and handlers, unchanged&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;middle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openAttack&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OngoingAttackCard&lt;/span&gt;
          &lt;span class="na"&gt;openAttack&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openAttack&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;pulseStyle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pulseStyle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onSetIntensity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSetIntensity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RecentAttacksList&lt;/span&gt; &lt;span class="na"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emptyText&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.noAttacksYet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every change mapped to a named refactoring from the catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extract Component:&lt;/strong&gt; &lt;code&gt;IntensityPicker&lt;/code&gt;, &lt;code&gt;OngoingAttackCard&lt;/code&gt;, &lt;code&gt;RecentAttacksList&lt;/code&gt;, &lt;code&gt;HomeActions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract Function into hooks:&lt;/strong&gt; &lt;code&gt;useReduceMotion&lt;/code&gt;, plus &lt;code&gt;useDictation&lt;/code&gt; and a generic &lt;code&gt;useKeyboardVisible&lt;/code&gt; on the log-input screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move Function:&lt;/strong&gt; &lt;code&gt;formatTime()&lt;/code&gt; went to the shared formatting module, so it wouldn't be duplicated in two new components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove Dead Code:&lt;/strong&gt; an unused style and a dead import it found along the way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Home()&lt;/code&gt; went from ~330 to ~150 lines. The total line count actually grew a bit because of new files and imports, and that's fine: the point is that each piece now has one job. Behavior didn't change, since the agent kept refactoring strictly separate from feature work, and &lt;code&gt;tsc&lt;/code&gt; and &lt;code&gt;eslint&lt;/code&gt; came back clean.&lt;/p&gt;

&lt;p&gt;One honest caveat: the agent only ran static checks, not the app, so checking the screens on a simulator was still on me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've learned so far
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Narrow skills beat one giant prompt.&lt;/strong&gt; A skill that does one job with clear steps gives far more consistent output than a long general instruction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it act, not just advise.&lt;/strong&gt; A reviewer that only lists problems creates homework. One that makes the change and explains it saves time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understanding the project is still the weak spot.&lt;/strong&gt; Agents struggle to read a codebase and actually use that knowledge later. They can miss existing features, so I often have to steer them. Tools like graphify, which gives the agent a map of the codebase, help a lot here, but it's not solved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm planning to open the harness to other React Native developers. If you're building something similar, or would want a toolkit like this, I'd love to hear what you'd expect from it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mobile</category>
      <category>productivity</category>
      <category>reactnative</category>
    </item>
  </channel>
</rss>
