<?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: Nikolas M I</title>
    <description>The latest articles on DEV Community by Nikolas M I (@nikolas_mi_5f4aa63cc4080).</description>
    <link>https://dev.to/nikolas_mi_5f4aa63cc4080</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%2F4020976%2Fc77302c0-6e1a-48d9-84e8-be2e93cb2067.png</url>
      <title>DEV Community: Nikolas M I</title>
      <link>https://dev.to/nikolas_mi_5f4aa63cc4080</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikolas_mi_5f4aa63cc4080"/>
    <language>en</language>
    <item>
      <title>Our failed React Native template launch, dissected</title>
      <dc:creator>Nikolas M I</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:16:55 +0000</pubDate>
      <link>https://dev.to/nikolas_mi_5f4aa63cc4080/our-failed-react-native-template-launch-dissected-13hh</link>
      <guid>https://dev.to/nikolas_mi_5f4aa63cc4080/our-failed-react-native-template-launch-dissected-13hh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;v1 launch: $29 template, 180 hours of work, $87 revenue, 40% refund rate.&lt;/li&gt;
&lt;li&gt;Root cause: shipped a UI kit, marketed as "full-stack."&lt;/li&gt;
&lt;li&gt;Rebuild fixed: real Supabase backend, real docs, demo videos, agent-readiness, price tripled, refund policy surfaced.&lt;/li&gt;
&lt;li&gt;Refund rate on v2: ~4%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;We built a React Native template. Twelve screens, Expo, TypeScript, NativeWind, tab navigation, auth screens that looked like Instagram's. What we did &lt;em&gt;not&lt;/em&gt; ship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/mockApi.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getNotes&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;HARDCODED_NOTES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FAKE_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FAKE_SESSION&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;That was our "backend." A &lt;code&gt;.env.example&lt;/code&gt; referenced Supabase but nothing in the app actually called it. The landing page used the phrase "full-stack" four times.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened at launch
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch surface&lt;/td&gt;
&lt;td&gt;X, Indie Hackers, small subreddit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-4-hour visits&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales&lt;/td&gt;
&lt;td&gt;5 at $29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refunds&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue after refunds&lt;/td&gt;
&lt;td&gt;$87&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hours invested&lt;/td&gt;
&lt;td&gt;~180&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Effective hourly rate: about $0.48.&lt;/p&gt;

&lt;h2&gt;
  
  
  The angry email
&lt;/h2&gt;

&lt;p&gt;The refund email that mattered, paraphrased:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I thought this was going to save me a week. It saved me maybe an evening. The auth doesn't connect to anything, the API returns hardcoded data, and I still have to build the entire backend. Why did I pay $29 for what's basically a Figma export in TypeScript?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He was right. We had shipped a UI kit and called it full-stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Real backend, not &lt;code&gt;mockApi.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The rebuild replaced every mock with real Supabase. Migrations, seed data, RLS policies, Auth flows (email + Apple + Google), Storage with signed URLs, and Edge Functions proxying AI calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: real RLS policy shipped with every table&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"Users read their own notes"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"Users insert their own notes"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your buyer has to write their first RLS policy after unzipping your template, you have shipped an unfinished product. Reference: &lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;Supabase RLS docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tripled the price ($29 → $79)
&lt;/h3&gt;

&lt;p&gt;Counterintuitive but repeatable across cohorts. $29 attracted price-shopping customers whose expectations were miscalibrated. $79 attracts senior indies buying back time at a $100+/hour opportunity cost. Refund rate dropped from ~40% to ~4%.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Demo video per template
&lt;/h3&gt;

&lt;p&gt;40 seconds, silent, showing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:applighter/ai-voice-notes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-voice-notes
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;npx expo start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then sign-up, hero action, data landing in Supabase. Screenshots let skeptical devs assume the worst. Video removes the doubt.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 12,000-word docs per template
&lt;/h3&gt;

&lt;p&gt;We wrote 800 words for v1 and 12,000 for v2. The 15× increase doubled conversion. Buyers do not care how many screens you ship. They care whether they can be shipping &lt;em&gt;their&lt;/em&gt; version inside 24 hours. That's a docs problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Ship for agents
&lt;/h3&gt;

&lt;p&gt;The single biggest architectural change. In 2026, buyers point Claude Code, Codex, or Cursor at your repo before they read a file themselves. If the repo doesn't ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; at the root&lt;/li&gt;
&lt;li&gt;Slash commands / skills for the common workflows (add screen, add table, add AI provider)&lt;/li&gt;
&lt;li&gt;Real TypeScript types (no &lt;code&gt;any&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Predictable file layout an agent can grep&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…the agent flails, the buyer concludes your template is bad. Every current &lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=lessons-from-our-first-failed-react-native-template-launch" rel="noopener noreferrer"&gt;Applighter template&lt;/a&gt; ships with skills and slash commands pre-installed, tested against Claude Code, Codex, Cursor, and Windsurf.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Refund policy above the buy button
&lt;/h3&gt;

&lt;p&gt;"7-day refund, no questions asked." Refunds went up slightly (the ones we should have honored anyway). Chargebacks went to zero. Chargebacks cost more than refunds — both financially and to your Stripe account health.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1 vs v2 at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;v1 ($29)&lt;/th&gt;
&lt;th&gt;v2 ($79)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mockApi.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Real Supabase (Postgres + Auth + Storage + Edge Functions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RLS&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Per-table policies + seed users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;800 words&lt;/td&gt;
&lt;td&gt;~12,000 words + video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Demo&lt;/td&gt;
&lt;td&gt;Screenshots&lt;/td&gt;
&lt;td&gt;40-sec walkthrough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent readiness&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Skills + slash commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refund rate&lt;/td&gt;
&lt;td&gt;~40%&lt;/td&gt;
&lt;td&gt;~4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I'd tell 2025-me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the docs first. If they're hard to write, the product is wrong.&lt;/li&gt;
&lt;li&gt;Price at $79 minimum for anything with a real backend.&lt;/li&gt;
&lt;li&gt;Ship a real backend from day one. Mock nothing a buyer would reasonably expect to be real.&lt;/li&gt;
&lt;li&gt;Record one 40-second video per template. Silent. Happy path.&lt;/li&gt;
&lt;li&gt;Ship for agents. &lt;code&gt;CLAUDE.md&lt;/code&gt;, slash commands, real types. This is a moat.&lt;/li&gt;
&lt;li&gt;Refund policy above the buy button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gap between "nice UI I built in a week" and "template a senior indie will pay $79 for" is roughly three months of unglamorous infrastructure work. Skip any of it and the launch dies quietly.&lt;/p&gt;




&lt;p&gt;The longer version of this postmortem — including the comparison against building from scratch — is &lt;a href="https://the-path.hashnode.dev/the-react-native-template-launch-that-made-us-87" rel="noopener noreferrer"&gt;on my Hashnode blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the worst launch you've had, and what did it teach you? Drop it in the comments — misery loves company and I'd genuinely like to read them.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>startup</category>
    </item>
    <item>
      <title>Fixture-first prompting: how seed data changes what the model builds</title>
      <dc:creator>Nikolas M I</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:49:42 +0000</pubDate>
      <link>https://dev.to/nikolas_mi_5f4aa63cc4080/fixture-first-prompting-how-seed-data-changes-what-the-model-builds-1ek9</link>
      <guid>https://dev.to/nikolas_mi_5f4aa63cc4080/fixture-first-prompting-how-seed-data-changes-what-the-model-builds-1ek9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model skims your prompt, reads your types carefully, and treats your seed data as gospel.&lt;/li&gt;
&lt;li&gt;Three short happy-path rows produce a demo UI. Twenty messy rows produce a real one.&lt;/li&gt;
&lt;li&gt;Write the fixture &lt;em&gt;before&lt;/em&gt; the Figma frame and &lt;em&gt;before&lt;/em&gt; the prompt.&lt;/li&gt;
&lt;li&gt;Ship four fixture variants per entity: populated, empty, loading, error.&lt;/li&gt;
&lt;li&gt;Same prompt + different seed = different product.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The single biggest change to my design-to-code workflow this year wasn't a new tool. It was moving the seed data file from the end of a feature ticket to the front. Once the fixture leads, the model starts building the UI the fixture implies — and the fixture, unlike a Figma frame, is something engineers, PMs, and designers can all edit in the same file without merge pain.&lt;/p&gt;

&lt;p&gt;Here's the workflow, why it works, and the specific shapes of seed data that make the model produce good vs. bad output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design brief the model actually reads is your seed data
&lt;/h2&gt;

&lt;p&gt;When you ask an LLM to build a dashboard, it doesn't read your prompt as tightly as you think it does. It reads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The prompt (skimmed)&lt;/li&gt;
&lt;li&gt;The type definitions (carefully)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The seed / mock data&lt;/strong&gt; (as gospel — this is what the UI has to accommodate)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your &lt;code&gt;mockUsers.ts&lt;/code&gt; has three users, all with 4-character first names and no avatars, the model builds a UI that assumes short names and shows initials. If your &lt;code&gt;mockUsers.ts&lt;/code&gt; has 40 users with a mix of "Amélie", "李", "Björn-Alexander", and one 60-character corporate email address, the model builds a UI that truncates, wraps, and reserves space for avatars — because the data forced it to.&lt;/p&gt;

&lt;p&gt;Same prompt. Different seed. Different product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before / after: same prompt, two seed files
&lt;/h2&gt;

&lt;p&gt;Prompt (identical in both runs): "Build a customer list page with a search bar and a table."&lt;/p&gt;

&lt;h3&gt;
  
  
  Seed A (what most people ship as a fixture)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a@x.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b@x.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cara&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c@x.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&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;What the model built: a table with 4 columns, left-aligned, no empty state, no pagination, no plan badge styling — just plain text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seed B (fixture designed to stress-test the UI)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cus_9f3a2b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice Chen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@acme.co&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mrr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastActiveAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-28T14:20:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;signupAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-03-11T09:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enterprise-lead&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;champion&lt;/span&gt;&lt;span class="dl"&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cus_1c8d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// deliberately short — tests min-width&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob.a.verylonglocalpart@somecompany.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// tests truncation&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mrr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastActiveAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2025-11-02T03:11:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// stale, tests "inactive" styling&lt;/span&gt;
    &lt;span class="na"&gt;signupAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2025-10-30T09:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;avatarUrl&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="c1"&gt;// tests fallback initials&lt;/span&gt;
    &lt;span class="na"&gt;tags&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;// ... 38 more, including one with a right-to-left name, one with a null email&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the model built off Seed B: a table with plan badges (because &lt;code&gt;plan&lt;/code&gt; was one of a known set), a relative-time formatter for &lt;code&gt;lastActiveAt&lt;/code&gt; (because dates were varied and some were old), avatar fallbacks with initials (because one was null), email truncation with a tooltip (because one was long), and a tag chip row under each name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same prompt. The seed did all the design work.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A seed-first workflow: fixtures before figma, fixtures before prompt
&lt;/h2&gt;

&lt;p&gt;The order I now run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the fixture first.&lt;/strong&gt; Before Figma, before a prompt, before a ticket description — write the 15-20 rows of seed data that represent the full messy reality of the domain. Long names. Missing fields. Stale timestamps. One extreme outlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the fixture to the PM and the designer.&lt;/strong&gt; This is faster than a mock. Everyone can look at the JSON, argue about whether "tags" is actually a thing, whether MRR should be integer cents or a float, whether we care about right-to-left names. Alignment happens in 20 minutes instead of 2 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then draft the Figma / write the prompt.&lt;/strong&gt; Both now reference the fixture as the source of truth. Designer sizes components to accommodate the longest string in the seed. Prompt says "build the UI implied by &lt;code&gt;fixtures/customers.ts&lt;/code&gt;, using the design tokens in &lt;code&gt;theme.ts&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model generates.&lt;/strong&gt; Because the fixture is comprehensive, the UI is comprehensive on the first pass. Iteration count drops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On our team the average "draft to reviewable UI" time went from ~3 hours to ~50 minutes once we moved to this order. Not because the model got faster — because the inputs got denser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns: the 3 seed shapes that make AI output collapse
&lt;/h2&gt;

&lt;p&gt;Seeds that reliably produce bad AI code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The three happy rows.&lt;/strong&gt; Every field populated, every string short, every date recent. The model builds a UI that only works for demos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The single-row seed.&lt;/strong&gt; Model can't infer variability, so it builds no empty state, no pagination, no sort. You'll fix all of these later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The lorem-ipsum seed.&lt;/strong&gt; Placeholder text with no semantic hints. "lorem ipsum dolor sit amet" doesn't tell the model whether this is a title, a bio, or a comment. Ambiguous input, ambiguous output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Good seeds are boring to write and dramatic in effect. Budget 30 minutes per feature for them. It's the highest-ROI 30 minutes in the whole cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  A repeatable folder structure
&lt;/h2&gt;

&lt;p&gt;What we settled on across three products:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  fixtures/
    customers.ts          # 15-20 rows, messy realism
    customers.empty.ts    # explicit empty state fixture
    customers.loading.ts  # skeleton fixture (shape only)
    customers.error.ts    # error object fixture
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every fixture file has four variants: &lt;strong&gt;populated, empty, loading, error.&lt;/strong&gt; The prompt then reads "build the four states for customers using the fixtures in &lt;code&gt;src/fixtures/customers.*&lt;/code&gt;" and gets all four screens in one generation pass, instead of one screen that assumes data always exists.&lt;/p&gt;

&lt;p&gt;Storybook picks up the fixtures automatically for visual regression. Playwright picks them up for E2E. Designers can open the JSON in a code sandbox and edit values live. One artifact, four consumers, and it lives in the repo where it can be code-reviewed.&lt;/p&gt;

&lt;p&gt;If you're setting up a new project and don't want to reinvent this scaffolding, the fixture-per-state pattern is the kind of workflow default that lives inside a good template starter — see the ones on &lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=fixture-first-prompting" rel="noopener noreferrer"&gt;AppLighter&lt;/a&gt; for how the &lt;code&gt;fixtures/&lt;/code&gt;, &lt;code&gt;stories/&lt;/code&gt;, and &lt;code&gt;e2e/&lt;/code&gt; folders wire together on day one so the model has good bones to build against from your first prompt.&lt;/p&gt;

&lt;p&gt;The general principle is simple enough to fit on a sticky note: &lt;strong&gt;the model doesn't design your product from your prompt. It designs it from your data. So design your data first.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;What does your fixture file look like right now — three happy rows, or the messy version? Drop a comment with the nastiest edge case you deliberately seed in.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>react</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The design work you must finish before Google Play's 14-day closed test</title>
      <dc:creator>Nikolas M I</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:58:14 +0000</pubDate>
      <link>https://dev.to/nikolas_mi_5f4aa63cc4080/the-design-work-you-must-finish-before-google-plays-14-day-closed-test-p73</link>
      <guid>https://dev.to/nikolas_mi_5f4aa63cc4080/the-design-work-you-must-finish-before-google-plays-14-day-closed-test-p73</guid>
      <description>&lt;p&gt;Google Play's 14-day closed test isn't just an ops requirement — it's 14 days when real people will use your app for the first time. If your design isn't ready for that scrutiny, you're getting expensive feedback on the wrong things.&lt;/p&gt;

&lt;p&gt;Here's what design should have shipped before day one of the closed test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 'good enough for testers' isn't
&lt;/h2&gt;

&lt;p&gt;The temptation: 'they're just testers, we'll polish for production launch.' What actually happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testers give feedback on rough UI, not the product. You lose the signal.&lt;/li&gt;
&lt;li&gt;Screenshots make it to social. First impression matters.&lt;/li&gt;
&lt;li&gt;The 14-day counter resets if engagement dips. Rough UX = lower engagement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ship the closed test with launch-quality design or you're paying the 3-week price for lower-quality data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five artefacts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Onboarding flow.&lt;/strong&gt; Every tester's first 90 seconds. Empty states, permissions requests, initial value proof — all designed, not 'I'll clean it up later'.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. All error states.&lt;/strong&gt; Network fail, permission denied, invalid input. Real users hit these on day 1. Placeholder 'something went wrong' text loses trust fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Play Store listing assets.&lt;/strong&gt; Screenshots, feature graphic, description. Play requires them before you can even publish to closed testing. Don't leave this for day 13.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Empty states for every screen.&lt;/strong&gt; 'You have no [thing] yet' + 'here's how to get started'. Most closed-test users are seeing your app the first time with zero data — every empty state matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A visible feedback mechanism.&lt;/strong&gt; In-app 'Send feedback' button or shake-to-report. Testers WILL find bugs; make it easy to report while they're in the moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the in-app feedback loop
&lt;/h2&gt;

&lt;p&gt;Testers who have to remember to email you get around to it maybe half the time. Testers who can tap 'Send feedback' from inside the app respond 5-10x more often.&lt;/p&gt;

&lt;p&gt;Design this before day 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent 'feedback' entry point (settings screen, help menu, floating button in beta builds).&lt;/li&gt;
&lt;li&gt;Feedback form is 3 fields max: (1) What were you trying to do? (2) What happened? (3) Screenshot (auto-attached).&lt;/li&gt;
&lt;li&gt;Sends to a dedicated inbox / Slack channel where you triage daily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do NOT gate this behind 'rate our app' prompts — those trigger active hostility and are usually a Play policy issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Console assets — before day 1
&lt;/h2&gt;

&lt;p&gt;Play requires all of the following BEFORE you can start a closed test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App icon (512x512)&lt;/li&gt;
&lt;li&gt;Feature graphic (1024x500)&lt;/li&gt;
&lt;li&gt;Minimum 2 phone screenshots&lt;/li&gt;
&lt;li&gt;Short description (80 chars)&lt;/li&gt;
&lt;li&gt;Full description (4000 chars)&lt;/li&gt;
&lt;li&gt;Privacy policy URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The screenshots specifically catch teams — most don't realise closed testing requires them too, then scramble on day 0. Design should have all six of the above done and reviewed a week before the closed test starts. If wrangling the release pipeline itself is eating your prep time, tools like &lt;a href="https://www.letsdeploy.it/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=play-closed-test-design-checklist" rel="noopener noreferrer"&gt;LetsDeployIt&lt;/a&gt; can take the deployment side off your plate so design gets those days back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot tip:&lt;/strong&gt; the closed-test screenshots don't need to be the launch-day marketing screenshots. Ship functional screenshots for the closed test; iterate to marketing-quality between day 7 and day 14 (Play allows updating).&lt;/p&gt;

&lt;h2&gt;
  
  
  What to change based on 14 days of data
&lt;/h2&gt;

&lt;p&gt;If you built the feedback loop above, you'll have 20-50 feedback items by day 14. Categorise them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bugs:&lt;/strong&gt; engineering fix, don't wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusion (user didn't know how to do X):&lt;/strong&gt; design fix, ship a copy or flow change in the next build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing feature:&lt;/strong&gt; roadmap item, note but don't ship in the 14-day window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nice-to-have:&lt;/strong&gt; roadmap item, deferred.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aim to ship 3-5 design fixes during the 14-day window. Testers seeing 'oh you fixed my thing' triples their engagement and improves the meaningful-engagement metric Play looks at. Free win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical: the design pre-launch checklist for Play
&lt;/h2&gt;

&lt;p&gt;Before opening the closed-test signup form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Onboarding flow: designed, prototyped, reviewed.&lt;/li&gt;
&lt;li&gt;[ ] All error states: designed with real copy.&lt;/li&gt;
&lt;li&gt;[ ] All empty states: designed with 'get started' guidance.&lt;/li&gt;
&lt;li&gt;[ ] In-app feedback mechanism: implemented + tested.&lt;/li&gt;
&lt;li&gt;[ ] Play Console assets: all six items ready.&lt;/li&gt;
&lt;li&gt;[ ] Screenshots + short description: reviewed for tone.&lt;/li&gt;
&lt;li&gt;[ ] Privacy policy URL: live and accurate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 2-3 days of focused design work. Do it before the 14-day counter starts and the closed test becomes what it should be: a high-signal user research phase. Skip it and it's just a 14-day wait.&lt;/p&gt;




&lt;p&gt;What's your Play closed test looking like — drop a comment with where you are in the 14 days.&lt;/p&gt;

</description>
      <category>android</category>
      <category>googleplay</category>
      <category>ux</category>
      <category>beta</category>
    </item>
    <item>
      <title>Why "Just Use Claude Code" Isn't a Mobile Strategy</title>
      <dc:creator>Nikolas M I</dc:creator>
      <pubDate>Wed, 08 Jul 2026 09:18:10 +0000</pubDate>
      <link>https://dev.to/nikolas_mi_5f4aa63cc4080/why-just-use-claude-code-isnt-a-mobile-strategy-1jpe</link>
      <guid>https://dev.to/nikolas_mi_5f4aa63cc4080/why-just-use-claude-code-isnt-a-mobile-strategy-1jpe</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code is a &lt;strong&gt;tool&lt;/strong&gt;, not a mobile app strategy — the strategy is the substrate it runs on.&lt;/li&gt;
&lt;li&gt;The parts that break aren't code generation: EAS Build credentials, Supabase RLS contracts, Stripe webhook correctness, and AI keys leaking through &lt;code&gt;EXPO_PUBLIC_*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An agent can implement any of these correctly — it just won't know it &lt;em&gt;should&lt;/em&gt; until the pattern already exists in the repo.&lt;/li&gt;
&lt;li&gt;Give Claude Code a well-shaped repo (&lt;code&gt;CLAUDE.md&lt;/code&gt;, visible patterns, typed boundaries) and it's 10x faster. Give it a blank folder and it's a liability with autocomplete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every React Native thread on Reddit right now has some version of the same take: &lt;em&gt;"why would I buy a template when Claude Code exists?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fair question. Wrong framing.&lt;/p&gt;

&lt;p&gt;Claude Code is a &lt;strong&gt;tool&lt;/strong&gt;. A mobile app strategy is a &lt;strong&gt;substrate&lt;/strong&gt;. Confusing the two is why so many AI-generated Expo apps die in the TestFlight queue.&lt;/p&gt;

&lt;p&gt;Let me be specific about the failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code alone breaks on mobile
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EAS Build is stateful
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eas build &lt;span class="nt"&gt;--platform&lt;/span&gt; ios &lt;span class="nt"&gt;--profile&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apple ID + team ID&lt;/li&gt;
&lt;li&gt;Distribution certificate&lt;/li&gt;
&lt;li&gt;Provisioning profile matching the bundle ID&lt;/li&gt;
&lt;li&gt;App Store Connect API key&lt;/li&gt;
&lt;li&gt;A working &lt;code&gt;eas.json&lt;/code&gt; with the right &lt;code&gt;production&lt;/code&gt; profile&lt;/li&gt;
&lt;li&gt;Metro bundler config that plays nice with your assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is code. It's account state and CLI ritual. Claude Code can generate a plausible-looking &lt;code&gt;eas.json&lt;/code&gt;, but it can't create your certificates, can't upload them to Apple, and can't recover when your provisioning profile expires mid-build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supabase RLS is a contract, not a vibe
&lt;/h3&gt;

&lt;p&gt;Ask an agent to "add auth" and you'll often see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"allow all"&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a policy. That's a data breach with a &lt;code&gt;create policy&lt;/code&gt; statement in front of it.&lt;/p&gt;

&lt;p&gt;Real RLS looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"users read own row"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"users update own row"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's just the trivial case. Multi-tenant apps, invitation flows, sharing, admin roles — all require policy design &lt;em&gt;before&lt;/em&gt; the agent starts writing. The agent can implement your contract. It shouldn't invent it. Docs: &lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;Supabase RLS reference&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payments do not tolerate hallucination
&lt;/h3&gt;

&lt;p&gt;A Stripe webhook handler generated by an agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.session.completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;grantLicense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_email&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&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;Everything is wrong. &lt;code&gt;req.json()&lt;/code&gt; breaks signature verification (you need the raw body). There's no idempotency check. Email is a bad key for entitlement. There's no error handling. And where's the signing secret validated?&lt;/p&gt;

&lt;p&gt;Correct version — abbreviated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&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;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bad signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.session.completed&lt;/span&gt;&lt;span class="dl"&gt;'&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;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;upsertLicense&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&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;The point is not that Claude Code &lt;em&gt;couldn't&lt;/em&gt; write the correct version. It's that it doesn't know it &lt;em&gt;should&lt;/em&gt; until you tell it. That's the strategy — deciding what "correct" looks like before the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI provider keys leak by default
&lt;/h3&gt;

&lt;p&gt;If your Expo app imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&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;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPO_PUBLIC_OPENAI_KEY&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;You just shipped your OpenAI key to every user of your app. &lt;code&gt;EXPO_PUBLIC_*&lt;/code&gt; env vars are bundled into the JS. Anyone can &lt;code&gt;curl&lt;/code&gt; your bundle and read it.&lt;/p&gt;

&lt;p&gt;The strategy answer: every LLM call goes through a Supabase edge function that holds the key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/summarize`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Edge function&lt;/span&gt;
&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OPENAI_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toReadableStream&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;Claude Code can write both halves. It won't know to write the second half unless the pattern is already in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code is genuinely great
&lt;/h2&gt;

&lt;p&gt;Given a well-shaped repo, Claude Code is transformative. Some concrete wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screen scaffolding.&lt;/strong&gt; Point it at &lt;code&gt;app/(tabs)/index.tsx&lt;/code&gt; and ask for a variant. It preserves imports, styling, and data-fetching patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration writing.&lt;/strong&gt; Give it two existing Supabase migrations. Ask for a third. It matches your style, including RLS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge function templating.&lt;/strong&gt; Given one AI provider, it can generate an equivalent for another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codebase Q&amp;amp;A.&lt;/strong&gt; "Where does the license grant happen?" — with &lt;code&gt;file:line&lt;/code&gt; accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The requirement: a repo where those patterns are visible. That's what a template gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Just Claude Code&lt;/th&gt;
&lt;th&gt;Claude Code + Substrate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first TestFlight&lt;/td&gt;
&lt;td&gt;Days–weeks&lt;/td&gt;
&lt;td&gt;Same day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RLS + auth&lt;/td&gt;
&lt;td&gt;Invented per session&lt;/td&gt;
&lt;td&gt;Pre-designed, tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EAS Build config&lt;/td&gt;
&lt;td&gt;You figure it out&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eas.json&lt;/code&gt; profiles included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Hallucinated handlers&lt;/td&gt;
&lt;td&gt;Verified webhook + entitlements RLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;td&gt;Mean-of-training-data&lt;/td&gt;
&lt;td&gt;Brand-tokenized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent conventions&lt;/td&gt;
&lt;td&gt;Reinvented every prompt&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt;, slash commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term maintenance&lt;/td&gt;
&lt;td&gt;Code drift&lt;/td&gt;
&lt;td&gt;Documented architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What "having a strategy" actually means
&lt;/h2&gt;

&lt;p&gt;Decisions made &lt;em&gt;before&lt;/em&gt; you prompt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Distribution — Expo + EAS, which profiles, which channels&lt;/li&gt;
&lt;li&gt;Backend — Supabase tables, RLS design, edge functions&lt;/li&gt;
&lt;li&gt;AI provider — model, streaming, cost cap, key location&lt;/li&gt;
&lt;li&gt;Payments — Stripe web / IAP / hybrid&lt;/li&gt;
&lt;li&gt;Design system — typography, tokens, dark mode, motion&lt;/li&gt;
&lt;li&gt;Agent conventions — &lt;code&gt;CLAUDE.md&lt;/code&gt;, slash commands&lt;/li&gt;
&lt;li&gt;Store readiness — privacy manifest, subscription copy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can build all of this yourself. It's 3–6 weeks of mid-level mobile engineering work per app. That's the honest baseline.&lt;/p&gt;

&lt;p&gt;Or you start from a substrate that has it decided already — like the &lt;a href="https://www.applighter.com" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt; React Native + Expo templates, which ship with all of the above and a &lt;code&gt;CLAUDE.md&lt;/code&gt; that turns Claude Code into a targeted execution engine rather than a random screen generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line version
&lt;/h2&gt;

&lt;p&gt;Claude Code is the leverage. A mobile app strategy is the fulcrum. You need both.&lt;/p&gt;

&lt;p&gt;If you're building an AI feature, start with a substrate that ships the AI patterns already — &lt;a href="https://www.applighter.com/apps/ai-voice-notes" rel="noopener noreferrer"&gt;AI Voice Notes&lt;/a&gt; and &lt;a href="https://www.applighter.com/apps/chat-with-pdf" rel="noopener noreferrer"&gt;Chat with PDF&lt;/a&gt; are the two we recommend as starting points.&lt;/p&gt;

&lt;p&gt;What are you building right now that hit one of these walls? Drop it in the comments — especially if it was the RLS one.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
