<?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: Joan Winter</title>
    <description>The latest articles on DEV Community by Joan Winter (@joan_winter_cfc6ba9b590e7).</description>
    <link>https://dev.to/joan_winter_cfc6ba9b590e7</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%2F4015594%2Fab35351d-18c7-4206-9ba2-328f632bac44.png</url>
      <title>DEV Community: Joan Winter</title>
      <link>https://dev.to/joan_winter_cfc6ba9b590e7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joan_winter_cfc6ba9b590e7"/>
    <language>en</language>
    <item>
      <title>Why Google keeps disapproving your Shopping products — and how to spot it in 10 seconds</title>
      <dc:creator>Joan Winter</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:48:14 +0000</pubDate>
      <link>https://dev.to/joan_winter_cfc6ba9b590e7/why-google-keeps-disapproving-your-shopping-products-and-how-to-spot-it-in-10-seconds-4m4i</link>
      <guid>https://dev.to/joan_winter_cfc6ba9b590e7/why-google-keeps-disapproving-your-shopping-products-and-how-to-spot-it-in-10-seconds-4m4i</guid>
      <description>&lt;p&gt;If you run Google Shopping ads for a Shopify or WooCommerce store, you've seen it: products quietly marked &lt;strong&gt;Disapproved&lt;/strong&gt; in Merchant Center, silently cutting your reach and wasting spend. The frustrating part is that the causes are almost never interesting — they're boring data problems in your product feed.&lt;/p&gt;

&lt;p&gt;Here are the ones that bite most often in 2026, and a free tool I built to find them instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual suspects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Invalid GTIN.&lt;/strong&gt; Every barcode number ends in a &lt;em&gt;check digit&lt;/em&gt; computed from the others. One mistyped digit fails validation and disapproves the item. And here's the trap: &lt;strong&gt;never invent a GTIN to make the error go away&lt;/strong&gt; — fabricated identifiers can get your whole account suspended. If a product genuinely has no barcode (handmade, custom, own-brand), set &lt;code&gt;identifier_exists = no&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Price format.&lt;/strong&gt; Google wants a number plus an ISO currency code: &lt;code&gt;24.99 GBP&lt;/code&gt;. A currency symbol (&lt;code&gt;£24.99&lt;/code&gt;) or a comma decimal (&lt;code&gt;24,99&lt;/code&gt;) gets rejected. The feed price also has to match the landing page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Availability values.&lt;/strong&gt; It must be exactly &lt;code&gt;in_stock&lt;/code&gt;, &lt;code&gt;out_of_stock&lt;/code&gt;, &lt;code&gt;preorder&lt;/code&gt;, or &lt;code&gt;backorder&lt;/code&gt;. Friendly-looking values like &lt;code&gt;available&lt;/code&gt;, &lt;code&gt;in stock&lt;/code&gt;, or &lt;code&gt;yes&lt;/code&gt; are rejected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Titles.&lt;/strong&gt; Promotional text (&lt;code&gt;Free shipping&lt;/code&gt;, &lt;code&gt;Sale&lt;/code&gt;, &lt;code&gt;Best price&lt;/code&gt;), ALL-CAPS titles, and titles over 150 characters are all common triggers. Titles should describe the product, not advertise it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Missing fields.&lt;/strong&gt; Every item needs a unique &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, &lt;code&gt;image_link&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, and &lt;code&gt;availability&lt;/code&gt;. A blank description (common when a Shopify product's body is empty) is enough to disapprove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding yours without reading diagnostics row by row
&lt;/h2&gt;

&lt;p&gt;Merchant Center will tell you &lt;em&gt;that&lt;/em&gt; something is wrong, but digging through it product by product is slow. So I built &lt;strong&gt;&lt;a href="https://yoonustaji.github.io/feed-doctor/" rel="noopener noreferrer"&gt;Feed Doctor&lt;/a&gt;&lt;/strong&gt; — a single page where you drop your product feed (a Shopify CSV export or a Google feed, CSV/TSV/XML) and it validates every row against Google's product-data spec, returning a ranked, plain-English list of what will be disapproved and why.&lt;/p&gt;

&lt;p&gt;The part I care about most: &lt;strong&gt;it runs entirely in your browser.&lt;/strong&gt; Your product data is never uploaded anywhere — there's no backend to send it to. Close the tab and it's gone. For something touching your store's catalogue, that mattered to me.&lt;/p&gt;

&lt;p&gt;It checks GTIN check digits, price/availability formatting, title issues, missing fields, and the identifier logic above. There's a one-click paid fixer if you want the corrected feed generated for you, but the audit itself is free and needs no signup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it: &lt;a href="https://yoonustaji.github.io/feed-doctor/" rel="noopener noreferrer"&gt;https://yoonustaji.github.io/feed-doctor/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to go deeper on any single error, I wrote up the &lt;a href="https://yoonustaji.github.io/feed-doctor/guides/why-is-my-google-shopping-product-disapproved.html" rel="noopener noreferrer"&gt;full list of disapproval reasons&lt;/a&gt; and a &lt;a href="https://yoonustaji.github.io/feed-doctor/guides/fix-missing-invalid-gtin-google-shopping.html" rel="noopener noreferrer"&gt;dedicated guide to fixing invalid GTINs the safe way&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with a small, dependency-free JS validation engine — happy to talk about how the GTIN check-digit and rule layer work if anyone's interested.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>webdev</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Stop prompting, start encoding: turning engineering discipline into Claude Code skills</title>
      <dc:creator>Joan Winter</dc:creator>
      <pubDate>Sat, 04 Jul 2026 23:01:41 +0000</pubDate>
      <link>https://dev.to/joan_winter_cfc6ba9b590e7/stop-prompting-start-encoding-turning-engineering-discipline-into-claude-code-skills-1j6j</link>
      <guid>https://dev.to/joan_winter_cfc6ba9b590e7/stop-prompting-start-encoding-turning-engineering-discipline-into-claude-code-skills-1j6j</guid>
      <description>&lt;p&gt;Claude Code is remarkably capable. But out of the box it does what you &lt;em&gt;prompt&lt;/em&gt; — and most prompts quietly skip the checks a senior engineer would never skip. Ask it to "fix the slow endpoint" and it will happily eager-load a relation and call it done, without ever proving the slowness was an N+1 or measuring the result. The capability is there. The &lt;em&gt;discipline&lt;/em&gt; is not — because you did not ask for it.&lt;/p&gt;

&lt;p&gt;The fix is not a bigger prompt. It is encoding the workflow as a &lt;strong&gt;skill&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a skill actually is
&lt;/h2&gt;

&lt;p&gt;A Claude Code skill is a Markdown file (&lt;code&gt;SKILL.md&lt;/code&gt;) with a little frontmatter and a body of instructions. The frontmatter is what makes it discoverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n-plus-one-hunter&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;N+1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;problems&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hot-path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;waste.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;when&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;slow,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DB&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;high,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;traffic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;events."&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;description&lt;/code&gt; is load-bearing: it is how Claude decides &lt;em&gt;when&lt;/em&gt; to reach for the skill. Write it as "use when X" and the model matches it to the situation on its own.&lt;/p&gt;

&lt;p&gt;The body is where discipline lives. The difference between a skill that works and one that produces confident nonsense comes down to one principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification gates beat goal descriptions
&lt;/h2&gt;

&lt;p&gt;Compare two ways of writing the same step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal description:&lt;/strong&gt; "Remove dead code carefully."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification gate:&lt;/strong&gt; "A symbol is provably dead only if ALL hold: no references in code, templates, configs, or string literals (grep with and without word boundaries — dynamic languages call things by string); not part of any public API surface; not framework-magic (signals, fixtures, tasks); and the test suite passes with it removed. Anything failing a criterion is quarantined, not deleted."&lt;/p&gt;

&lt;p&gt;The first is a vibe. The second is a checklist the model cannot skip without noticing. Same goal — wildly different reliability, especially on the tenth run when the novelty has worn off.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Here is the spine of an N+1 hunting skill, compressed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locate suspects statically.&lt;/strong&gt; Identify the ORM and its lazy-loading idioms; grep for relation access inside loops, &lt;code&gt;.map&lt;/code&gt;, and template iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm with query counts — never fix an unconfirmed suspect.&lt;/strong&gt; Set up query counting (&lt;code&gt;assertNumQueries&lt;/code&gt;, an engine event listener, Prisma &lt;code&gt;$on('query')&lt;/code&gt;). Exercise the endpoint at realistic N (20+, not 2). A real N+1 shows the query count scaling with N. Record: endpoint, N, count, statement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix in preference order:&lt;/strong&gt; eager-load, then annotate/aggregate in the DB, then restructure, then cache (last resort — and say so, because it hides the problem).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove it.&lt;/strong&gt; Re-run the counter, record before/after in the commit ("queries: 142 → 4 at N=50"), run the full suite (eager loading changes queryset semantics — a test change is a red flag, not a rubber stamp).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice step 2. Without the "confirm before fixing" gate, an eager assistant optimizes things that were never slow and never measures whether it helped. The gate is the whole ballgame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;I packaged two of these as a free repo you can drop into any project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;readme-refresh&lt;/code&gt; — treat every claim in a README as something to verify against the code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pr-description&lt;/code&gt; — write the PR description from the actual diff, not from memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/joanwinter333-rgb/claude-code-skills-free" rel="noopener noreferrer"&gt;https://github.com/joanwinter333-rgb/claude-code-skills-free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install is a copy into &lt;code&gt;.claude/skills/&lt;/code&gt;, then type &lt;code&gt;/&lt;/code&gt; in Claude Code.&lt;/p&gt;

&lt;p&gt;If they are useful, there is a full set of 10 (legacy audit, migration safety, security sweep, perf baseline, incident postmortem, and more) here: &lt;strong&gt;&lt;a href="https://winterjoy1.gumroad.com/l/qnrbr" rel="noopener noreferrer"&gt;https://winterjoy1.gumroad.com/l/qnrbr&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Honest note: these skill docs were authored with AI assistance and shaped by real production experience maintaining live systems. Every workflow was tested in Claude Code before release.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
