DEV Community

shekhar chandran
shekhar chandran

Posted on Originally published at aiinsider.in

My Copy Buttons Vanished on Production. The Bug Was Three WordPress Layers Deep

I shipped a small feature: 49 "šŸ“‹ Copy Prompt" buttons on a prompt-library page, each with an inline onclick that copies text to the clipboard. Worked perfectly in preview. Went live. Every single button vanished.

Not broken — gone. Not in the DOM. Not in "view source." Just... not there.

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.

Symptom #1: things were also just... centered wrong

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

/

styles had no explicit text-align, and something in the theme/page-builder CSS cascade was centering them under specific conditions. Fix: add explicit text-align:left 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.

Symptom #2: the buttons weren't just styled wrong, they didn't exist

This is the one that took real digging. My working theory list, in order of how wrong each one was:

  • Cache serving stale content → ruled out (forced no-cache fetch, same result)
  • CSS display:none somewhere → ruled out (not in computed styles, because the elements weren't in the DOM at all)
  • A plugin stripping

The technique that actually cracked it was comparing the same content at three different layers:

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

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.

Root cause: WordPress's own wptexturize() was corrupting my HTML attributes

WordPress runs wptexturize() on the_content 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.

My button markup looked like this:

 onclick="navigator.clipboard.writeText('...');this.textContent='āœ“ Copied';">
  šŸ“‹ Copy Prompt

Enter fullscreen mode Exit fullscreen mode

wptexturize() converted some of the straight ' characters inside that onclick string into curly-quote HTML entities (’). That meant the served HTML had no real closing quote for the attribute value anymore. The browser's HTML parser, following spec, treated the

Enter fullscreen mode Exit fullscreen mode

Bug #3: the fix... also didn't work at first

Buttons were back in the DOM. Great. Except the click handler still didn't fire. Same three-layer trick: DB content had the