<?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: shekhar chandran</title>
    <description>The latest articles on DEV Community by shekhar chandran (@aiinsider).</description>
    <link>https://dev.to/aiinsider</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%2F4122254%2Fbf9efa9e-ed29-4fa5-addd-6799cdd8f224.png</url>
      <title>DEV Community: shekhar chandran</title>
      <link>https://dev.to/aiinsider</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aiinsider"/>
    <language>en</language>
    <item>
      <title>My Copy Buttons Vanished on Production. The Bug Was Three WordPress Layers Deep</title>
      <dc:creator>shekhar chandran</dc:creator>
      <pubDate>Sat, 12 Sep 2026 14:59:00 +0000</pubDate>
      <link>https://dev.to/aiinsider/my-copy-buttons-vanished-on-production-the-bug-was-three-wordpress-layers-deep-203j</link>
      <guid>https://dev.to/aiinsider/my-copy-buttons-vanished-on-production-the-bug-was-three-wordpress-layers-deep-203j</guid>
      <description>&lt;p&gt;I shipped a small feature: 49 "📋 Copy Prompt" buttons on a prompt-library page, each with an inline &lt;code&gt;onclick&lt;/code&gt; that copies text to the clipboard. Worked perfectly in preview. Went live. Every single button vanished.&lt;/p&gt;

&lt;p&gt;Not broken — &lt;em&gt;gone&lt;/em&gt;. Not in the DOM. Not in "view source." Just... not there.&lt;/p&gt;

&lt;p&gt;Here's how I traced it through three separate, unrelated bugs stacked on top of each other, and what I learned about WordPress content filters and caching layers that I wish I'd known before shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom #1: things were also just... centered wrong
&lt;/h2&gt;

&lt;p&gt;Before I even got to the missing buttons, screenshots came in showing paragraphs and prompt boxes that should've been left-aligned rendering centered or oddly indented on certain line wraps. That one turned out to be simple: several wrapper divs and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;h4&amp;gt;&lt;/code&gt; styles had no explicit &lt;code&gt;text-align&lt;/code&gt;, and something in the theme/page-builder CSS cascade was centering them under specific conditions. Fix: add explicit &lt;code&gt;text-align:left&lt;/code&gt; inline styles everywhere it mattered. Not interesting on its own — but it meant I almost stopped looking once I fixed the "obvious" visual bug, instead of noticing the buttons were missing entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom #2: the buttons weren't just styled wrong, they didn't exist
&lt;/h2&gt;

&lt;p&gt;This is the one that took real digging. My working theory list, in order of how wrong each one was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache serving stale content → ruled out (forced no-cache fetch, same result)&lt;/li&gt;
&lt;li&gt;CSS &lt;code&gt;display:none&lt;/code&gt; somewhere → ruled out (not in computed styles, because the elements weren't in the DOM at all)&lt;/li&gt;
&lt;li&gt;A plugin stripping &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; tags → seemed unlikely but worth checking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The technique that actually cracked it was comparing the &lt;strong&gt;same content at three different layers&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What's stored in the database&lt;/strong&gt; — clean, correct HTML, buttons present, &lt;code&gt;onclick&lt;/code&gt; intact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's actually served over HTTP&lt;/strong&gt; — fetched the live URL directly (bypassing any client-side rendering) and diffed it against #1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What the browser's DOM ends up with&lt;/strong&gt; — &lt;code&gt;document.querySelectorAll('button')&lt;/code&gt; in devtools.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Layer 1 was fine. Layer 2 was corrupted. That narrowed it to something between "database" and "the HTTP response" — i.e., a content filter running at render time, not anything client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root cause: WordPress's own &lt;code&gt;wptexturize()&lt;/code&gt; was corrupting my HTML attributes
&lt;/h2&gt;

&lt;p&gt;WordPress runs &lt;code&gt;wptexturize()&lt;/code&gt; on &lt;code&gt;the_content&lt;/code&gt; at render time — it's the filter that turns straight quotes into "smart" curly quotes for readability in plain prose. The problem: it doesn't know the difference between quote characters in your visible text and quote characters inside an HTML attribute.&lt;/p&gt;

&lt;p&gt;My button markup looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"navigator.clipboard.writeText('...');this.textContent='✓ Copied';"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  📋 Copy Prompt
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;wptexturize()&lt;/code&gt; converted some of the straight &lt;code&gt;'&lt;/code&gt; characters inside that &lt;code&gt;onclick&lt;/code&gt; string into curly-quote HTML entities (&lt;code&gt;&amp;amp;#8217;&lt;/code&gt;). That meant the &lt;em&gt;served&lt;/em&gt; HTML had no real closing quote for the attribute value anymore. The browser's HTML parser, following spec, treated the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; tag as an attribute that never terminated — and silently swallowed everything after it into that one unclosed tag, until the next literal &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; string in the markup forced it closed. 49 buttons, cascading into each other, all eaten by the parser. That's why they weren't just mis-styled — they'd never actually existed as separate elements in the parsed DOM at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; never put inline &lt;code&gt;onXXX="..."&lt;/code&gt; attributes with string literals containing quote characters into WordPress post/page content. I replaced all 49 buttons with a plain class and moved the behavior into one delegated listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"copy-btn"&lt;/span&gt; &lt;span class="na"&gt;data-text=&lt;/span&gt;&lt;span class="s"&gt;"the actual prompt text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  📋 Copy Prompt
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;data-no-optimize=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;data-cfasync=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.copy-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✓ Copied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bug #3: the fix... also didn't work at first
&lt;/h2&gt;

&lt;p&gt;Buttons were back in the DOM. Great. Except the click handler still didn't fire. Same three-layer trick: DB content had the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block, but the served HTML didn't.&lt;/p&gt;

&lt;p&gt;This time it wasn't &lt;code&gt;wptexturize&lt;/code&gt; — it was LiteSpeed Cache's JS minify/combine optimizer silently stripping the inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block during minification. Confirmed via response headers: &lt;code&gt;x-litespeed-cache: miss&lt;/code&gt; on a forced fresh render (so it wasn't a stale-cache problem) but &lt;code&gt;x-litespeed-tag&lt;/code&gt; showed an active JS-minify hash — meaning the optimizer was actively processing and dropping the block, not just caching it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; LiteSpeed Cache respects a documented exclusion convention — add &lt;code&gt;data-no-optimize="1" data-cfasync="false"&lt;/code&gt; to any inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; you need untouched (already in the snippet above). Script showed up in the served HTML immediately after that, and the click handler worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways if you're shipping anything similar on WordPress + LiteSpeed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never use inline &lt;code&gt;onXXX&lt;/code&gt; attributes with quoted string literals in post/page content.&lt;/strong&gt; &lt;code&gt;wptexturize()&lt;/code&gt; runs on &lt;code&gt;the_content&lt;/code&gt; and can corrupt quote characters inside them without warning — it only cares about "readable prose," not your JS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer &lt;code&gt;class&lt;/code&gt; + a single delegated event listener&lt;/strong&gt; over per-element inline handlers. Cleaner, and immune to the above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; in post content on a LiteSpeed-cached WordPress site needs &lt;code&gt;data-no-optimize="1"&lt;/code&gt;&lt;/strong&gt;, or the JS minifier can silently strip it — no error, no warning, it's just gone from the served page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When something works in the database/editor but not live, diff three layers&lt;/strong&gt;: stored content → raw served HTTP response → parsed DOM. Whichever layer first shows the corruption tells you exactly which system to blame, instead of guessing across the whole stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixing the obvious visual bug can make you stop looking too early.&lt;/strong&gt; The centering issue was real, but it wasn't the actual story — always check for a second, quieter bug hiding behind the loud one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ran into this while rebuilding a prompt-library page over on &lt;a href="https://aiinsider.in/ai-prompts/" rel="noopener noreferrer"&gt;AIInsider.in&lt;/a&gt; — if you're curious what the finished (bug-free) version looks like, that's it in the wild.&lt;/p&gt;

&lt;p&gt;Has anyone else been bitten by &lt;code&gt;wptexturize&lt;/code&gt; mangling inline JS? Curious how common this actually is outside my one site.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ai</category>
      <category>webdev</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
