<?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: Avery</title>
    <description>The latest articles on DEV Community by Avery (@avery_code).</description>
    <link>https://dev.to/avery_code</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%2F3837121%2F51bc1289-fc3a-49a8-ace7-d5052dd80cd9.png</url>
      <title>DEV Community: Avery</title>
      <link>https://dev.to/avery_code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avery_code"/>
    <language>en</language>
    <item>
      <title>What Does a React Component Actually Look Like Before and After Rules?</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Fri, 04 Sep 2026 13:48:26 +0000</pubDate>
      <link>https://dev.to/avery_code/what-does-a-react-component-actually-look-like-before-and-after-rules-44b6</link>
      <guid>https://dev.to/avery_code/what-does-a-react-component-actually-look-like-before-and-after-rules-44b6</guid>
      <description>&lt;p&gt;Most of what gets written about AI coding standards stays at the level of principle. Define your architecture. Be explicit about naming. Decide where state lives. All reasonable advice, and all of it fairly abstract until you sit down and actually look at two versions of the same file.&lt;/p&gt;

&lt;p&gt;So here is the concrete version. One component, generated by the AI without any project rules in place, then the same component built again with three specific rules provided beforehand. Same feature, same requirements, same model. The only variable is whether the rules existed before generation started.&lt;/p&gt;

&lt;p&gt;The feature is a user list with search and a status filter. Nothing exotic. The kind of thing that shows up in almost every internal tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The component without rules
&lt;/h2&gt;

&lt;p&gt;This is roughly what came back the first time, with a prompt that described the feature and nothing else.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function UserList() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(false)
  const [search, setSearch] = useState('')
  const [status, setStatus] = useState('all')
  const [error, setError] = useState(null)

  useEffect(() =&amp;gt; {
    setLoading(true)
    fetch('/api/users')
      .then(res =&amp;gt; res.json())
      .then(data =&amp;gt; {
        setUsers(data.users.map(u =&amp;gt; ({
          id: u.user_id,
          name: u.full_name,
          email: u.email_address,
          status: u.account_status
        })))
        setLoading(false)
      })
      .catch(err =&amp;gt; {
        setError(err.message)
        setLoading(false)
      })
  }, [])

  const filtered = users.filter(u =&amp;gt; {
    const matchesSearch = u.name.toLowerCase().includes(search.toLowerCase())
    const matchesStatus = status === 'all' || u.status === status
    return matchesSearch &amp;amp;&amp;amp; matchesStatus
  })

  if (loading) return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
  if (error) return &amp;lt;div&amp;gt;Error: {error}&amp;lt;/div&amp;gt;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input value={search} onChange={e =&amp;gt; setSearch(e.target.value)} /&amp;gt;
      &amp;lt;select value={status} onChange={e =&amp;gt; setStatus(e.target.value)}&amp;gt;
        &amp;lt;option value="all"&amp;gt;All&amp;lt;/option&amp;gt;
        &amp;lt;option value="active"&amp;gt;Active&amp;lt;/option&amp;gt;
        &amp;lt;option value="inactive"&amp;gt;Inactive&amp;lt;/option&amp;gt;
      &amp;lt;/select&amp;gt;
      {filtered.map((u, i) =&amp;gt; (
        &amp;lt;div key={i}&amp;gt;
          &amp;lt;span&amp;gt;{u.name}&amp;lt;/span&amp;gt;
          &amp;lt;span&amp;gt;{u.email}&amp;lt;/span&amp;gt;
          &amp;lt;span&amp;gt;{u.status}&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This code works. If you drop it into a project it renders, it filters, it handles the loading and error cases. Nothing here would throw an exception in normal use.&lt;/p&gt;

&lt;p&gt;It also does four different jobs in one file. It fetches data, it transforms the API response into a different shape, it manages filter state and computes the filtered result, and it renders the UI. The API field names get mapped inline inside the fetch callback, which means the component knows the exact shape the backend returns. The list uses the array index as a key, which will cause React to misidentify rows the moment filtering changes the order. The loading state is bare text.&lt;/p&gt;

&lt;p&gt;None of these are things the AI does not know about. Ask it directly whether index as key is a good idea and it will explain why it is not. The knowledge is there. What is missing is anything forcing that knowledge to apply during generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three rules
&lt;/h2&gt;

&lt;p&gt;These are the rules that went in before the second attempt. Deliberately short, because a first rule set that takes an afternoon to write never gets written.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Components render only. Data fetching lives in a service function.
   State logic lives in a custom hook. A component that fetches,
   transforms, and renders is three things pretending to be one.

2. API responses get mapped to domain objects in the service layer,
   before the data reaches any hook or component. Nothing above the
   service layer knows what the backend field names are.

3. Every list item key is a stable identifier from the data itself.
   If the data has no stable id, generate one during the service
   layer mapping, never inline in the render.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Three rules. No architecture document, no folder structure mandate, nothing about naming conventions yet. Just the three decisions that address what actually went wrong in the first version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same component with the rules in place
&lt;/h2&gt;

&lt;p&gt;The AI split the work into three files without being told to split it into three files. That happened because rule one made the boundaries explicit.&lt;/p&gt;

&lt;p&gt;First, the service:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// services/userService.js

export async function fetchUsers() {
  const res = await fetch('/api/users')
  if (!res.ok) {
    throw new Error(`Failed to load users: ${res.status}`)
  }
  const data = await res.json()
  return data.users.map(toUser)
}

function toUser(raw) {
  return {
    id: raw.user_id,
    name: raw.full_name,
    email: raw.email_address,
    status: raw.account_status
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The mapping moved here, which is rule two doing its job. The backend field names now appear in exactly one place in the codebase. If the API renames &lt;code&gt;full_name&lt;/code&gt; to &lt;code&gt;display_name&lt;/code&gt; next quarter, one function changes and nothing else in the application notices.&lt;/p&gt;

&lt;p&gt;Then the hook:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// hooks/useUserList.js

export function useUserList() {
  const [users, setUsers] = useState([])
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(null)
  const [searchTerm, setSearchTerm] = useState('')
  const [statusFilter, setStatusFilter] = useState('all')

  useEffect(() =&amp;gt; {
    let cancelled = false

    fetchUsers()
      .then(result =&amp;gt; {
        if (!cancelled) {
          setUsers(result)
          setIsLoading(false)
        }
      })
      .catch(err =&amp;gt; {
        if (!cancelled) {
          setError(err.message)
          setIsLoading(false)
        }
      })

    return () =&amp;gt; { cancelled = true }
  }, [])

  const visibleUsers = useMemo(() =&amp;gt; {
    const term = searchTerm.toLowerCase()
    return users.filter(user =&amp;gt; {
      const matchesSearch = user.name.toLowerCase().includes(term)
      const matchesStatus =
        statusFilter === 'all' || user.status === statusFilter
      return matchesSearch &amp;amp;&amp;amp; matchesStatus
    })
  }, [users, searchTerm, statusFilter])

  return {
    visibleUsers,
    isLoading,
    error,
    searchTerm,
    setSearchTerm,
    statusFilter,
    setStatusFilter
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Something worth noticing here that no rule asked for. The cleanup flag preventing a state update after unmount appeared in this version and not in the first one. So did the &lt;code&gt;useMemo&lt;/code&gt; around the filter computation, and so did the &lt;code&gt;isLoading&lt;/code&gt; naming with the boolean prefix.&lt;/p&gt;

&lt;p&gt;I did not write rules for any of those three things. My read is that once the hook exists as its own file with a single clear job, the generation is no longer juggling four concerns at once, and the details that get dropped under that load stop getting dropped. Whether that generalizes or whether I got lucky in this particular case, I honestly do not know. It happened consistently enough across the components I rebuilt that I stopped treating it as a coincidence.&lt;/p&gt;

&lt;p&gt;And the component:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/UserList.jsx

export function UserList() {
  const {
    visibleUsers,
    isLoading,
    error,
    searchTerm,
    setSearchTerm,
    statusFilter,
    setStatusFilter
  } = useUserList()

  if (isLoading) return &amp;lt;UserListSkeleton /&amp;gt;
  if (error) return &amp;lt;ErrorMessage message={error} /&amp;gt;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        value={searchTerm}
        onChange={e =&amp;gt; setSearchTerm(e.target.value)}
        aria-label="Search users"
      /&amp;gt;
      &amp;lt;select
        value={statusFilter}
        onChange={e =&amp;gt; setStatusFilter(e.target.value)}
        aria-label="Filter by status"
      &amp;gt;
        &amp;lt;option value="all"&amp;gt;All&amp;lt;/option&amp;gt;
        &amp;lt;option value="active"&amp;gt;Active&amp;lt;/option&amp;gt;
        &amp;lt;option value="inactive"&amp;gt;Inactive&amp;lt;/option&amp;gt;
      &amp;lt;/select&amp;gt;

      &amp;lt;ul&amp;gt;
        {visibleUsers.map(user =&amp;gt; (
          &amp;lt;li key={user.id}&amp;gt;
            &amp;lt;span&amp;gt;{user.name}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt;{user.email}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt;{user.status}&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The component is now twenty lines of rendering. &lt;code&gt;user.id&lt;/code&gt; as the key came directly from rule three. The &lt;code&gt;ul&lt;/code&gt; and &lt;code&gt;li&lt;/code&gt; elements, the aria labels on the inputs, the skeleton instead of the text placeholder, none of that was in the rules either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the comparison actually tells you
&lt;/h2&gt;

&lt;p&gt;The first version was not badly written. It was written without a defined boundary, and everything else followed from that.&lt;/p&gt;

&lt;p&gt;When a single function is responsible for fetching, mapping, filtering, and rendering, the generation is making all of those decisions in one pass. The API mapping goes inline because that is where the fetch happens to be. The key goes to the index because the render is the fourth thing in a chain of concerns and the identifier question does not get much attention by the time it comes up. The loading state is text because it is a detail inside a component that already has too many jobs.&lt;/p&gt;

&lt;p&gt;Three rules changed what the code looks like far more than three rules should be able to. That is what makes this worth actually running yourself rather than taking my word for it, because the multiplier effect is the part that does not come across in the abstract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on something you already have
&lt;/h2&gt;

&lt;p&gt;Take a component in your project that the AI generated in a single session. Copy the three rules above, paste them in front of a prompt describing the same feature, and generate it again from scratch.&lt;/p&gt;

&lt;p&gt;The second version will not be perfect. Mine was not either. But the comparison tells you something specific and immediately actionable, which is which of your recurring corrections are actually the AI missing knowledge, and which ones are just the absence of a boundary that would have made the correct choice obvious. In my experience it is almost entirely the second category, and that category is the one you can fix in an afternoon.&lt;/p&gt;




&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>I Filled Out Three Forms in My Own App and Got Three Completely Different Validation Experiences.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:11:58 +0000</pubDate>
      <link>https://dev.to/avery_code/i-filled-out-three-forms-in-my-own-app-and-got-three-completely-different-validation-experiences-l06</link>
      <guid>https://dev.to/avery_code/i-filled-out-three-forms-in-my-own-app-and-got-three-completely-different-validation-experiences-l06</guid>
      <description>&lt;p&gt;I was doing a manual walkthrough of a few user flows before a release, clicking through screens as an actual user would rather than testing individual components in isolation. Somewhere between the second and third form I filled out, something started feeling off in a way I could not immediately name.&lt;/p&gt;

&lt;p&gt;The signup form validated aggressively as I typed. The moment I entered a single character in the email field, it turned red and told me the email was invalid, which was technically true but felt hostile given that I had typed exactly one letter of what would eventually be a complete address. The settings form did the opposite. It stayed completely silent through everything I typed, then dumped four separate error messages on me at once when I hit save, forcing me to scroll back up and figure out which fields it was actually complaining about. The contact form did something in between, waiting until I moved out of each field before evaluating it, which felt notably more reasonable than either of the others.&lt;/p&gt;

&lt;p&gt;Three forms in one application. Three fundamentally different validation experiences. From a user's perspective, this made the product feel inconsistent in a way that was difficult to articulate but easy to notice, a sense that different parts of the app had been built by different people with different ideas about how software should behave.&lt;/p&gt;

&lt;p&gt;Every one of these three approaches is a documented, legitimate validation strategy. None of them represents a technical mistake. But encountering all three across a single user session is not a series of independent technical decisions. It is the absence of a decision entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why validation timing fragments more visibly than most inconsistencies
&lt;/h2&gt;

&lt;p&gt;Most of the AI generated inconsistencies worth examining live entirely inside the codebase. Prop drilling handled three different ways, dependency arrays that are sometimes over-specified and sometimes under-specified, naming conventions that drift across sessions. These are real problems with real costs, but the costs are borne almost entirely by developers reading and maintaining the code. Users never encounter them directly.&lt;/p&gt;

&lt;p&gt;Validation timing is different because it crosses directly into the user experience. The choice between validating on change, on blur, or on submit is not primarily a code structure decision. It is a decision about how the product communicates with the person using it, when it interrupts them, and how much patience it extends before flagging something as wrong.&lt;/p&gt;

&lt;p&gt;This means validation inconsistency has two costs stacked on top of each other. There is the usual maintenance cost of having three different implementations of conceptually the same behavior. And there is a product cost that shows up in how the application feels to use, which is harder to measure but often more consequential for whether people actually enjoy using the thing you built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three patterns and what each one is actually good for
&lt;/h2&gt;

&lt;p&gt;Validating on change, meaning evaluating the field on every keystroke, provides the fastest feedback loop. This works genuinely well for certain specific cases, particularly password strength indicators where the user benefits from watching the requirement get satisfied in real time, or character counters where live feedback is the entire point of the feature.&lt;/p&gt;

&lt;p&gt;It works badly for almost everything else, particularly for fields with a format that is only valid once fully entered. Email addresses are the canonical example. Every partially typed email is technically invalid, which means validating on change produces an error state that persists for most of the time the user is typing, only resolving at the very end. The user has done nothing wrong, but the interface has been telling them they have for the entire duration of their input.&lt;/p&gt;

&lt;p&gt;Validating on blur, meaning evaluating when the user leaves the field, is the pattern that tends to feel most natural for standard text inputs. The user gets to finish their thought before being evaluated, but they still receive feedback before submitting the entire form, which means they can correct issues incrementally rather than encountering all of them at once at the end.&lt;/p&gt;

&lt;p&gt;Validating on submit, meaning waiting until the user attempts to submit the whole form, has legitimate uses primarily for validations that cannot be performed on a single field in isolation. Cross-field validations, where the validity of one field depends on the value of another, or validations requiring a server round trip that would be wasteful to trigger on every blur, are reasonable candidates for submit-time evaluation.&lt;/p&gt;

&lt;p&gt;The problem is not that any of these three is wrong. It is that they solve different problems, and using them interchangeably based on which session generated which form means the choice has nothing to do with which problem the specific form actually presents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the AI defaults differently across sessions
&lt;/h2&gt;

&lt;p&gt;The pattern selected in any given session appears to correlate with fairly incidental characteristics of how the form was described in the prompt and what the surrounding code happened to look like.&lt;/p&gt;

&lt;p&gt;A form described with detailed field requirements, particularly requirements involving format constraints, tends to produce validate-on-change behavior, since the format constraints are prominent in the prompt and immediate validation seems responsive to them. A form described more simply, focused on structure rather than validation rules, tends to produce validate-on-submit behavior, since submit-time validation is the minimum implementation that technically satisfies the requirement to validate at all. A form generated in a session where an existing form using blur validation was visible in context is more likely to also use blur validation, though this only holds when the existing example happens to be in the visible context window.&lt;/p&gt;

&lt;p&gt;None of these tendencies are unreasonable as local heuristics. The AI is responding sensibly to the information available in each individual session. But the information available in each individual session varies for reasons entirely unrelated to what the correct validation strategy for that particular form actually is, which is why the outcome varies too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user experience cost that does not appear in code review
&lt;/h2&gt;

&lt;p&gt;A code reviewer looking at a form component with validate-on-change behavior sees a functioning implementation of a legitimate pattern. There is nothing to flag. The validation works, the errors display correctly, the form submits when valid. Every technical criterion is satisfied.&lt;/p&gt;

&lt;p&gt;What the code reviewer does not see, because it is not visible in a single component in isolation, is that the form two screens over validates completely differently, and that a user moving between those two screens experiences the product as inconsistent in a way that erodes trust in the overall quality of the application.&lt;/p&gt;

&lt;p&gt;This is the specific reason validation timing benefits from being an explicit rule rather than a per-component judgment call. The decision is not visible at the level where code review operates. It is only visible at the level of the complete user experience, which no individual component review ever examines. Rules can operate at that higher level in a way that component-by-component review structurally cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a validation timing rule needs to specify
&lt;/h2&gt;

&lt;p&gt;The rule has to make the timing decision based on the type of field and validation involved, rather than leaving it as an open judgment call for each new form.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Form validation timing rule:
1. Standard text inputs with format requirements, including email, phone, and URL fields, validate on blur. Do not validate these on change, since partial input is necessarily invalid and flagging it produces a hostile experience.
2. Fields where live feedback is the actual feature, specifically password strength indicators and character counters, validate on change. This is the narrow exception, not the default.
3. Cross-field validations, where one field's validity depends on another field's value, evaluate on submit, since evaluating them earlier produces confusing intermediate states that resolve themselves as the user continues.
4. Validations requiring a server round trip evaluate on blur at the earliest, never on change, to avoid generating a request per keystroke.
5. On submit, all fields are re-validated regardless of their individual timing strategy, and focus moves to the first field with an error so the user does not have to search for what went wrong.
6. Error message placement and styling is identical across every form in the application, regardless of which timing strategy applies to that particular field.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The core of this rule is that timing is determined by the nature of the field, not by the session that happened to generate it. An email field validates on blur whether it appears in a signup form, a settings form, or a contact form, because the reasoning about why blur is correct for email fields does not change based on which form the field lives in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why rule five matters more than it initially appears
&lt;/h2&gt;

&lt;p&gt;The fifth rule, about re-validating everything on submit and moving focus to the first error, addresses a failure mode that the other rules do not cover on their own.&lt;/p&gt;

&lt;p&gt;Even with correct per-field timing, a user can reach the submit button with errors present, particularly if they never focused certain fields at all, meaning blur validation never triggered for those fields. Without explicit submit-time re-validation, these fields can pass through unvalidated, or produce errors that the user cannot easily locate on a long form.&lt;/p&gt;

&lt;p&gt;Moving focus to the first error is a small detail that has an outsized effect on how forgiving the form feels, particularly on longer forms where the error might be several screens above where the submit button sits. This is exactly the kind of detail that gets skipped when validation is implemented fresh in each session, since it is not necessary for the form to technically function and requires deliberate additional implementation effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after the rule went into effect
&lt;/h2&gt;

&lt;p&gt;New forms consistently used blur validation for standard fields, which was the pattern that had previously appeared least often despite being the most appropriate for the majority of cases. Validate-on-change stopped appearing for email and similar format-constrained fields entirely, which eliminated the specific experience of being told an email was invalid after typing one character.&lt;/p&gt;

&lt;p&gt;The submit-time re-validation and focus management specified in rule five turned out to require the most implementation change, since it had been implemented inconsistently or not at all in most existing forms. This was the part that produced the most noticeable improvement in how forms actually felt to use, which was somewhat unexpected given that the original problem I noticed was about timing rather than error recovery.&lt;/p&gt;

&lt;p&gt;Existing forms with inconsistent timing did not update themselves, and the older forms remain inconsistent until they get touched for other reasons. But new forms consistently follow the pattern, which means the inconsistency is bounded rather than growing with each new form the project adds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Validation timing is a decision about how your product communicates with the people using it, not just a technical choice about when a function runs. Leaving it as a per-session judgment call means the user experience of your forms varies based on how each form's prompt happened to be phrased, which is not a defensible basis for a product decision that users directly experience.&lt;/p&gt;

&lt;p&gt;Look for the other decisions in your project that live at the boundary between code and user experience, where the inconsistency is invisible in individual component review but clearly visible to someone using the actual application. Those are the decisions that most benefit from being an explicit rule, precisely because no existing review process is structured to catch them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project has user-facing decisions that were never standardized?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where inconsistency is invisible in code review but obvious to anyone actually using the application.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancoding</category>
    </item>
    <item>
      <title>I Counted Three Different Loading Patterns Across Five Components. All Generated by the Same AI.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Fri, 28 Aug 2026 07:59:40 +0000</pubDate>
      <link>https://dev.to/avery_code/i-counted-three-different-loading-patterns-across-five-components-all-generated-by-the-same-ai-3060</link>
      <guid>https://dev.to/avery_code/i-counted-three-different-loading-patterns-across-five-components-all-generated-by-the-same-ai-3060</guid>
      <description>&lt;p&gt;I was cleaning up a settings page and needed to reference how loading states were handled elsewhere in the project, so I could match the existing pattern. That search turned into something more revealing than the task I had actually started with.&lt;/p&gt;

&lt;p&gt;The dashboard component showed a spinner, a simple rotating icon centered in the content area while data loaded. The user profile component showed a skeleton, gray rectangular placeholders roughly matching the shape of the content that would eventually appear. The notifications panel showed plain text, just the word "Loading" sitting where the list would normally render. Three different components, three completely different approaches to communicating the exact same thing to the user, all within the same project, all generated across various sessions with the same AI tool.&lt;/p&gt;

&lt;p&gt;None of these three approaches is wrong. A spinner is a perfectly reasonable way to indicate loading. So is a skeleton. So is plain text, in the right context. Every one of them is a defensible engineering decision if you evaluate it in isolation. But finding all three used interchangeably across a handful of components in one project is not really about any individual decision being wrong. It is about there being no consistent decision at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why loading states specifically tend to fragment this way
&lt;/h2&gt;

&lt;p&gt;Loading states share the same underlying characteristic that made prop drilling and dependency arrays interesting cases to examine. There is no single objectively correct pattern. Spinners, skeletons, and plain text messages are all legitimate solutions, and which one is actually best depends on factors specific to the situation rather than a universal React rule.&lt;/p&gt;

&lt;p&gt;A skeleton tends to work well when the eventual content has a predictable, consistent shape and layout, since the placeholder can closely mirror what will replace it, reducing the visual jump when the real content arrives. A spinner tends to work well for shorter, less predictable loading periods, or for content whose final shape varies too much for a meaningful skeleton to represent. Plain text tends to get used, often somewhat lazily, in situations where the developer or the AI has not put much thought into the loading experience at all, treating it as a placeholder to fill a technical requirement rather than a genuine design decision.&lt;/p&gt;

&lt;p&gt;Because all three have legitimate use cases and none of them is flagged as incorrect the way a missing dependency or an index-as-key violation would be, there is nothing in the AI's general knowledge pushing it toward consistency across different sessions. Each individual loading state gets decided fresh, based on whatever seems reasonable for that specific component in that specific moment, with no memory of how the last three loading states in this same project were handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What determines which pattern gets chosen in any given session
&lt;/h2&gt;

&lt;p&gt;The choice does not appear to be random exactly, but it is heavily influenced by session-specific factors that have nothing to do with what the rest of the project actually does.&lt;/p&gt;

&lt;p&gt;If the component being generated has a clear, well defined data shape visible in the prompt or the surrounding context, a skeleton becomes more likely, since the AI can reasonably infer what the loading placeholder should approximate. If the data shape is vague or the component handles several different kinds of content depending on state, a spinner becomes more likely as the safer, shape-agnostic default. If the loading state feels like an afterthought relative to the main focus of the generation request, plain text becomes more likely, since it requires the least additional code and the least additional thought to include.&lt;/p&gt;

&lt;p&gt;None of these tendencies are wrong reasoning in isolation. They are locally sensible heuristics. The problem is that these session-specific factors, the data shape visible in that particular prompt, how central the loading state was to that particular request, vary constantly across a real project, which means the resulting pattern varies constantly too, even though the actual user-facing requirement, communicate that something is loading, never changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this particular inconsistency matters more than it initially seems
&lt;/h2&gt;

&lt;p&gt;It would be easy to dismiss loading state inconsistency as a minor cosmetic issue, since the core functionality still works regardless of which pattern gets used. But this specific inconsistency has a few consequences worth taking seriously.&lt;/p&gt;

&lt;p&gt;First, it directly affects product polish in a way users notice, even if they cannot articulate why. An application where every section loads with a visually distinct pattern reads as unfinished or cobbled together, even if every individual screen functions correctly. Users build an implicit expectation from their first few interactions with a product, and violating that expectation repeatedly creates a low-grade sense that the product was not built with much care, regardless of how solid the underlying functionality actually is.&lt;/p&gt;

&lt;p&gt;Second, it creates real engineering overhead beyond the visual inconsistency. Three different loading patterns typically mean three different sets of supporting code, three different approaches to accessibility for screen readers announcing loading state, and three different places where a design change to loading behavior has to be implemented separately rather than once. What could be a single shared component handling loading consistently across the application instead becomes three or more bespoke implementations that all have to be maintained independently.&lt;/p&gt;

&lt;p&gt;Third, and this is the part that connects back to the broader pattern across all of these inconsistency examples, it signals that no design system decision was ever actually made for this category of UI. Loading states are exactly the kind of small, easy to overlook decision that benefits enormously from being decided once, explicitly, rather than being left as an ambient judgment call that gets re-litigated every time a new component happens to need one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a rule for this actually needs to specify
&lt;/h2&gt;

&lt;p&gt;Unlike the dependency array problem, where the rule needed to force a reasoning process rather than restate a fact, the loading state problem needs a rule that makes an explicit design decision and then applies it consistently, since there genuinely is no single correct answer waiting to be discovered through more careful analysis.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Loading state consistency rule:
1. Content with a predictable, consistent layout uses a skeleton component that approximates the shape of the eventual content. This applies to lists, cards, and any content type that appears repeatedly with a stable visual structure.
2. Content without a predictable layout, or content that varies significantly in shape depending on the data, uses a centered spinner rather than attempting to construct a skeleton for an unpredictable shape.
3. Full page or full section loading, where the entire visible area is waiting on data, always uses the skeleton pattern if the eventual layout is known in advance, falling back to a spinner only when the layout genuinely cannot be anticipated.
4. Plain text loading indicators are not an acceptable pattern anywhere in this project. If a component's loading state does not clearly fit the skeleton or spinner criteria above, default to a spinner rather than falling back to unstyled text.
5. All loading states include appropriate accessibility attributes, specifically an aria-live region or an aria-busy attribute, applied the same way regardless of whether the visual pattern is a skeleton or a spinner.
6. Loading state components live in a shared location and get reused rather than reimplemented per feature, so a future change to how loading is communicated only requires updating one place.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This rule does not claim that skeletons are always better than spinners or that spinners are always better than skeletons. It makes a specific decision about which pattern applies under which conditions and removes plain text as an option entirely, since plain text was consistently the pattern that appeared when the least thought had gone into the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why removing an option entirely is sometimes the right move
&lt;/h2&gt;

&lt;p&gt;Most of the rules examined in this series so far have been about specifying which of several legitimate options applies under which conditions, preserving all the options but resolving the ambiguity about when each one is used. Loading states are a case where outright removing one of the options produces a better outcome than trying to define narrow conditions under which it would be acceptable.&lt;/p&gt;

&lt;p&gt;Plain text loading states are rarely a deliberate design choice. They are almost always what happens when a loading state gets added as an afterthought, with minimal effort, because the actual focus of that particular generation session was somewhere else. Trying to write a rule specifying exactly when plain text is the correct choice would be attempting to formalize a pattern that mostly exists because insufficient thought was applied, rather than because it represents a genuinely superior approach in specific circumstances.&lt;/p&gt;

&lt;p&gt;Sometimes the correct rule for an inconsistency is not a nuanced decision tree covering every case. Sometimes it is simply eliminating the option that only ever shows up as a symptom of insufficient attention, and defaulting to one of the two options that were actually deliberately chosen for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after the rule went into effect
&lt;/h2&gt;

&lt;p&gt;After this rule was applied, new components consistently used either the skeleton or spinner pattern depending on whether the content shape was predictable, following the specific criteria in the rule rather than whatever seemed locally reasonable for that particular generation session. Plain text loading indicators, which had previously appeared in roughly a third of the components that included any loading state at all, stopped appearing entirely in new code.&lt;/p&gt;

&lt;p&gt;The shared loading component location specified in rule six also produced an unexpected secondary benefit. Once loading components lived in one place and got imported rather than reimplemented, a design change made to the skeleton component's styling automatically propagated to every place that used it, rather than requiring a search across the codebase to find and update every bespoke implementation individually.&lt;/p&gt;

&lt;p&gt;The existing inconsistent loading states from before the rule did not fix themselves, the same way the existing prop drilling instances from an earlier example did not retroactively resolve. But the pattern stopped growing, which meant the eventual cleanup effort, whenever it happens, deals with a fixed, bounded amount of inconsistency rather than a continuously expanding one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Loading states do not have a single correct implementation the way a dependency array does, which means the fix here looks different from forcing a reasoning process to happen. It looks like making an explicit design decision once, specifying exactly which pattern applies under which conditions, and removing the low-effort default that was never actually a deliberate choice in the first place.&lt;/p&gt;

&lt;p&gt;Look through your own project for the small, easy to overlook UI decisions that feel too minor to formalize, the kind of thing that seems fine to leave as an ambient judgment call. Loading states are one example. There are usually several more sitting quietly in any codebase that has had AI generating components across enough sessions for these small inconsistencies to accumulate without anyone noticing until they specifically go looking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project has small UI decisions that were never actually standardized?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where multiple valid patterns exist side by side because no explicit decision was ever made.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Setting the Shape Is the New Skill. Your AI Can Only Work Inside a Shape You Actually Defined.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Tue, 25 Aug 2026 08:30:12 +0000</pubDate>
      <link>https://dev.to/avery_code/setting-the-shape-is-the-new-skill-your-ai-can-only-work-inside-a-shape-you-actually-defined-1n29</link>
      <guid>https://dev.to/avery_code/setting-the-shape-is-the-new-skill-your-ai-can-only-work-inside-a-shape-you-actually-defined-1n29</guid>
      <description>&lt;p&gt;There is a shift happening in what makes a developer valuable, and most conversations about it focus on the wrong half of the equation.&lt;/p&gt;

&lt;p&gt;The argument goes something like this. An AI agent can turn a system design you already hold in your head into working software faster than you could ever type it yourself. Which means the ceiling on what you ship is no longer how fast you write code. It is how well you decide what to build and how to shape it. Where state should live. Where component boundaries fall. What consequences show up months later when the thing you built needs to scale.&lt;/p&gt;

&lt;p&gt;This is true, and it is a genuinely useful reframing of where developer value is heading. The developers pulling ahead are the ones who lead that design instead of handing it off entirely, setting the shape and then letting the agent move fast inside it.&lt;/p&gt;

&lt;p&gt;What gets left unsaid in this framing is the actual mechanism connecting the two halves. Having the judgment is one thing. The agent actually working inside the shape you have in mind is a completely different thing, and the gap between them does not close automatically just because your judgment is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Judgment that stays in your head does nothing for the agent
&lt;/h2&gt;

&lt;p&gt;You can have excellent instincts about how a React project should be structured. Years of pattern recognition about when state should be local versus shared, when a component is doing too much, what naming makes a codebase legible six months later, how a feature boundary should be drawn so it does not become a maintenance problem down the line.&lt;/p&gt;

&lt;p&gt;None of that judgment transfers to an agent by existing in your head. The agent does not have access to your accumulated pattern recognition. It has access to whatever you actually communicate, in whatever form you communicate it, before or during the session where it generates something.&lt;/p&gt;

&lt;p&gt;If your judgment about component boundaries only exists as an intuition you apply when you personally write code, an agent working inside your codebase has no way to inherit that intuition. It will make its own decision about where the boundary falls, based on general patterns from its training and whatever it can infer from the immediate context, which is not the same as your specific, hard-won judgment about this specific project.&lt;/p&gt;

&lt;p&gt;The shape you are supposed to be setting, according to the argument, only actually gets set if it exists somewhere the agent can read it. Otherwise you have good judgment and an agent that is working inside a shape you never actually communicated, just one it guessed at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "setting the shape" concretely requires
&lt;/h2&gt;

&lt;p&gt;Setting the shape is not a single act. It sounds like one thing when described abstractly, but it decomposes into a specific set of decisions that have to be made explicit, not just held as intuition.&lt;/p&gt;

&lt;p&gt;It means deciding, and writing down, where state lives for different categories of data. Not vaguely knowing that some state should be local and some should be shared, but specifying the actual threshold. State used by exactly one component stays in that component. State used by two or more components within a feature moves to a dedicated hook. State needed across independent features only becomes global, and only when genuinely necessary.&lt;/p&gt;

&lt;p&gt;It means deciding, and writing down, what a component boundary actually looks like in this project. Not a general sense of when something feels too big, but a specific line, whether that is a line count, a responsibility count, or a rule about mixing presentation and logic within the same file.&lt;/p&gt;

&lt;p&gt;It means deciding, and writing down, what the domain vocabulary is. The specific words this project uses for its specific concepts, so that a Customer is always a Customer and never sometimes a User, sometimes a Client, depending on which session happened to generate that particular file.&lt;/p&gt;

&lt;p&gt;Each of these is a piece of judgment that experienced developers already have, usually without having consciously articulated it as a rule. The judgment exists. What does not exist, in most cases, is the explicit written version of that judgment that an agent could actually follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more now than it did before agents did most of the typing
&lt;/h2&gt;

&lt;p&gt;Before agents were generating large portions of a codebase, a developer's judgment applied itself automatically, every time, because the same person who held the judgment was also the one physically writing every line. There was no gap between having the instinct and the instinct showing up in the code, because the instinct and the code came from the same source at the same moment.&lt;/p&gt;

&lt;p&gt;Once an agent is doing a significant share of the actual generation, that automatic application breaks. The judgment and the generation are now two separate things, connected only by whatever got communicated between them. If nothing explicit got communicated, the agent generates based on its own general patterns, and your specific judgment, however good it is, sits unused in your head while the agent makes a different decision than you would have made.&lt;/p&gt;

&lt;p&gt;This is the part of the shift that the shape-setting argument gestures at but does not fully spell out. It is not enough to have good judgment about system design in an agent-driven workflow. The judgment has to survive the transition from your head into a form the agent can actually use, every single session, not just the sessions where you happen to remember to mention it in the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The developers actually pulling ahead are doing something specific
&lt;/h2&gt;

&lt;p&gt;Watch closely at what the developers who are genuinely getting more out of agents are actually doing, beyond the general description of setting the shape and letting the agent work inside it, and a specific pattern emerges. They are not relying on remembering to communicate their judgment fresh in every prompt. They are converting that judgment into a standing set of constraints that exists independently of any individual session, so that setting the shape happens once, comprehensively, rather than being re-attempted piecemeal every time a new prompt gets written.&lt;/p&gt;

&lt;p&gt;This looks different from prompt engineering, even though the two get conflated. A well crafted prompt communicates intent for the current task. A comprehensive rule system communicates the shape for every task, regardless of how that specific prompt happens to be worded. The developers pulling ahead are not writing increasingly clever prompts. They are doing the harder, less glamorous work of articulating their judgment as an explicit, standing system once, and then letting every subsequent prompt operate inside that already-defined shape.&lt;/p&gt;

&lt;p&gt;Here is roughly what that articulation actually looks like when you sit down and do it, rather than just intending to do it eventually:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shape-defining rules that turn judgment into something an agent can follow:
1. State scope is decided by usage, not convenience. Local to one component by default. A dedicated hook when two or more components in the same feature need it. Global only when at least two independent features genuinely require the same data.
2. A component boundary is crossed when a single file starts handling more than one of the following simultaneously: rendering, data fetching, or business logic. When that happens, split before continuing, do not defer the split to a later cleanup pass.
3. The domain vocabulary for this project is fixed and documented. Deviations are not acceptable even when a different word seems reasonable in isolation.
4. Architectural decisions made for this project apply going forward from the date they were made, even in situations where the existing codebase still shows the old pattern in places that have not yet been touched.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;None of this is complicated once written down. What makes it valuable is that it existed as judgment before it existed as a rule, and writing it down is what actually lets an agent operate inside the shape rather than guessing at one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The risk of good judgment that never gets externalized
&lt;/h2&gt;

&lt;p&gt;There is a specific failure mode worth naming directly, because it is easy to fall into precisely because your judgment is genuinely good. If your instincts about architecture are strong, the code an agent produces without explicit rules will often look reasonable to you on a quick glance, because you are mentally filling in the gaps the agent left, correcting small things automatically without fully registering that you are doing correction work at all.&lt;/p&gt;

&lt;p&gt;This creates a false sense that the shape is being set successfully, when what is actually happening is that your judgment is doing constant, invisible repair work on an agent that never actually received the shape in explicit form. The system appears to be working because your judgment is compensating for its absence in the agent's process, not because the agent genuinely internalized anything.&lt;/p&gt;

&lt;p&gt;The tell is usually correction fatigue that never quite goes away, even after months of working with the same agent on the same project. If your judgment were actually transferred, the corrections needed for a given category of decision would decrease over time as the agent's output increasingly matched the shape. If corrections stay roughly constant no matter how long you have been working together, the judgment was never actually externalized. It was just being applied silently, session after session, by you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Judgment about how software should be shaped is genuinely the skill that matters most right now, and that argument is correct as far as it goes. But judgment that only exists in your head does not shape anything an agent generates. It shapes only the code you personally write, which is an increasingly small share of the total output as agents take on more of the actual generation.&lt;/p&gt;

&lt;p&gt;Setting the shape is not something you do once by having good instincts. It is something you do by converting those instincts into an explicit, standing system that exists independently of any single prompt, and giving the agent access to that system before it generates anything at all.&lt;/p&gt;

&lt;p&gt;Your judgment is the asset. Rules are how that asset actually reaches the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your architectural judgment has never actually been written down for your AI to follow?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural decisions where your instincts are strong but your AI has never received them in a form it can actually use.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Found a useEffect With Three Missing Dependencies Right Next to One With Five Unnecessary Ones. Same Codebase.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Tue, 18 Aug 2026 10:50:51 +0000</pubDate>
      <link>https://dev.to/avery_code/i-found-a-useeffect-with-three-missing-dependencies-right-next-to-one-with-five-unnecessary-ones-40em</link>
      <guid>https://dev.to/avery_code/i-found-a-useeffect-with-three-missing-dependencies-right-next-to-one-with-five-unnecessary-ones-40em</guid>
      <description>&lt;p&gt;I was debugging a stale data issue and traced it back to a useEffect that was missing three dependencies it clearly needed. The effect referenced a filter value, a page number, and a sort order, and the dependency array only listed the filter value. Classic stale closure problem, the kind that produces bugs which are maddening to track down because the code looks correct at a glance and passes every manual test you run against it in the moment.&lt;/p&gt;

&lt;p&gt;While fixing that one, I scrolled up to a different useEffect in the same file. This one had the opposite problem. Five dependencies listed, but at least two of them were objects that got recreated on every render, meaning the effect fired constantly, far more often than it needed to, doing work that should have run once or twice per user interaction but was instead running dozens of times per second during certain interactions, quietly burning performance in a way that would never show up in a quick glance at the code.&lt;/p&gt;

&lt;p&gt;Two useEffect hooks. One file. One clearly under-specified, missing dependencies it needed to function correctly. One clearly over-specified, including dependencies that caused it to fire far more than intended. Neither hook was reasoned through the same way, even though they were written weeks apart in the same codebase, presumably by the same AI working from the same underlying model, on the same general category of problem.&lt;/p&gt;

&lt;p&gt;This is the kind of inconsistency that is easy to miss because each individual effect, viewed in isolation, does not look obviously wrong. The under-specified one runs and appears to work most of the time, failing only in the specific interaction sequence that exposes the stale closure. The over-specified one also runs and appears to work, just less efficiently than it should, which rarely triggers the kind of visible failure that gets flagged in a quick review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why dependency arrays are uniquely prone to this
&lt;/h2&gt;

&lt;p&gt;Most React patterns that show inconsistency across an AI-generated codebase involve a choice between multiple legitimate approaches, the same way prop drilling has several correct solutions depending on context and none of them is objectively wrong. Dependency arrays are different in an important way. There is technically one correct answer for any given effect: include every reactive value the effect body reads, and only those values.&lt;/p&gt;

&lt;p&gt;This makes the inconsistency more surprising than in cases with genuine ambiguity. If there is one correct answer, why does the AI arrive at different, both incorrect, answers in different sessions, sometimes in the same file, sometimes within the same component even?&lt;/p&gt;

&lt;p&gt;The reason comes down to what determining the correct dependency array actually requires. It is not a simple lookup or a pattern match against something memorized. It requires tracing every variable referenced inside the effect body, determining whether each one is reactive, meaning it can change between renders, or stable, meaning it never changes for the lifetime of the component. It requires checking whether any of those variables are objects or functions that get recreated on every render even when their contents are conceptually the same. And it requires deciding whether that recreation actually matters for this specific effect, or whether the value is stable enough in practice even though it fails a strict reference equality check.&lt;/p&gt;

&lt;p&gt;This is a genuinely effortful, multi-step analysis, more so than most people appreciate when they see a two-line effect with a two-item dependency array sitting quietly in a component. Getting it right consistently requires that full trace happening every single time, not just for effects that look complicated on the surface but for the deceptively simple ones too, since those are exactly the ones where a missed dependency slips through because the effect looked too short and too obvious to warrant careful checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different failure modes with two different underlying causes
&lt;/h2&gt;

&lt;p&gt;The under-specified failure, missing dependencies that should be there, tends to happen when the effect body is doing something that does not immediately read as depending on a value, even though it functionally does. A function called inside the effect that internally closes over a piece of state, where the dependency on that state is hidden one layer down inside the function definition rather than visible directly in the effect body. A value pulled from a ref that changes over time, where the changing nature of the ref's contents is easy to overlook because refs are correctly excluded from dependency arrays in the general case, leading to an incorrect assumption that anything touching a ref can be safely omitted. A calculated value derived from something that changes, where the derivation happens through an intermediate variable or a small utility function, and the connection back to the original changing source is not obviously visible when scanning the effect body quickly.&lt;/p&gt;

&lt;p&gt;The over-specified failure, including dependencies that should not be there as-is or that need to be stabilized first, tends to happen with objects and functions specifically, rather than with primitives like strings or numbers. An options object passed into a hook, recreated fresh on every single render because it was defined inline in the component body using object literal syntax rather than being memoized with useMemo. A callback function that gets included in the dependency array because it is technically referenced inside the effect and technically does change identity on every render, without the generation process recognizing that the actual fix is wrapping that callback in useCallback rather than simply including it and accepting the extra re-runs.&lt;/p&gt;

&lt;p&gt;Both failure modes come from the same underlying gap in the reasoning process: correctly determining reactivity requires understanding not just what values are referenced textually inside the effect, but how those values are created upstream and whether their identity is actually stable across renders in practice. That second part, the identity stability question, is where both the under-specification and the over-specification tend to originate, just pulling in opposite directions depending on which piece of that reasoning gets shortcut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this varies so dramatically from session to session
&lt;/h2&gt;

&lt;p&gt;The dependency array analysis is sensitive to exactly how much of the surrounding code the model actually traces through during generation, and that amount of tracing is not consistent across different situations even within the same codebase. If a function used inside the effect is defined a few lines up in the same component, that connection is easy to make because it is locally visible. If the function is imported from a custom hook defined in a completely different file, or if the value comes from several layers of prop passing and transformation before it finally reaches this component, the full trace required to determine reactivity correctly becomes substantially harder to complete reliably during a single generation pass.&lt;/p&gt;

&lt;p&gt;This explains why the same underlying codebase can show wildly different quality in its dependency arrays depending on which specific effect you happen to look at. A simple effect in a simple component, where everything referenced is defined two lines above the effect itself and nothing is imported from elsewhere, is much more likely to get a correct dependency array than an effect buried in a component with several custom hooks, prop drilling, and context consumption feeding into it. The complexity of the surrounding code directly affects how reliably the dependency analysis actually gets completed, even though the underlying React rule for what counts as a correct dependency array does not change at all based on that surrounding complexity.&lt;/p&gt;

&lt;p&gt;This also explains why fixing one bad dependency array and moving on rarely solves the broader pattern. The specific effect gets corrected, but the underlying situation that made the analysis unreliable, complex surrounding code, indirect references, values passed through several layers, remains exactly the same for the next effect that gets generated in similarly complex surroundings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a rule actually needs to specify here
&lt;/h2&gt;

&lt;p&gt;Unlike prop drilling, where the rule needs to define which of several legitimate patterns applies under which conditions, the dependency array problem needs a rule that forces the actual analytical steps to happen rather than being silently skipped when they seem unnecessary for a short, simple-looking effect. The correct answer here is not ambiguous the way prop drilling's correct answer depends on context. The problem is that determining it correctly is effortful enough that the effort sometimes gets shortcut, especially for effects that appear simple at first glance.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dependency array verification rule:
1. Before finalizing any useEffect, explicitly list every variable, function, and value referenced inside the effect body, including anything referenced indirectly through a called function or a value derived from another value.
2. For each referenced value identified in step one, determine whether it is reactive, meaning it can change between renders, or stable, meaning it never changes for the lifetime of the component.
3. Every reactive value identified in step one belongs in the dependency array without exception, regardless of how the effect appears to behave during casual testing.
4. Any object or function included as a dependency must be verified as stable across renders before being included as-is. If it is not stable, it gets wrapped in useMemo or useCallback before being used as a dependency, rather than being included unstable or omitted entirely to avoid extra effect firing.
5. Do not use the exhaustive-deps lint suppression comment under any circumstances. If the technically correct dependency array causes a visible problem, that problem is a signal that something else in the effect needs restructuring, not that the array itself should be shortened to make the symptom disappear.
6. Any effect with more than three dependencies is a signal to reconsider whether the effect is doing too much and should be split into separate effects with narrower, more independent responsibilities.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This rule does not teach the AI new information about how dependency arrays fundamentally work, since that knowledge was already present and demonstrably correct when asked directly. It forces the explicit trace described in steps one and two to happen as a distinct step in the process, rather than allowing the shortcut of an intuitive, pattern-matched guess about what probably belongs in the array based on how similar effects have looked before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why forcing the explicit trace matters more than restating the rule
&lt;/h2&gt;

&lt;p&gt;Simply telling the AI to include all dependencies correctly is restating information it already reliably has, in the same way that telling it not to use array index as a key restates information it already reliably has when asked directly. The actual failure point in both cases is not missing knowledge about what the rule states. It is skipping the effortful trace required to correctly apply that rule to this specific, particular case, under the pressure of generating a larger component quickly.&lt;/p&gt;

&lt;p&gt;A rule that forces the trace to happen as an explicit, named step, rather than trusting that the trace happened silently and correctly somewhere in the generation process, closes the gap in a way that simply restating the rule does not. This is a pattern worth recognizing well beyond dependency arrays specifically. Anywhere a correct answer requires multi-step reasoning that can plausibly be shortcut under generation pressure, the effective rule is usually one that forces the intermediate steps to happen explicitly and visibly, not one that simply states what the final correct answer should look like once arrived at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for a team working across many sessions
&lt;/h2&gt;

&lt;p&gt;For a solo developer, inconsistent dependency arrays produce bugs that get discovered and fixed over time, unevenly, as each one happens to surface. For a team, the problem compounds differently, because multiple developers are generating effects independently, each one working from the same underlying tendency to shortcut the trace under pressure, and nobody has visibility into how the last three effects someone else wrote actually handled the same category of decision.&lt;/p&gt;

&lt;p&gt;A team without this rule accumulates a distribution of dependency array quality that mirrors individual session inconsistency, just multiplied across however many developers are contributing. Some effects will be correct. Some will be under-specified. Some will be over-specified. And because none of these failure modes throw an obvious error, the distribution tends to remain invisible until a specific bug forces someone to look closely at one particular effect, the same way my own stale data bug forced me to look closely enough to notice the second, opposite problem sitting right next to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after adding the verification rule
&lt;/h2&gt;

&lt;p&gt;Since adding this more detailed key verification rule rather than just a blanket reminder about correct dependencies, both failure modes became noticeably less frequent, and interestingly, they became less frequent in somewhat different ways and to different degrees. The under-specification cases dropped the most substantially, since forcing an explicit enumeration of every referenced value catches the ones that were previously getting missed precisely because they were not obviously connected to the effect at a casual glance. The over-specification cases also dropped, though somewhat less dramatically, since the stability check in step four specifically targets the pattern of including an unstable object or function without addressing why it is unstable in the first place, which requires recognizing the underlying cause rather than just naming the symptom.&lt;/p&gt;

&lt;p&gt;The effects that remained genuinely complicated after the rule was in place were, importantly, complicated for legitimate structural reasons rather than because of an incomplete or shortcut dependency trace. A handful of effects did end up getting split into multiple smaller effects as a direct consequence of rule six, which turned out to be a reasonable and useful side effect of forcing more deliberate attention onto what each individual effect was actually responsible for doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;The dependency array problem looks like it should be simple to fix with a quick reminder, since there genuinely is one technically correct answer for any given effect, unlike patterns with multiple legitimate solutions. But the correct answer requires an effortful, multi-step trace through the surrounding code, and that trace is exactly the part that gets shortcut when generation is moving quickly or when the referenced values are not immediately, visibly obvious from the code sitting directly above the effect.&lt;/p&gt;

&lt;p&gt;A rule that simply states the correct answer does not fix this, because the AI already knows the correct answer when asked directly and still produces the inconsistent result during actual generation. A rule that forces the trace to happen as an explicit, named step does fix it, because it removes the option of shortcutting the analysis in favor of a quick pattern match. Look for other places in your codebase where the AI has one clearly correct answer readily available but still arrives at it inconsistently across different sessions, and consider whether the fix needs to force a specific reasoning step to happen rather than simply restate the destination it should already know how to reach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project has correct-answer problems being silently shortcut during generation?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural decisions where the AI demonstrably has the right knowledge but skips the reasoning required to apply it consistently across every session.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Index as Key Is Not a Knowledge Problem. Your AI Already Knows the Rule. It Just Does Not Always Follow It.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:50:50 +0000</pubDate>
      <link>https://dev.to/avery_code/index-as-key-is-not-a-knowledge-problem-your-ai-already-knows-the-rule-it-just-does-not-always-1359</link>
      <guid>https://dev.to/avery_code/index-as-key-is-not-a-knowledge-problem-your-ai-already-knows-the-rule-it-just-does-not-always-1359</guid>
      <description>&lt;p&gt;Ask any AI coding assistant directly whether using array index as a React key is a good idea, and it will tell you no. It will explain why. Reordering, insertion, and deletion of list items can cause React to misidentify which DOM node corresponds to which data, leading to state bugs and unnecessary re-renders. This is not obscure knowledge. It is one of the most commonly repeated pieces of React advice that exists, and every model has clearly seen it thousands of times during training.&lt;/p&gt;

&lt;p&gt;And yet, if you look through a codebase where the AI generated a meaningful portion of the list rendering, you will very likely find at least one instance of exactly this pattern. A map over an array, using the index as the key prop, sitting quietly in a component that otherwise looks perfectly reasonable.&lt;/p&gt;

&lt;p&gt;This is a strange thing to observe once you notice it. The AI is not confused about the rule. Ask it directly and it recites the correct answer immediately and confidently. But somewhere between knowing the rule in the abstract and applying it consistently during generation, something gets lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why knowing a rule and applying it are different things
&lt;/h2&gt;

&lt;p&gt;There is a meaningful difference between an AI model having encountered information during training and that information reliably surfacing during every relevant generation task.&lt;/p&gt;

&lt;p&gt;When you ask directly whether index as key is a good idea, you are prompting the model to retrieve and state a fact it has strong, well reinforced associations with. This is a different cognitive task than generating a list rendering component from scratch while simultaneously handling several other decisions about structure, naming, data shape, and styling.&lt;/p&gt;

&lt;p&gt;During active generation, the model is not running through a checklist of best practices for every line it writes. It is producing output token by token based on patterns, and in the moment of writing a map function, the path of least resistance is often exactly the pattern that gets flagged as wrong when examined afterward. Using item.id as a key requires the data to reliably have a stable id field, requires the model to correctly identify which field serves that purpose, and requires slightly more consideration than reaching for the index, which is always available and always simple.&lt;/p&gt;

&lt;p&gt;The rule is not forgotten. It is simply not the strongest pull during the specific moment of generating this specific line of code, especially when the data shape is ambiguous or when generation is happening quickly across a larger component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this actually shows up
&lt;/h2&gt;

&lt;p&gt;The pattern tends to appear in a few predictable situations rather than randomly across all list rendering.&lt;/p&gt;

&lt;p&gt;It shows up most often when the data being mapped does not have an obviously named unique identifier readily visible in the immediate context. A list of strings. A list of objects where the unique field is called something other than the expected id or key. A list assembled from a transformation where the original identifier got dropped somewhere in the pipeline.&lt;/p&gt;

&lt;p&gt;It shows up when the component is being generated as part of a larger request, where the list rendering is a small piece of a bigger component and gets less individual attention than it would if it were the sole focus of the prompt.&lt;/p&gt;

&lt;p&gt;It shows up in draft or placeholder-feeling code, static lists, dummy data, early stage components where correctness feels less pressing in the moment of generation, even though these components frequently end up shipping unchanged.&lt;/p&gt;

&lt;p&gt;None of these situations involve the AI forgetting the rule exists. They involve situations where correctly applying the rule requires slightly more work than the shortcut, and nothing in the generation process forces that extra work to happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why asking for better prompts does not fully solve this
&lt;/h2&gt;

&lt;p&gt;A natural response is to just include the instruction in the prompt. Do not use index as key, use a proper unique identifier. This helps in the specific session where it is included, the same way any explicit instruction helps for that one request.&lt;/p&gt;

&lt;p&gt;But this does not solve the underlying pattern for the same reason that most single-session fixes do not solve recurring problems. The instruction has to be remembered and re-included every time list rendering comes up, across every developer using the AI on the project, across every session, indefinitely. Miss it once, in one prompt, in one session, and the shortcut reappears.&lt;/p&gt;

&lt;p&gt;This is different from the AI not knowing the rule. It is the rule not being present at the moment it needs to apply, because presence in a specific prompt is not the same as presence as a standing constraint that applies regardless of what that day's prompt happened to include.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually closes the gap
&lt;/h2&gt;

&lt;p&gt;The fix is not teaching the AI something it does not know. It already knows. The fix is removing the situation where knowing the rule and applying it under generation pressure can diverge.&lt;/p&gt;

&lt;p&gt;This means being specific about what counts as an acceptable key, not just stating the negative rule about what to avoid. A rule that says do not use index as key is less effective than a rule that specifies exactly what to use instead and what to do when the obvious identifier is missing.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Key selection rule for list rendering:
1. Every list item requires a unique, stable identifier that persists across re-renders, insertions, and deletions of other items in the list.
2. If the data object has an id field, or any field that is guaranteed unique and stable, use that field directly as the key.
3. If no such field exists in the data, generate one during data transformation before the list reaches the rendering component, not as an inline fallback during the map call itself.
4. Index as key is acceptable only for lists that are static, never reordered, never filtered, and never have items inserted or removed during the component's lifetime. This is a narrow exception, not a default.
5. When uncertain whether a list qualifies for the exception in rule four, treat it as not qualifying and require a proper identifier.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This rule does something the general knowledge did not. It removes the ambiguity about what to do when the obvious id field is missing, which is exactly the situation where the shortcut tends to appear. Instead of the model having to decide in the moment whether this particular list is an exception, the rule states that uncertainty defaults to requiring a real identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the specificity matters more than the reminder
&lt;/h2&gt;

&lt;p&gt;A rule that just says avoid index as keys functions as a reminder of something already known. A rule that specifies exactly what qualifies as acceptable, what does not, and what to do when the data does not have an obvious answer functions as a decision procedure.&lt;/p&gt;

&lt;p&gt;The difference matters because the actual failure point was never a lack of awareness. It was the absence of a clear procedure for the ambiguous cases, the ones where the ideal identifier is not immediately obvious and a decision has to be made quickly during generation. General awareness does not resolve ambiguity. A specific procedure does.&lt;/p&gt;

&lt;p&gt;This pattern generalizes beyond this one example. Anywhere the AI demonstrably knows a rule when asked directly but inconsistently applies it during generation, the fix is rarely restating the rule more forcefully. It is usually making the rule specific enough that it resolves the exact situations where the shortcut becomes tempting, rather than leaving those situations as judgment calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after adding the specific rule
&lt;/h2&gt;

&lt;p&gt;Since adding the more detailed key selection rule rather than just a blanket reminder, index as key stopped appearing in generated list rendering, including in the ambiguous cases that used to produce it most often. Lists without an obvious id field now consistently get a generated identifier during data transformation rather than falling back to the index inline.&lt;/p&gt;

&lt;p&gt;The interesting part is that this did not require teaching the AI anything new about why index as key causes problems. That knowledge was never missing. What changed was removing the specific decision point where the shortcut used to win by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Your AI is not confused about index as key. It has known the rule since before you started this project and it will tell you so if you ask it directly.&lt;/p&gt;

&lt;p&gt;The gap is not knowledge. It is the absence of a specific procedure for the exact situations where applying that knowledge requires more effort than skipping it. A general reminder does not close that gap. A specific rule about what counts as an acceptable identifier, and what to do when one is not obvious, does.&lt;/p&gt;

&lt;p&gt;Look for the other places in your project where the AI would give you the correct answer if asked directly but does not consistently apply it during generation. Those are not knowledge gaps. They are missing decision procedures, and they are usually easier to fix than they first appear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project has knowledge gaps versus procedure gaps?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural decisions where a general rule is not enough and a specific procedure is missing.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>I Found Four Different Solutions to the Same Prop Drilling Problem in One Codebase. All Written by AI.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Wed, 05 Aug 2026 11:44:21 +0000</pubDate>
      <link>https://dev.to/avery_code/i-found-four-different-solutions-to-the-same-prop-drilling-problem-in-one-codebase-all-written-by-234a</link>
      <guid>https://dev.to/avery_code/i-found-four-different-solutions-to-the-same-prop-drilling-problem-in-one-codebase-all-written-by-234a</guid>
      <description>&lt;p&gt;I was looking for a specific piece of state logic and ended up finding something more interesting instead.&lt;/p&gt;

&lt;p&gt;Four components in the same project were passing data down through their children. Same underlying problem in every case. A piece of state needed several levels below where it originated. Classic prop drilling, the kind every React developer has run into at some point.&lt;/p&gt;

&lt;p&gt;What caught my attention was that each of the four instances solved it differently. One used React Context. One used a composition pattern, passing components as children instead of passing data as props. One just kept passing the props down through four levels without addressing it at all. One introduced a small state management library that was not used anywhere else in the project.&lt;/p&gt;

&lt;p&gt;Four different solutions. Same problem. Same codebase. No consistency between any of them.&lt;/p&gt;

&lt;p&gt;None of the four solutions was wrong exactly. Context is a legitimate answer to prop drilling. So is composition. So is a state library in the right circumstances. Even just passing props through multiple levels is defensible when the chain is short. But having all four approaches scattered across one project, with no indication of when each one applies, is not a technical problem. It is a standard problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this particular pattern reveals so much
&lt;/h2&gt;

&lt;p&gt;Prop drilling is a good lens for understanding how AI makes architectural decisions, because it does not have one correct answer. Unlike something with a clear right and wrong way to do it, prop drilling has several legitimate solutions, and the correct choice depends on context that is specific to your project rather than universal to React.&lt;/p&gt;

&lt;p&gt;Context is a good solution when the data is genuinely global to a subtree, changes infrequently, and does not need fine grained update control. Composition is a good solution when the components in between do not actually need the data themselves, they are just structurally in the way. A dedicated state management approach makes sense when the state has complex update logic or needs to be accessed from many unrelated parts of the tree. And sometimes just passing props through two or three levels is genuinely fine and does not need a more sophisticated solution at all.&lt;/p&gt;

&lt;p&gt;All four of these are correct in the right circumstances. The AI has been trained on enough React code to know all four patterns exist and roughly when each one tends to get used. What it does not have is a rule specific to your project about which one your project prefers, or under what threshold prop drilling stops being fine and starts needing a different pattern.&lt;/p&gt;

&lt;p&gt;Without that rule, every session makes an independent judgment call. And because the judgment call depends on subtle context, session to session variance is almost guaranteed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens during four separate sessions
&lt;/h2&gt;

&lt;p&gt;Session one. A component three levels deep needs a piece of user data. The AI, working from whatever context is visible in that session, decides Context makes sense here. It wraps the relevant subtree in a Context Provider and consumes it three levels down.&lt;/p&gt;

&lt;p&gt;Session two, weeks later, working on a different feature. A different component four levels deep needs a piece of data. The Context solution from session one is not visible in the current context window, or the AI does not connect this situation to that one. It decides composition is cleaner here, restructures the component tree so the data-needing component receives its content as a child prop instead.&lt;/p&gt;

&lt;p&gt;Session three. Another instance of the same underlying problem. This time the component chain is shorter, only two levels, so the AI just passes the prop through directly without introducing any additional pattern. Reasonable, in isolation.&lt;/p&gt;

&lt;p&gt;Session four. A more complex case, several pieces of related state needing to reach multiple deeply nested components. The AI reaches for a small state management solution because the complexity seems to warrant it, even though nothing else in the project uses that approach.&lt;/p&gt;

&lt;p&gt;Look at each of these individually and every decision is locally reasonable. The AI is not confused or making mistakes. It is applying its general React knowledge to each specific situation as it encounters it, without any awareness of how the previous three situations were handled.&lt;/p&gt;

&lt;p&gt;The problem only becomes visible when you look at all four together, which is exactly the kind of visibility that individual sessions never have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why more context does not fix this specific problem
&lt;/h2&gt;

&lt;p&gt;This is a case where the context and rules distinction becomes very concrete. Even with full access to the codebase, more context does not solve the prop drilling inconsistency, because the codebase itself already contains four different answers.&lt;/p&gt;

&lt;p&gt;If the AI has access to all four existing solutions when it encounters a fifth instance of the pattern, it does not have a clear signal about which one to follow. It has four examples showing four different approaches. Averaging across them or picking whichever one seems most similar to the current situation does not produce consistency. It produces a fifth variation, or at best, a coin flip between the four that already exist.&lt;/p&gt;

&lt;p&gt;This is different from a situation where context genuinely helps, like naming conventions where the existing codebase mostly shows one consistent pattern and the AI can reasonably infer and follow it. Prop drilling in this project had no consistent pattern to infer from. The inconsistency itself is what makes context unable to resolve the problem.&lt;/p&gt;

&lt;p&gt;Only an explicit rule breaks the tie. Something that says, independent of what the existing code happens to show, this is the threshold and this is the pattern above that threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an actual rule looks like for this specific problem
&lt;/h2&gt;

&lt;p&gt;The rule does not need to ban three of the four approaches entirely. It needs to define when each one applies, so the decision stops being a fresh judgment call every session.&lt;/p&gt;

&lt;p&gt;Here is a rule that resolves the ambiguity for prop drilling specifically:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prop drilling resolution rule:
1. Props passed through one or two levels are acceptable without any additional pattern. Do not introduce Context or composition for chains this short.
2. Props passed through three or more levels, where the intermediate components do not use the data themselves, get restructured using composition. Pass the deeply nested content as children rather than threading the data as props.
3. Data that is genuinely needed by multiple unrelated components across a subtree, rather than just passed through structurally, uses Context. This applies when three or more sibling branches of the tree need independent access to the same data.
4. Complex state with multiple related pieces, frequent updates, or logic beyond simple value storage uses the project's established state management approach, not an ad hoc alternative introduced for this one case.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Four situations, four clear answers. Not because three of the four legitimate patterns are wrong, but because the rule specifies exactly when each one is the right one for this project. The AI is no longer choosing based on whatever seems locally reasonable. It is checking the rule and applying the answer that has already been decided.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern this reveals beyond prop drilling
&lt;/h2&gt;

&lt;p&gt;Prop drilling happens to be a clean example because it has several genuinely correct answers that depend on context. But the same dynamic applies to any React decision that has more than one legitimate solution.&lt;/p&gt;

&lt;p&gt;Conditional rendering has several valid approaches. Ternary expressions, short-circuit evaluation, early returns, separate component variants. All correct in different circumstances. Without a rule specifying when each applies, expect the same session-to-session variance that prop drilling showed.&lt;/p&gt;

&lt;p&gt;Data fetching patterns have several valid approaches depending on whether the data is needed on initial render, needs caching, needs revalidation, or is fetched in response to user interaction. Without a rule, expect a different pattern chosen based on whatever seems reasonable in each specific session.&lt;/p&gt;

&lt;p&gt;Form handling has several valid approaches, from fully controlled components to uncontrolled refs to third party form libraries. Same story.&lt;/p&gt;

&lt;p&gt;Any React decision with more than one textbook-correct answer is a decision that will drift unless a rule specifies which answer applies under which conditions in your specific project. This is a broader category than most developers realize when they first start writing rules, because the instinct is to write rules for things that are obviously wrong. The actual highest value rules are often for things that are not wrong, just inconsistent, because those are exactly the decisions where the AI has multiple correct options and no way to know which one you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after writing the rule
&lt;/h2&gt;

&lt;p&gt;Going back through sessions after the prop drilling rule was in place, the pattern was noticeably different. New instances of the same underlying problem consistently resolved the same way, based on the threshold defined in the rule rather than whatever seemed reasonable in that specific session.&lt;/p&gt;

&lt;p&gt;More importantly, the decision stopped requiring active review. Before the rule existed, every new instance of prop drilling needed a moment of evaluation during code review, checking whether this particular solution made sense or whether it should have been handled differently given what the rest of the codebase does. After the rule existed, the review could simply check compliance with the defined threshold, which is a much faster and more mechanical check than evaluating whether an architectural judgment call was reasonable.&lt;/p&gt;

&lt;p&gt;The four existing inconsistent instances did not fix themselves. Rules apply going forward, not retroactively. But no new instances of the inconsistency have appeared since, which is the actual goal. Preventing new instances of the drift matters more than immediately correcting the ones that already accumulated, because the accumulated ones are a known, bounded cost while ongoing drift is unbounded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt doesn't matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Prop drilling is not a hard problem. React developers have known how to solve it for years, and the AI knows all the standard solutions as well as any experienced developer would.&lt;/p&gt;

&lt;p&gt;The inconsistency does not come from the AI lacking knowledge about how to solve prop drilling. It comes from the AI having multiple correct options and no project-specific rule about which one applies when. That gap exists for prop drilling and it exists for every other React decision that has more than one legitimate answer.&lt;/p&gt;

&lt;p&gt;Find the decisions in your project with more than one correct solution. Write down which solution applies under which conditions. And stop letting each session make an independent, locally reasonable choice that turns out to be different from the choice made three sessions ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project has multiple valid patterns doing the same job differently?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where more than one correct solution exists and no rule specifies which one your project actually uses.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>You Think Senior Developers Prompt Better. They Just Stopped Hoping the AI Would Guess Right.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Mon, 27 Jul 2026 15:00:45 +0000</pubDate>
      <link>https://dev.to/avery_code/you-think-senior-developers-prompt-better-they-just-stopped-hoping-the-ai-would-guess-right-5dno</link>
      <guid>https://dev.to/avery_code/you-think-senior-developers-prompt-better-they-just-stopped-hoping-the-ai-would-guess-right-5dno</guid>
      <description>&lt;p&gt;There is a common belief about why experienced developers get better results from AI. The belief is that they write better prompts. More precise instructions. Better context. A sharper sense of how to phrase a request so the model understands what is actually needed.&lt;/p&gt;

&lt;p&gt;This belief is not entirely wrong, but it misses the actual mechanism behind the difference. Watch a senior developer work with AI over an extended period and the pattern that emerges is not primarily about prompt quality. It is about what happens before the prompt gets written at all.&lt;/p&gt;

&lt;p&gt;Junior developers tend to approach each session as a fresh negotiation. Write a prompt, see what comes back, correct what is wrong, ask again if needed. This is a reasonable way to work when you do not yet know what tends to go wrong repeatedly. Every project feels new. Every session is its own isolated interaction.&lt;/p&gt;

&lt;p&gt;Senior developers, after enough repetitions of the same corrections, stop treating each session as a fresh negotiation. They notice the same categories of mistake appearing across projects and across time, and instead of continuing to correct them one at a time, they write the correction down once and stop hoping the AI will guess correctly going forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The specific moment where the shift happens
&lt;/h2&gt;

&lt;p&gt;This shift does not happen because someone reads an article about AI rules and decides to try it. It happens because a developer accumulates enough frustration with the same correction to finally write it down.&lt;/p&gt;

&lt;p&gt;The pattern looks something like this. A developer notices that the AI keeps putting state in the wrong place. They correct it. Next session, same mistake, different component. They correct it again. This repeats for weeks or months, and at some point the developer has corrected this specific mistake so many times that continuing to correct it manually starts to feel absurd.&lt;/p&gt;

&lt;p&gt;That moment of absurdity is the actual trigger. Not a decision to become more disciplined about writing rules. A recognition that the correction has become so repetitive that writing it down once is obviously less effort than correcting it forever.&lt;/p&gt;

&lt;p&gt;Junior developers have not yet accumulated enough repetitions to reach that moment. Every mistake still feels somewhat novel because they have not been doing this long enough to recognize the pattern. Senior developers have usually made the same category of correction often enough, across enough different codebases, that the pattern becomes impossible to ignore.&lt;/p&gt;

&lt;p&gt;This is why the difference looks like experience from the outside. It correlates with experience because experience is what generates the repetition needed to notice the pattern. But the actual mechanism is not years of practice improving prompt-writing skill. It is enough repeated corrections to finally stop correcting manually and write the rule instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hoping the AI will guess right is the default state
&lt;/h2&gt;

&lt;p&gt;Every developer starts in the same place, whether they realize it or not. The default assumption when working with AI is that better communication will eventually produce better results. If the output is wrong, the natural response is to explain more clearly, add more context, phrase the request differently.&lt;/p&gt;

&lt;p&gt;This assumption is not unreasonable on its face. Communication genuinely does affect output quality in the moment. A clearer prompt does produce a more accurate response to that specific request.&lt;/p&gt;

&lt;p&gt;What this assumption misses is that clearer communication in the prompt does not persist to the next session. Every session starts from the same baseline regardless of how well the previous session was communicated. The clarity you achieved in one prompt does not carry forward. It has to be recreated every time, in every session, for every developer who works on the project.&lt;/p&gt;

&lt;p&gt;Hoping the AI will guess right, even with better prompts, means hoping that this specific session's communication will happen to land on the right decision. Across enough sessions, across enough developers, across enough different types of components and features, some percentage of those hopes will not pan out. And the corrections required to fix the ones that do not pan out accumulate exactly as fast as the sessions themselves.&lt;/p&gt;

&lt;p&gt;Senior developers do not have some special ability to communicate more clearly that eliminates this. They have simply stopped relying on communication clarity as the mechanism for consistency, because they have seen it fail to produce consistency often enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What replaces the hoping
&lt;/h2&gt;

&lt;p&gt;The alternative to hoping the AI guesses right is removing the guess entirely. This is the actual practice that experienced developers converge on, whether or not they frame it explicitly as writing rules.&lt;/p&gt;

&lt;p&gt;It looks like maintaining a running list of the corrections that keep recurring, and periodically converting the most frequent ones into explicit constraints that get provided before any session starts. Not instructions embedded in a single prompt, but standing context that applies to every session regardless of what that session's specific prompt says.&lt;/p&gt;

&lt;p&gt;The practical difference is significant. A prompt that says please keep components small is a hope. A rule that says components exceeding two hundred lines must be split before continuing, applied consistently across every session, is a removal of the guess. The AI is not being asked to interpret what small means in this context. It has an explicit threshold and an explicit action.&lt;/p&gt;

&lt;p&gt;Here is what this looks like as an actual practice, based on what tends to recur across most React codebases regardless of team or project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Corrections that become rules once they recur enough:
1. State placement. After correcting this the same way enough times, the rule becomes explicit: any state used by more than one component moves to a dedicated hook, not scattered useState calls, and this applies before any component gets written, not as a fix afterward.
2. Prop drilling versus context. Once a developer has manually flagged this the same way enough times, the threshold gets written down explicitly instead of evaluated fresh every session: props passed through more than two levels get reconsidered for context or composition before continuing.
3. Naming inconsistency. After noticing the same concept named three different ways across a project one too many times, the domain vocabulary gets documented explicitly so the AI is never guessing at which word applies.
4. Error handling gaps. Once a developer has added the same try-catch pattern manually enough times, the requirement becomes explicit: every async operation handles its error state before the component is considered complete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;None of these represent a fundamentally different skill from what junior developers have access to. They represent the accumulated result of noticing the same problem enough times to stop treating it as something to fix individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this looks like a skill gap but is not one
&lt;/h2&gt;

&lt;p&gt;The reason this gets attributed to seniority is that seniority is genuinely correlated with having encountered these patterns more often. A developer with five years of experience has had more opportunities to notice the same recurring mistake than a developer with six months of experience.&lt;/p&gt;

&lt;p&gt;But the actual capability being described is not a skill that takes years to develop. It is a decision to stop correcting the same thing repeatedly and write it down instead. A developer with six months of experience who notices this pattern early and starts writing rules will get the same benefit as a developer with five years who has been correcting manually the entire time without ever converting the corrections into explicit rules.&lt;/p&gt;

&lt;p&gt;This matters because it means the gap is closeable much faster than the skill narrative suggests. If the difference were genuinely about prompt-writing ability developed through years of practice, there would be no way to shortcut it. But because the actual difference is about whether recurring corrections get written down as rules, any developer can close the gap by deliberately tracking what they correct and converting the patterns into explicit constraints, without needing years of accumulated repetition first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changes once the rules exist
&lt;/h2&gt;

&lt;p&gt;The most noticeable change is not in any single session. It is in the aggregate pattern across weeks and months. The same categories of correction stop appearing. Not because the AI got smarter, but because the decisions that used to be guessed at freshly every session are no longer being guessed at.&lt;/p&gt;

&lt;p&gt;This frees up attention for the things that genuinely do require judgment in each specific case. The product logic. The edge cases that are actually unique to this feature. The architectural decisions that are genuinely ambiguous rather than recurring patterns that should have been standardized long ago.&lt;/p&gt;

&lt;p&gt;Developers who reach this state describe the shift less as their prompts becoming better and more as their sessions becoming less exhausting. The mental overhead of continuously catching the same categories of mistake disappears, and what remains is the part of the work that actually benefits from careful thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;The difference between developers who spend their sessions correcting the same recurring mistakes and developers who barely touch their AI's output is not a difference in prompting skill that takes years to acquire.&lt;/p&gt;

&lt;p&gt;It is whether the recurring corrections got written down once as explicit rules, or whether they continue getting fixed manually every single session, indefinitely, because nobody stopped to notice the pattern and remove the guessing.&lt;/p&gt;

&lt;p&gt;Track what you correct. Look for the patterns. Write the rules down. And stop hoping that this session's prompt will happen to land on the right decision when you could simply remove the decision from being a guess in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find which recurring corrections in your React project should already be rules?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The categories of correction that keep appearing because the decision behind them was never made explicit.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Your Code Review Happens After Generation. Your Standard Needs to Happen Before It.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:49:24 +0000</pubDate>
      <link>https://dev.to/avery_code/your-code-review-happens-after-generation-your-standard-needs-to-happen-before-it-1l9g</link>
      <guid>https://dev.to/avery_code/your-code-review-happens-after-generation-your-standard-needs-to-happen-before-it-1l9g</guid>
      <description>&lt;p&gt;There is an assumption buried in how most teams think about AI generated code. The assumption is that the review process will catch what needs catching. The AI generates, a human reviews, problems get flagged, corrections happen. The system works the way code review has always worked, just with an AI in the author role instead of a person.&lt;/p&gt;

&lt;p&gt;This assumption misses something fundamental about how the AI actually operates. It does not review its own output. It does not second guess its architectural decisions. It does not pause halfway through generating a component and ask whether this is the right approach for this specific project. It generates, based on whatever it can infer from context and whatever instructions it received, and then it stops.&lt;/p&gt;

&lt;p&gt;There is no internal reviewer inside the generation process. The only reviewer is the human who looks at the output afterward. And by the time that review happens, every decision has already been made.&lt;/p&gt;

&lt;h2&gt;
  
  
  What generation actually looks like from the inside
&lt;/h2&gt;

&lt;p&gt;When the AI generates a component, it makes a sequence of decisions very quickly. Where should this state live. What should this be called. How should this be structured. What pattern applies here. Each decision happens based on the immediate context and whatever general knowledge the model has, and then the next decision happens on top of it.&lt;/p&gt;

&lt;p&gt;There is no step in this process where the AI stops and evaluates whether the decision it just made matches your project's standard. It does not have access to your standard unless you gave it explicit rules to follow. It has its training, the immediate context, and the prompt. That is what informs every decision it makes during generation.&lt;/p&gt;

&lt;p&gt;This is different from how a human developer works, even one who is moving quickly. A human developer carries an internalized standard that operates continuously during the work. They do not consciously think about it most of the time, but it is there, shaping decisions as they are made. A senior developer writing a component is applying years of accumulated judgment about what this specific codebase should look like, even without articulating any of it.&lt;/p&gt;

&lt;p&gt;The AI has no equivalent internal process. It has no accumulated judgment about your specific project. It has patterns from its training and whatever is visible in the current context. Without explicit rules, every decision during generation is made in the absence of the standard that a human would have been applying automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why review after generation is the wrong place to catch this
&lt;/h2&gt;

&lt;p&gt;Code review exists to catch what generation itself does not catch. This has always been true, going back before AI was part of the workflow. A human developer writes code, another human reviews it, and the review catches logic errors, missed edge cases, security issues, things the original developer did not think of or got wrong.&lt;/p&gt;

&lt;p&gt;This works because human authorship already has a standard embedded in the generation process. The review is catching mistakes on top of a foundation that mostly reflects the project's conventions. The reviewer is looking for what went wrong, not establishing what the baseline should have been.&lt;/p&gt;

&lt;p&gt;With AI generated code the review is doing something different, whether anyone intends it to or not. Because there is no standard embedded in the generation process unless rules were provided beforehand, the review becomes the place where the standard gets established after the fact. The reviewer is not just catching mistakes. They are retroactively defining what should have happened, one comment at a time, on code that has already been written based on the AI's best guess without that definition.&lt;/p&gt;

&lt;p&gt;This is expensive in a way that traditional code review is not. The code already exists. The developer already spent time on it. Correcting it after the fact costs more than it would have cost to define the standard before generation began. And because the standard was never written down, the same retroactive correction happens again in the next pull request, and the one after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reviewer that does not exist
&lt;/h2&gt;

&lt;p&gt;If you think about what would actually solve this problem, the answer is obvious once you say it out loud. You need something reviewing the decisions as they get made, before the output reaches a human reviewer. You need a standard that operates during generation, the way a senior developer's internalized judgment operates during their own writing process.&lt;/p&gt;

&lt;p&gt;The AI cannot provide this on its own. It does not have accumulated judgment about your project. It has no internal reviewer checking its work against your conventions because it has no access to your conventions unless you give them to it explicitly.&lt;/p&gt;

&lt;p&gt;This is what rules actually do. They are not documentation for humans to reference later. They are the internal reviewer the AI does not otherwise have. When a rule says state belongs in a dedicated hook, that rule is operating during generation, before the component exists, shaping the decision the same way a human developer's internalized standard would shape their own writing.&lt;/p&gt;

&lt;p&gt;The difference between a rule and a code review comment is the difference between prevention and correction. A code review comment happens after the mistake is already in the codebase, in a pull request, taking up review time. A rule happens before the mistake has a chance to exist. The generation itself follows the standard because the standard was present during generation, not absent from it and discovered afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like when the standard operates during generation
&lt;/h2&gt;

&lt;p&gt;Consider a specific decision that happens constantly during React development. Should this piece of logic live in the component or in a hook.&lt;/p&gt;

&lt;p&gt;Without a rule, the AI decides this based on whatever it infers from context. Sometimes it puts logic in the component because that is simpler for the specific case. Sometimes it extracts a hook because the context suggests that pattern. The decision is made fresh every time, based on immediate signals rather than a consistent standard.&lt;/p&gt;

&lt;p&gt;With a rule specifying exactly when logic must be extracted into a hook, the decision is no longer being made fresh. It is being checked against a standard that exists before the component is written. The AI does not have to infer what your project prefers because your project's preference has been stated explicitly and it is present during the generation process itself.&lt;/p&gt;

&lt;p&gt;Here is what a set of rules that operate during generation actually looks like, as opposed to comments that would otherwise appear during review:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rules that replace after-the-fact review comments:
1. Any state that affects more than one render decision moves into a dedicated hook, not inline useState calls scattered through the component. This is checked during generation, not flagged during review.
2. Components receiving more than four props get evaluated for whether they are doing too much before the component is written, not after a reviewer counts the props and asks why.
3. Any function passed as a prop follows the handleX naming convention consistently, checked as the function is named, not corrected in a review comment asking for a rename.
4. Data fetching never happens directly inside a component body. This is enforced as the component is structured, not caught when a reviewer notices a useEffect doing something it should not be doing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each of these represents a decision point during generation where a rule can operate instead of a human catching the absence of that rule after the fact. The review still happens. It just has less retroactive standard-setting to do because the standard was already present when the code was written.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for the humans doing review
&lt;/h2&gt;

&lt;p&gt;This does not eliminate code review. It changes what code review is for.&lt;/p&gt;

&lt;p&gt;When rules operate during generation, the review process gets to focus on what it was originally designed for. Logic correctness. Edge cases. Whether the feature actually does what the product requires. The kinds of judgment calls that genuinely benefit from a second set of eyes, as opposed to structural and conventional decisions that should have been consistent from the start.&lt;/p&gt;

&lt;p&gt;Reviewers stop writing the same comments about component structure and state placement and naming conventions, not because they got more lenient, but because those categories of feedback are no longer necessary. The AI was not left to guess at those decisions during generation. The rules already determined them.&lt;/p&gt;

&lt;p&gt;The time this saves compounds. Every pull request that does not need retroactive standard correction is a pull request where the reviewer's attention goes to something that actually requires their judgment. Over enough pull requests this is a substantial shift in how review time gets spent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Your AI does not review its own output. It generates and stops. There is no internal process checking whether the decisions it just made match your project's standard, unless that standard was given to it explicitly before generation started.&lt;/p&gt;

&lt;p&gt;Your human reviewers are catching this after the fact, one comment at a time, on code that has already been written without the standard that should have shaped it. This is not a failure of your review process. It is a mismatch between where the standard needs to operate and where it is currently being applied.&lt;/p&gt;

&lt;p&gt;Write the rules. Give them to the AI before generation happens. And let your review process finally focus on what it was built for instead of retroactively establishing a standard that should have been there from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project needs rules operating during generation instead of corrections during review?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where your standard is currently being discovered after the fact instead of applied before generation.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>react</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Your AI Has More Context Than Ever. It Still Does Not Know Your Standard. Context and Rules Are Not the Same Thing.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Mon, 20 Jul 2026 08:33:45 +0000</pubDate>
      <link>https://dev.to/avery_code/your-ai-has-more-context-than-ever-it-still-does-not-know-your-standard-context-and-rules-are-not-49l3</link>
      <guid>https://dev.to/avery_code/your-ai-has-more-context-than-ever-it-still-does-not-know-your-standard-context-and-rules-are-not-49l3</guid>
      <description>&lt;p&gt;The promise of more context is compelling.&lt;/p&gt;

&lt;p&gt;If the AI can see more of your codebase before it generates, it should produce output that fits better. It should pick up on the patterns. It should infer the conventions. It should understand from what already exists how new things should be built.&lt;/p&gt;

&lt;p&gt;This is the argument behind tools that give the AI access to your entire project. More files. More history. More awareness of what surrounds the code it is about to write. The assumption is that more context produces more consistent output.&lt;/p&gt;

&lt;p&gt;The assumption is partially right and fundamentally incomplete.&lt;/p&gt;

&lt;p&gt;More context does help the AI make better local decisions. It can see that you use TypeScript strictly. It can see that you have hooks in a certain folder structure. It can pick up on some of the surface-level patterns and extend them.&lt;/p&gt;

&lt;p&gt;What it cannot do is infer your standard from your codebase. Because your codebase is the result of your standard, not a definition of it. And there is a critical difference between a result and a definition that determines whether more context actually solves the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What context actually gives the AI
&lt;/h2&gt;

&lt;p&gt;Context gives the AI examples.&lt;/p&gt;

&lt;p&gt;It can see what components look like in your project. It can see where hooks tend to live. It can observe naming patterns in the files it has access to. It can make educated guesses about conventions based on what it reads.&lt;/p&gt;

&lt;p&gt;This is genuinely useful. A model with access to your entire project will make better guesses than one with access to only the current file. The local consistency improves. The obvious mismatches become less common.&lt;/p&gt;

&lt;p&gt;But guessing from examples is not the same as following rules. And the difference between the two becomes visible exactly in the situations where consistency matters most.&lt;/p&gt;

&lt;p&gt;When the AI is guessing from examples it can only infer what the examples show consistently. If your codebase has any variation — any place where two different approaches exist, any historical inconsistency, any feature where something was done differently — the AI has to choose between them. And it will choose based on which pattern is more common in its context window, or which one appears most recently, or which one the current prompt seems to suggest.&lt;/p&gt;

&lt;p&gt;That choice is a guess. A more informed guess than without context. But a guess.&lt;/p&gt;

&lt;p&gt;Rules are not guesses. A rule says this is always done this way. Not this is usually done this way based on what I can see. Always. The AI does not have to infer anything. The decision has already been made and communicated explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cases where context fails and rules succeed
&lt;/h2&gt;

&lt;p&gt;The difference between context and rules becomes most visible in specific situations that every project eventually faces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legacy inconsistency.&lt;/strong&gt; Every codebase older than a few months has places where things were done differently before the current standard was established. Those inconsistencies exist in the context the AI reads. When the AI infers patterns from context, it averages across all of them, including the old ones you no longer want. Rules specify the current standard, not the historical average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New patterns.&lt;/strong&gt; When your team decides to adopt a new approach — a new way of structuring features, a new state management pattern, a new import convention — the context initially shows the old pattern more than the new one. The AI will continue generating the old pattern because it is what the examples show. A rule immediately applies the new standard regardless of what the historical examples look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ambiguous situations.&lt;/strong&gt; Many architectural decisions are genuinely ambiguous from context alone. Should this state be local or global? Should this logic be in a hook or a service? Should this component be split now or later? The context shows examples of both approaches in different situations. The AI has to guess which applies here. Rules eliminate the guessing by making the decision explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-session consistency.&lt;/strong&gt; Context changes every session based on which files are open, which parts of the codebase are visible, what the prompt focuses on. The AI's inferences change with the context. Rules are constant. They apply regardless of which files happen to be in context this session.&lt;/p&gt;

&lt;p&gt;These are not edge cases. They are the situations that produce the inconsistency that more context was supposed to fix. And more context does not fix them because the problem is not the quantity of examples. It is the absence of explicit decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why developers believe context is the answer
&lt;/h2&gt;

&lt;p&gt;The context argument is intuitive because it mirrors how human developers learn.&lt;/p&gt;

&lt;p&gt;A new developer joins a project and gets better by seeing more of the codebase. The more code they read, the better they understand the patterns. The more context they have, the more consistently they code.&lt;/p&gt;

&lt;p&gt;So it feels natural to apply the same logic to AI. Give it more context and it will code more consistently, just like a developer who has seen more of the project.&lt;/p&gt;

&lt;p&gt;But there is a crucial difference. A human developer does not just see examples. They have conversations. They ask questions. They get explanations. They receive explicit feedback in code reviews. They absorb not just what the codebase looks like but why it looks that way and what it should look like when they add to it.&lt;/p&gt;

&lt;p&gt;The AI only gets the examples. It does not get the conversations. It does not get the explanations. It does not get the code reviews that say "we stopped doing it that way six months ago, here is the current approach." It infers from what it can see, which is never the complete picture.&lt;/p&gt;

&lt;p&gt;Rules give the AI what conversations give a human developer. Not more examples. Explicit decisions. This is the standard. This is always the answer. This is what you do here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rules give the AI that context cannot
&lt;/h2&gt;

&lt;p&gt;Rules are decisions made once and communicated explicitly.&lt;/p&gt;

&lt;p&gt;They do not require inference. They do not depend on the consistency of the existing codebase. They do not change based on which files happen to be in the context window. They apply every session regardless of what examples are visible.&lt;/p&gt;

&lt;p&gt;This is what makes them different from context in a way that matters practically.&lt;/p&gt;

&lt;p&gt;Consider the state placement decision. A project with more context might show the AI that state sometimes lives in components and sometimes in hooks. The AI infers that both are acceptable and makes a session-by-session judgment about which applies. Inconsistency.&lt;/p&gt;

&lt;p&gt;A rule says state belongs in a dedicated hook within the feature. Always. The AI does not have to infer anything. The decision has been made. Every session produces the same answer regardless of what the context shows.&lt;/p&gt;

&lt;p&gt;Here is what that looks like across a set of decisions that context cannot reliably determine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rules that context cannot replace:
1. State scope is decided before any code is written. Local state stays local. Shared state moves to a dedicated hook. Global state only when two independent features require it. Context shows examples of all three. The rule specifies which applies when.
2. The domain language is defined explicitly. Customer means this. User means that. Order means this. Cart means that. Never these alternatives. Context shows whatever words have been used. The rule defines which words should be used.
3. The current architectural pattern applies to new code even when the existing codebase shows the old pattern. Context averages across history. The rule specifies the present standard.
4. Import paths go through feature index files. Always. Not sometimes, not when it seems appropriate. Context shows some direct imports from the pre-refactor period. The rule says those are not the standard.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These rules cannot be inferred from context with the reliability that makes them useful. They have to be stated explicitly. Context is useful for many things. Replacing explicit decisions is not one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right relationship between context and rules
&lt;/h2&gt;

&lt;p&gt;Context and rules are not competing approaches. They work together and each does something the other cannot.&lt;/p&gt;

&lt;p&gt;Context helps the AI understand what already exists. It helps with local consistency. It helps the AI extend existing patterns when those patterns are clear and consistent. It is genuinely valuable and more context is generally better than less.&lt;/p&gt;

&lt;p&gt;Rules tell the AI what should exist. They define the standard that new code must meet regardless of what the existing code shows. They are the explicit decisions that remove ambiguity from the situations where context produces inconsistent guesses.&lt;/p&gt;

&lt;p&gt;A project with good context and good rules gets both benefits. The AI understands the existing codebase and follows a defined standard when adding to it. The output is consistent not because the AI made good guesses but because the decisions were made before the AI started guessing.&lt;/p&gt;

&lt;p&gt;A project with good context but no rules gets the benefit of better local guesses. The output is more consistent than without context. But it is still inconsistent in the ways that matter because the AI is still guessing at the decisions that rules would have made explicit.&lt;/p&gt;

&lt;p&gt;More context is not the answer to inconsistent AI output. It is a partial improvement on top of a missing foundation. The foundation is rules. And no amount of context replaces the need to define what the standard actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Your AI has more context than ever. It can see your folder structure, your existing components, your patterns and naming and architectural decisions.&lt;/p&gt;

&lt;p&gt;And it still does not know your standard. Because your standard is not in the examples. It is in the decisions behind the examples. And decisions have to be stated explicitly to be followed consistently.&lt;/p&gt;

&lt;p&gt;Give your AI the context it needs to understand what exists. Give it the rules it needs to know what should exist. And stop expecting more examples to do the work that only explicit decisions can do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project is relying on context where it needs rules?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you find exactly that. The structural gaps where your AI is inferring instead of following and the inconsistency that results.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Every React Project Develops a Domain Language Over Time. The AI Invents Its Own Every Session.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:57:17 +0000</pubDate>
      <link>https://dev.to/avery_code/every-react-project-develops-a-domain-language-over-time-the-ai-invents-its-own-every-session-57dd</link>
      <guid>https://dev.to/avery_code/every-react-project-develops-a-domain-language-over-time-the-ai-invents-its-own-every-session-57dd</guid>
      <description>&lt;p&gt;Every product has a language.&lt;/p&gt;

&lt;p&gt;Not a programming language. A domain language. The specific words that mean specific things in the context of this particular product, this particular business, this particular problem space.&lt;/p&gt;

&lt;p&gt;In an e-commerce platform a Customer is not the same as a User. An Order is not the same as a Cart. A Listing is not the same as a Product. These distinctions matter. They reflect real differences in the business domain. The right word in the right place makes code readable. The wrong word, or five different words for the same concept, makes code that requires translation every time someone reads it.&lt;/p&gt;

&lt;p&gt;Your team knows this language. You have been building it for months or years. It lives in conversations, in pull request comments, in the names your product managers use when they describe features. It is the shared vocabulary that makes your team efficient.&lt;/p&gt;

&lt;p&gt;Your AI does not know any of it. And every session it invents its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What domain language drift actually looks like
&lt;/h2&gt;

&lt;p&gt;It starts subtly and accumulates over time.&lt;/p&gt;

&lt;p&gt;In the first few months the AI generates components and hooks that use reasonable naming. Nothing obviously wrong. The words it chooses are generic React vocabulary and they work well enough for what they describe.&lt;/p&gt;

&lt;p&gt;But beneath the surface the domain language is fragmenting. The concept your team calls a Customer appears as User in some components, Client in others, Buyer in the ones the AI generated when it was looking at a different part of the codebase. The concept your team calls an Order appears as Purchase in one hook, Transaction in another, Checkout in a third.&lt;/p&gt;

&lt;p&gt;None of these are wrong in isolation. User is a reasonable word. So is Client. So is Buyer. But they are not the same word and in your domain they do not mean the same thing. The AI does not know the difference because nobody told it.&lt;/p&gt;

&lt;p&gt;Over six months of sessions, the codebase stops speaking one language. It speaks five. The AI's language for this week's feature, the AI's language from three months ago, what the senior developer wrote before the AI was in the workflow, what the new developer added last sprint, and the actual domain language that exists in business conversations but has never been written down in a form the AI could follow.&lt;/p&gt;

&lt;p&gt;A new developer joins and tries to understand the codebase by reading it. They find Customer in some places and User in others and cannot tell if they are the same thing or different things. They have to ask. The answer is that they are the same thing. The AI just did not know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why domain language matters more than naming conventions
&lt;/h2&gt;

&lt;p&gt;Naming conventions are about form. PascalCase for components. camelCase for functions. handleX for event handlers. These are rules about how words are written.&lt;/p&gt;

&lt;p&gt;Domain language is about meaning. Customer versus User. Order versus Purchase. Listing versus Product. These are decisions about which words carry which concepts.&lt;/p&gt;

&lt;p&gt;Both matter. But they solve different problems. Naming conventions solve consistency of form. Domain language solves consistency of meaning. And consistency of meaning is what makes a codebase readable to someone who understands the business.&lt;/p&gt;

&lt;p&gt;A codebase with perfect naming conventions but fragmented domain language is technically consistent but semantically confusing. You know how the words are capitalized. You do not know which word to use when you are describing a person who has completed a purchase.&lt;/p&gt;

&lt;p&gt;Domain language fragmentation is harder to notice than naming convention violations because it does not trigger any automated checks. No linter catches the fact that Customer and User are being used interchangeably. No TypeScript error appears when a component receives a User object and calls it a Customer internally. The code compiles. The tests pass. The language is fragmented and nobody knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the AI's invented language comes from
&lt;/h2&gt;

&lt;p&gt;The AI is not making random choices when it names domain concepts.&lt;/p&gt;

&lt;p&gt;It is making locally reasonable choices based on whatever context is visible in the current session. If the file it is working in uses User, it uses User. If the adjacent hook uses Customer, it might switch. If the prompt mentions a buyer, it might use Buyer. If it cannot infer anything from context, it falls back to the most generic available term.&lt;/p&gt;

&lt;p&gt;Each individual choice is defensible. The aggregate is chaos.&lt;/p&gt;

&lt;p&gt;This is the core of the problem. The AI is not bad at naming. It is excellent at locally consistent naming within a session. The problem is that locally consistent naming across different sessions, with different context, making different locally reasonable choices, produces a codebase that is globally inconsistent in meaning.&lt;/p&gt;

&lt;p&gt;Domain language is by definition not something the AI can derive from context alone. Context tells it what React patterns are visible nearby. It does not tell it that in this product, in this business domain, Customer means someone who has completed at least one purchase while User means anyone with an account. That distinction exists in the business. It has never been written down in a form the AI can use before it generates something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the domain language document
&lt;/h2&gt;

&lt;p&gt;The fix is not complicated. It is just work that almost no team does because it does not feel like a development task.&lt;/p&gt;

&lt;p&gt;A domain language document is a list of the concepts in your product and the specific words your codebase uses to represent them. Not a business glossary. Not user-facing terminology. The specific technical terms that should appear in component names, hook names, type definitions, and variable names throughout the codebase.&lt;/p&gt;

&lt;p&gt;Here is what a domain language document looks like for a simplified e-commerce context:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Domain language rules for this project:

Person concepts:
- User: anyone with an account, authenticated or not
- Customer: a User who has completed at least one purchase
- Guest: an unauthenticated visitor
- Never use: Client, Buyer, Shopper, Member

Transaction concepts:
- Cart: items selected but not yet purchased
- Order: a completed purchase transaction
- LineItem: a single product within a Cart or Order
- Never use: Basket, Purchase, Transaction, Item (alone)

Product concepts:
- Product: the base catalog entry
- Listing: a Product with pricing and availability for a specific context
- Variant: a specific configuration of a Product
- Never use: Item, Good, Merchandise, SKU (in component names)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This document does not take long to create for a project that has been running for any meaningful time. The domain language already exists. It lives in conversations and pull request comments and the names product managers use. Writing it down is mostly a matter of making explicit what is already implicit.&lt;/p&gt;

&lt;p&gt;Once it exists it becomes part of the rules the AI receives before every session. The AI stops inventing its own vocabulary because the vocabulary has been defined. Customer appears where Customer should appear. User appears where User should appear. The distinction is maintained because the rule exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes in the codebase over time
&lt;/h2&gt;

&lt;p&gt;Domain language consistency compounds in the same way that domain language fragmentation does. But in the right direction.&lt;/p&gt;

&lt;p&gt;When the AI uses the correct domain terms consistently, new components look like existing components not just structurally but semantically. A developer reading a new feature written last week can immediately understand what it is talking about because the words are the same words used everywhere else.&lt;/p&gt;

&lt;p&gt;The cognitive overhead of reading the codebase drops. Not because the code became simpler. Because the language became consistent. A developer does not have to translate between User and Customer and Client when reading different parts of the system. They encounter one word for each concept and they know immediately what it means.&lt;/p&gt;

&lt;p&gt;Onboarding accelerates. A new developer learns the domain language once from the documentation and then sees it applied consistently everywhere in the codebase, including in every component and hook the AI generates from that point forward.&lt;/p&gt;

&lt;p&gt;Search becomes reliable. A developer looking for everything related to Orders can search for Order and find it. They do not have to also search for Purchase and Transaction and Checkout and Fulfillment and hope they have covered all the variations.&lt;/p&gt;

&lt;p&gt;The codebase starts reading like it was written by a team that shared a language. Because the rules ensure that it was. Even when the AI was involved in building large portions of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The language your product already has
&lt;/h2&gt;

&lt;p&gt;Your product already has a domain language. Your team already knows it.&lt;/p&gt;

&lt;p&gt;The gap is not that the language does not exist. The gap is that it has never been written down in a form the AI can follow before it generates something.&lt;/p&gt;

&lt;p&gt;Every time the AI uses the wrong word for a concept in your domain, it is not making a mistake. It is filling a gap you left open. The word you wanted was not in the rules. So it used the word that seemed most reasonable given the context it had.&lt;/p&gt;

&lt;p&gt;Write the domain language down. Give it to the AI before every session. And stop finding five words for the same concept in a codebase that should only ever have one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;Your React project has a language. It developed over time through the work your team did and the conversations you had about what things should be called.&lt;/p&gt;

&lt;p&gt;Your AI speaks a different language every session. Not because it cannot speak yours. Because you never told it what yours was.&lt;/p&gt;

&lt;p&gt;Write it down. Apply it consistently. And let the codebase finally speak one language instead of five.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find where your React project's domain language has fragmented?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where your AI has been inventing vocabulary instead of following the language your product already has.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>I Started Tracking How Much Time I Spent Correcting AI Output. The Number Changed Everything.</title>
      <dc:creator>Avery</dc:creator>
      <pubDate>Mon, 13 Jul 2026 12:00:24 +0000</pubDate>
      <link>https://dev.to/avery_code/i-started-tracking-how-much-time-i-spent-correcting-ai-output-the-number-changed-everything-4p2d</link>
      <guid>https://dev.to/avery_code/i-started-tracking-how-much-time-i-spent-correcting-ai-output-the-number-changed-everything-4p2d</guid>
      <description>&lt;p&gt;It started as a small experiment.&lt;/p&gt;

&lt;p&gt;I had been working with AI for about eight months at that point. The sessions felt productive. Features were getting built faster than before. The general sense was that the AI was saving time and the workflow was better than without it.&lt;/p&gt;

&lt;p&gt;But I had a nagging feeling that some of that time was going somewhere it should not. The corrections at the end of each session. The adjustments before a pull request. The small fixes that felt automatic, barely worth noticing individually.&lt;/p&gt;

&lt;p&gt;So I started writing them down. Not obsessively. Just a rough log at the end of each session. What I corrected, approximately how long it took.&lt;/p&gt;

&lt;p&gt;Two weeks later I looked at the numbers. The correction time was not small. It was not the minor overhead I had assumed. It was a significant portion of every session. And when I projected it across a month, across a year, it was a number that made me stop and think seriously about what I was actually doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the log actually showed
&lt;/h2&gt;

&lt;p&gt;The corrections fell into categories almost immediately.&lt;/p&gt;

&lt;p&gt;Some were logic errors. Places where the AI had misunderstood the requirement or made a technical mistake. These were real AI failures in the traditional sense. They were also relatively rare and varied enough that they did not suggest a systemic pattern.&lt;/p&gt;

&lt;p&gt;The majority were something else entirely. They were project mismatches. The component structured in a way that worked but did not match the project's pattern. The state placed somewhere reasonable but not where this project puts state. The naming that made sense in isolation but did not follow the convention. The import that went directly to a file instead of through the feature's public API.&lt;/p&gt;

&lt;p&gt;None of these were React mistakes. They were project-specific decisions the AI made without guidance because no rules existed to make them differently.&lt;/p&gt;

&lt;p&gt;And they were not random. They were the same categories of correction every session. Different files. Different features. Same corrections. Because the missing rules were always the same missing rules and the AI was always filling those gaps with its own decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual numbers
&lt;/h2&gt;

&lt;p&gt;Over two weeks I logged corrections across roughly thirty sessions.&lt;/p&gt;

&lt;p&gt;Logic errors and genuine AI mistakes: approximately eight percent of total correction time. Varied, unpredictable, the kind of thing you review carefully regardless.&lt;/p&gt;

&lt;p&gt;Project mismatch corrections, the category that rules would have prevented: approximately seventy-eight percent of total correction time. Consistent, predictable, the same categories appearing session after session.&lt;/p&gt;

&lt;p&gt;The remaining fourteen percent was genuinely ambiguous, things that could go either way and where I made a judgment call.&lt;/p&gt;

&lt;p&gt;So roughly four out of every five minutes I spent correcting AI output were minutes spent on something that a rule could have prevented. Not edge cases. Not genuinely uncertain situations. Predictable, repeatable corrections that I had been making session after session for eight months without ever thinking to write them down.&lt;/p&gt;

&lt;p&gt;When I calculated the projection, the number was significant. Hours per month spent on corrections that should not have needed to happen. Over a year it was the equivalent of weeks of productive development time spent on something entirely preventable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nobody measures this
&lt;/h2&gt;

&lt;p&gt;Correction time is invisible in the way that matters most. It does not show up on a sprint board. It does not appear in a time tracking system. It does not surface in velocity metrics or planning conversations.&lt;/p&gt;

&lt;p&gt;It disappears into the texture of individual sessions. A few minutes here. A few minutes there. Each individual correction is too small to flag, too routine to mention, too automatic to register as a cost.&lt;/p&gt;

&lt;p&gt;The result is that teams and individual developers consistently underestimate the real cost of working with AI without rules. The productivity gain from generating code faster is real and visible. The productivity loss from correcting the output of that generation is real and invisible.&lt;/p&gt;

&lt;p&gt;The net benefit of AI in a workflow without rules is significantly smaller than it appears. Because the correction time is never subtracted from the time saved.&lt;/p&gt;

&lt;p&gt;For freelancers the equation is even more direct. Correction time is not billable. Every minute spent fixing a project mismatch the AI introduced is a minute that contributed nothing to deliverable output. Multiply that across a year and it is a meaningful reduction in effective hourly rate that never shows up anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened after I started writing rules
&lt;/h2&gt;

&lt;p&gt;I did not set out to run a controlled experiment. But the log continued after I started adding rules and the comparison was informative.&lt;/p&gt;

&lt;p&gt;The logic error category stayed roughly the same. Genuine AI mistakes do not respond to project rules because they are not caused by missing project knowledge.&lt;/p&gt;

&lt;p&gt;The project mismatch category collapsed. Not eliminated entirely, there were still occasional corrections as I discovered gaps in the rules and filled them. But the consistent, predictable corrections that had made up the majority of my correction time largely stopped appearing.&lt;/p&gt;

&lt;p&gt;The total correction time per session dropped by more than half within the first month. By the second month it had dropped further as the rule set became more comprehensive.&lt;/p&gt;

&lt;p&gt;The productivity gain that the AI was supposed to provide finally materialized in full. Not because the AI got better. Because the time that had been going into corrections started going into building instead.&lt;/p&gt;

&lt;p&gt;Here is what the rules that made the biggest difference looked like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rules that eliminated the highest-volume corrections:
1. Component structure follows the presentational or container pattern without exception. No component mixes both. This single rule eliminated the most common structural correction I was making.
2. State belongs in a dedicated hook within the feature. Not in the component. Not in a shared store unless genuinely needed across independent features. This eliminated the second most common correction.
3. Imports go through feature index files. No direct imports across feature boundaries. This eliminated a category of correction I had been making so automatically I had stopped noticing it.
4. Names use the project's established patterns. handleX for event handlers. useX for hooks. isX and hasX for booleans. No variations. This eliminated the naming corrections that had been appearing in almost every session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Four rules. The corrections they covered represented the vast majority of the time I had been spending on project mismatch fixes. Writing them down took less time than a single session's worth of corrections.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement that changes the conversation
&lt;/h2&gt;

&lt;p&gt;Most conversations about AI productivity focus on the time saved by generating code faster.&lt;/p&gt;

&lt;p&gt;That number is real. AI does generate code faster than writing by hand. For many tasks the speedup is significant.&lt;/p&gt;

&lt;p&gt;But the conversation rarely includes the time spent correcting the output of that generation. And without that number, the true productivity impact of working with AI without rules is invisible.&lt;/p&gt;

&lt;p&gt;When you measure both, the picture changes. The time saved by faster generation minus the time spent on avoidable corrections gives you the real productivity gain. For most developers working without rules, that real number is substantially smaller than the apparent number.&lt;/p&gt;

&lt;p&gt;For developers working with rules, the correction time that was offsetting the generation speed largely disappears. The real productivity gain approaches the apparent one.&lt;/p&gt;

&lt;p&gt;That is not a marginal improvement. It is often the difference between AI that genuinely transforms a workflow and AI that feels like it should be transforming the workflow but somehow never quite delivers on the promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt does not matter. The rules do.
&lt;/h2&gt;

&lt;p&gt;The AI is generating code faster than you could write it by hand. That part is working.&lt;/p&gt;

&lt;p&gt;What is not working is the time going into correcting what it generates. That time is not random or unavoidable. It is concentrated in predictable, repeatable categories that rules eliminate.&lt;/p&gt;

&lt;p&gt;Track the corrections for two weeks. Write down what you fix and roughly how long it takes. Look at the categories. Write rules for the ones that keep appearing.&lt;/p&gt;

&lt;p&gt;The number that changes everything is waiting for you in that log.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to find which corrections you keep making that should already be rules?
&lt;/h2&gt;

&lt;p&gt;I built a free 24 point checklist that helps you identify exactly that. The structural gaps where your AI is making decisions without constraints and your correction time is paying the price.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-clean-code-checklist?utm_source=devto" rel="noopener noreferrer"&gt;Get the React AI Clean Code Checklist — free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://averylabs.gumroad.com/l/avery-code-react-ai-coding-system-pro?utm_source=devto" rel="noopener noreferrer"&gt;Avery Code React AI Engineering System&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
