Skip to content

Good bones and road grit.

Roadside Archivist

Posts by this author.

Roadside Archivist Avatar

  • The apostrophe after the tag

    The apostrophe after the tag

    There is a WordPress bug small enough to hide in a single character.

    Get this markup in the editor by writing out the HTML with the “Edit as HTML” inline block tool:

    <strong>He</strong>'s here.

    You can also type it in the visual editor with the keyboard shortcuts and inline block tools — and it will look OK in the editor, with a purely vertical apostrophe that’s not curled one way or the other.

    But on the published post the world sees, the apostrophe curls the wrong way — away from the ‘e’ it belongs to and out toward the ‘s’.

    You wanted a contraction. The apostrophe belongs to the word ‘he’. Unpatched, WordPress renders it as an opening single quotation mark instead. Here is the before, exactly as a stock install curls it:

    He‘s here.

    That little curl points the wrong way. It should be &#8217;, the right single quotation mark — the character English uses as a typographic apostrophe in contractions and possessives. Here is the after, the way a corrected wptexturize() renders the very same markup:

    He’s here.
    Garage workbench with HTML markup and a curled metal apostrophe-shaped shaving beside an out-of-focus laptop.
    Generated editorial photo for “The apostrophe after the tag.”

    Both lines above are typed straight into the page by hand, so they hold still as a reference no matter what runs after them. The whole bug lives in the gap between them: one word, the same markup, a single character apart.

    This is not the block editor inventing punctuation. It is older than that. The culprit is wptexturize(), WordPress’s long-running output filter that turns straight quotes, dashes, ellipses, and a few other plain-text marks into nicer typography on the rendered page.

    Most of the time, that is what people expect from WordPress. You type plain punctuation. WordPress dresses it up. The trouble starts when the word is split by HTML.

    The word got cut in half

    wptexturize() does not look at rendered text the way a reader does. It works through the HTML string. To avoid changing things inside tags, it splits the string around markup.

    So this:

    <strong>He</strong>'s here.

    starts to look more like this internally:

    <strong> | He | </strong> | 's here.

    The final piece starts with 's. Since the filter can no longer see the e in He, it treats the straight quote as if it begins a quotation. The visible word says He's. The string pieces say: tag, text, tag, quote at the beginning of a new run.

    That is the whole bug: the word context fell through the tag boundary.

    Dan wrote about this years ago in Apostrophes and Quotation Marks. That post has the useful minimal shape:

    <strong>Test</strong>'s
    <em>Test</em>'s

    One snippet in the article appears to have a small typo — <strong>Test<strong>'s is missing the slash in the closing </strong> tag — but the point is right. A closing inline tag followed immediately by a straight apostrophe is enough to confuse the old text filter.

    The whole family of failures

    It is not only contractions. Any straight quote that lands right after a closing inline tag can take the wrong curl. These are the shapes people have actually filed since the original 2011 report — single and double closing quotes, possessives, quoted names, and quotes that bump into a block tag, an ellipsis, or non-English punctuation. The first column is what you write; the last two are typed by hand so they hold still:

    The shapeYou writeRenders nowShould be
    Possessive after a tag<strong>He</strong>'sHe‘sHe’s
    Contraction split by a tagrock</strong>'n'rollrock‘n’rollrock’n’roll
    Closing single quote after a link'<a>quoted</a>'.‘quoted‘.‘quoted’.
    Closing double quote after a link"<a>quoted</a>".“quoted“.“quoted”.
    Possessive after a quoted name<em>"John"</em>'s“John”‘s“John”’s
    Closing quote before a block tag"<a>else</a>"</p>“else““else”
    Closing quote before an ellipsis"<a>link</a>"…“link“…“link”…
    Closing quote before CJK punctuation"<em>引用</em>"。“引用“。“引用”。

    A few cousins are genuinely harder and stay out of scope: an opening quote right after CJK text, and elisions where the apostrophe sits before or inside the tag — <strong>l</strong>'homme, <strong>O</strong>'Neill, d<em>'</em>accord. Those are a different boundary problem, noted on the ticket but not fixed by the same rule.

    Why this belongs upstream

    Dirtbag could try to be clever here. It could disable wptexturize() entirely:

    add_filter( 'run_wptexturize', '__return_false' );

    That would be too much. It would change more than apostrophes: quotes, dashes, ellipses, primes, and old WordPress behavior people may rely on. A theme should not make that decision for a site.

    Dirtbag could also patch the rendered content after the fact, flipping &#8216; to &#8217; after certain inline closing tags. That is less blunt, but still a theme pretending to be a typography engine.

    The right place is WordPress core. There is already an old ticket for the broader bug: Core Trac #18549, “wp_texturize incorrectly curls closing quotes after inline HTML end tags.” The ticket has a long history, patches, duplicate reports, and the correct diagnosis: wptexturize() loses quote context around inline HTML.

    The modern block-editor version of the report showed up again as Gutenberg #42345. It was closed there for the right reason. Gutenberg cannot fix an output filter that lives in core.

    So now we’ve finally caught up with that old issue, and the LLMs are good enough to sort out such a thorny logic puzzle.

    The useful test

    The smallest issue revival comment is not another essay-length comment. It is a test.

    /**
     * @ticket 18549
     */
    public function test_apostrophe_after_inline_formatting_tag() {
        $this->assertSame(
            '<strong>He</strong>&#8217;s here.',
            wptexturize( "<strong>He</strong>'s here." )
        );
    }

    Then add the neighboring cases:

    <em>It</em>'s fine.
    <a href="#">Dan</a>'s truck
    <strong>rock</strong>'n'roll

    And do not overcorrect these:

    <strong>Note:</strong> 'quoted text'
    <strong>He said</strong> 'go'

    That distinction matters. When a closing inline tag is immediately followed by an apostrophe and a letter or number, and the visible text before the tag ended with a letter or number, it is probably a contraction or possessive. When there is a space after the tag, normal opening-quote behavior should still be possible.

    The Dirtbag lesson

    This is the same kind of bug as the floated-title problem: one default, one tiny inherited behavior, one layout or text result that looks supernatural until the small rule is visible.

    A theme like Dirtbag is supposed to stay close to WordPress. That is the bargain. Use core blocks. Use plain HTML. Avoid clever replacements. But staying close also means you get close enough to see the burrs.

    This apostrophe is one of them. Not dramatic. Not a crisis. Just a small wrong curl after a tag.

    Small wrong things are still wrong.

  • We blamed the browser

    We blamed the browser

    The sidebar wanted one small thing: a thumbnail on the left with the headline tucked in beside it and the blurb wrapping underneath, the way a magazine sets a column. There is one old, reliable CSS tool for making text wrap under an element: the float. So we floated the thumbnail and moved on.

    It worked. Then it didn’t. In one person’s Chrome, the lower items dropped their headlines below the thumbnail instead of beside it. Not everywhere. Not in Safari. Not in the screenshots our tooling takes. Just there, on that screen, near the bottom of the list.

    That is the worst kind of bug: the one that only shows up in the room you’re not standing in.

    A list of things that were not the problem

    We are not too proud to show the wrong turns, because the wrong turns are the story.

    The cache. First reflex, always. View-source said the stylesheet was correct and current. Hard refresh, no change. Not the cache.

    An extension. Browsers are full of helpful little programs that rewrite the page behind your back. We opened a clean Incognito window. Same break. Not an extension.

    The lazy image. Modern browsers wait to load images that are off-screen, and a thumbnail that loads late could, in theory, knock the layout around. Plausible. We floated an empty box with no image in it at all. It broke exactly the same way. Not the image.

    The size of the box. The thumbnail was sized in a way that could land on a fractional pixel. We pinned it to a round number. Still broke. Not the box.

    The screen itself. The break only happened on a laptop set to a scaled, high-readability resolution, and was fine on an external monitor — so surely it was the odd pixel ratio of that display. We checked the number the browser reports. It was a clean, ordinary 2. Not the screen.

    The fancy grid. The two front-page columns are lined up with a newer CSS feature called subgrid. Exotic features are usually the first thing to suspect. We tore it out. Still broke. Not the grid.

    By now we had a confident, well-documented theory: a browser bug. The float, deep in a tall narrow column, simply wasn’t being laid out correctly until something forced a redo — and scrolling fixed it, which is exactly how those engine-level glitches behave. We wrote it all down, shipped the boring layout that didn’t use a float, and tipped our hat to a Chrome bug we couldn’t beat.

    We were wrong. Comfortably, thoroughly wrong.

    The smaller you make it, the louder it gets

    The only way to accuse a browser and mean it is to reproduce the problem with nothing else in the room. No WordPress, no theme, no plugins — a single HTML file with a float and some text, and a way to flip one variable at a time.

    So we built that page. A floated box, several stacked items, a tall narrow window, and a switch for each suspect: with the grid and without, with the image and without, real float versus empty box. We added one more switch almost as an afterthought — a switch that turned a plain text link into the exact kind of link WordPress wraps a post title in.

    Every other switch did nothing. That last one did everything.

    With the WordPress-style link, every item stacked. Flip it back to a plain link, and every item wrapped perfectly. The browser had been telling the truth the whole time.

    The line that did it

    WordPress styles the link around a post title as display: inline-block. That sounds like a detail, and it is, and it is also the entire bug.

    A normal run of text breaks across lines wherever it must. An inline-block won’t: it is a single atomic tile. Its contents can wrap inside the tile, but the tile itself can’t be split — it can’t take the narrow line beside the float and finish on the full line below. So once it is wider than the gap beside the float, it abandons that gap and drops whole to the next clear line, under the float.

    Which titles are widest? The long ones. Which items had the long titles? The ones lower in the list. Narrow the window and the gap beside the float shrinks, so more titles tip over, from the bottom up. Every spooky symptom we’d catalogued — lower items, narrow screens, breaks from the bottom — was just a long unbreakable title running out of room. Nothing was below the fold. And once the inline-block was in plain sight, none of it needed a browser-engine theory at all.

    The fix is one line. Tell the title link, in this one spot, to behave like normal text again:

    .sidebar-content:not(.is-grid) .sidebar-entry > .wp-block-post-title :where(a) {
      display: inline;
    }

    The float has worked on every screen since.

    That inline-block isn’t really ours to fix, though — it ships with WordPress itself, on every post-title link. So we filed it upstream (WordPress/gutenberg #79372) and asked the obvious question: is the atomic box load-bearing, or would plain inline do the job? Until that’s answered, the one-line override is the honest patch.

    What we actually learned

    We kept both layouts — the magazine float and the plain grid — behind a single class: drop is-grid on the sidebar and it swaps to the boxy, beside-not-under version. Pick the look; the wrap-under is no longer a gamble.

    But the layout is the souvenir, not the lesson. The lesson is older than CSS.

    We spent days interrogating everything around the problem — the cache, the screen, the browser, the network — because those are the suspects you can blame without admitting the call is coming from inside your own stylesheet. The browser was the convenient villain precisely because it was the one thing we couldn’t open up and read.

    The minimal reproduction is the boring, unglamorous move that ends these arguments. You strip the problem down until there is nowhere left for it to hide, and somewhere in the stripping the real cause stops being a theory and starts being a line number. We should have built the small page on day one. We built it on day five, and it answered in about a minute.

    Blame the browser last. It is almost never the browser. It was a coat of paint we’d inherited and never thought to scrape.

  • The price of a pun

    The price of a pun

    Every post on this site is signed the same way: From [author]’s dashboard. It is a small joke. A dashboard is where you keep your hands at ten and two, and it is also where WordPress keeps its knobs. The byline drives both meanings at once. Cheap pun, paid for once, runs forever.

    Except it didn’t run. On a narrow screen the byline kept throwing a rod.

    The apostrophe that wouldn’t stay in its lane

    Read it back: From Roadside Archivist’s dashboard. Three pieces — the word “From,” the author’s name, and the suffix “‘s dashboard.” On a wide screen they sit in a row like they should. Shrink the window and the last piece would snap off and drop to the next line, stranding an orphaned ‘s dashboard under a name it no longer belonged to.

    A possessive that loses its noun is not a typo. It is worse. It reads like the page forgot how grammar works.

    So either the joke had to go, or the apostrophe had to learn to stay glued to the name.

    How you glue an apostrophe to a name

    The byline is a flexbox — a little horizontal rack the three pieces ride in. Two settings hold it together.

    First, flex-wrap: nowrap: the three pieces are forbidden from breaking apart from each other. They travel as one unit or not at all.

    Second, a single line in theme.json: .byline { flex-shrink: 0 }. That tells the layout the byline will not be squeezed thinner to fit beside the date. When it doesn’t fit, the whole byline drops to its own line — intact — instead of shedding its tail to make room.

    There is a third thing, and it is a quirk, not a setting. Flexbox quietly eats the whitespace between its items. So the gap that would normally sit between the name and the “‘s” — the gap that let it break in the first place — simply isn’t rendered. The apostrophe ends up flush against the last letter of the name, like it was never two separate pieces of markup at all.

    Three small moves, and Archivist’s holds together down to the phone.

    What the joke costs

    Honesty time, because that is the house style.

    The byline is a split string. The translator gets “From” and “‘s dashboard” as two separate scraps wrapped around a name they never see. A language that builds possessives differently — or builds sentences in a different order — can’t just swap the words. It has to rephrase around a hole.

    It is also a CSS escape. Dirtbag keeps almost no hand-written CSS, and this spends two of the precious lines, both with !important on them — the layout equivalent of raising your voice.

    And it leans on that flexbox whitespace quirk, which is real and well-defined but is still a behavior, not a promise. Push the screen below about 340 pixels with a long enough name and the unbreakable byline will run off the edge rather than wrap. A short name never notices. A long one on a tiny phone does.

    None of it is free. All of it is for a pun.

    Is it defensible?

    Barely. And that is the honest answer, not a dodge.

    It earns the word defensible because it never leaves the property. No JavaScript. No functions.php. No plugin. Just a core block, a translatable pattern, and one small rule in the file where the theme already keeps its design. By Dirtbag’s own rules, it is in bounds.

    But it is the most indulgent thing in the whole theme. The cheaper build was sitting right there the entire time: By Roadside Archivist. Three words, no apostrophe, no split string, no flexbox sermon, translatable in any language without a fight. Every engineering instinct says ship that one.

    We didn’t. We kept the pun, wrote down exactly what it cost, and paid it on purpose.

    The thing under the thing

    Discipline is not never indulging. A site with no indulgences has no voice; it is a spec sheet with a domain name.

    Discipline is knowing the bill before you order. The trouble is the indulgence you take without pricing it — the framework you add because everyone does, the clever thing you can’t explain a month later, the dependency nobody remembers signing for. Those are the ones that total the truck.

    A possessive apostrophe held in place by a flexbox quirk is a flourish. But it is a measured one. We can tell you what it costs, where it breaks, and what the boring alternative would have been. That is the whole difference between a theme with character and a theme that just got away from somebody.

    Keep the joke. Keep the receipt.

  • The build sheet

    The build sheet

    Pop the hood on a block theme and one part is doing most of the steering: theme.json. It is the build sheet — the page that lists which options the theme came with, what is standard, and what the factory will not let you order. Dirtbag’s build sheet is tiny, about 8.7 KB, because most of it says no.

    That is not laziness. It is the whole idea.

    Two columns on the sheet

    There are only two things on a build sheet worth reading.

    The first is settings: what the factory will let you order. These are the controls the editor shows you — the color pickers, the font menus, the toggles.

    The second is styles: what actually rolled off the line. These are the numbers the theme commits to — the type sizes, the spacing, the heading scale.

    Settings is the menu. Styles is the plate. Read them in that order and the whole file makes sense.

    What you can’t order

    Most of Dirtbag’s settings are switched off, and that is the point.

    The bundled design tools are off. The color section turns off the custom picker, the gradients, the duotones, and ships a fixed palette of nineteen named colors — black, white, a row of greys, and the primaries that came packed in with HTML itself. No wheel, no hex field, no “brand purple #6B21A8.” If you have ever run View Source on a 1998 homepage, you already know this palette.

    The fonts are the same story: thirteen web-safe stacks, Arial through Verdana, all of them already on the machine. Nothing gets downloaded. Nothing phones home. The page renders the same whether the network is fast, slow, or on fire in a ditch.

    Shadows: off. Fluid type: off. A short menu is a kind of honesty. It tells you what the theme is for before you waste an afternoon finding out what it is not.

    What’s standard

    The other column is just plain numbers, written down where you can see them.

    Six type sizes, fifteen pixels up to forty-eight. A heading scale that steps down by level — 36, 28, 26, 20, 18, 15 — so an h2 is a smaller voice than an h1 and the screen reader and the eye agree on the outline. Body text at eighteen pixels with a 1.4 line height. A measure of about 42rem, because a line you can actually finish reading is worth more than a line that fills the window. Block gap at 1.4em. Square buttons. Underlined links.

    No cleverness. No per-block paint job. Just the spec, stated once, applied everywhere.

    The one drawer with a wrench in it

    Every honest toolbox has the drawer where the real wrench lives, and Dirtbag’s is a small block of hand-written CSS inside theme.json. Three rules, no more:

    One collapses the gap in a byline so the author and date read as a single line. One recolors the monochrome truck logo per style variation. And the newest one lines up the two columns on the front page so the sidebar’s posts don’t drift a line below the grid when a heading wraps.

    That is the whole drawer. The rule is simple: if you keep an exception, you write it down. Exceptions that live in the dark breed. Three rules in a list, each with a reason, is a thing you can audit on a slow afternoon. A stylesheet nobody remembers writing is how a small theme quietly becomes a big one.

    Why it fits on one page

    A theme.json gets fat when it tries to style every block — a rule here, an override there, a special case for the thing that looked wrong on one screen in one browser one Tuesday.

    Dirtbag’s stays thin because it does the opposite. It decides what not to allow, writes down a short list of numbers, and keeps three honest exceptions. Then it gets out of the way. The browser and WordPress core already know how to render a document.

    The build sheet fits on one page because the truck does not have many options. On purpose. Constrain, then declare. That is the whole theme, and it turns out you can read it in about eight kilobytes.

  • What the shoulder taught us

    What the shoulder taught us

    The first rule of Dirtbag was simple enough to write on the back of a gas receipt:

    Keep it small.

    No build step. No theme JavaScript. No stylesheet file pretending to be a lifestyle brand. No remote font with a passport. Plain pages, visible links, feeds, comments, and enough road grit to make the thing feel owned by a person instead of a launch committee.

    Then the theme went out for a drive and came back with better rules.

    1. Slogans leak

    “No JS” sounds clean until you look at an actual WordPress page. Core blocks are not cardboard cutouts. Navigation has to open. An overlay has to close. A lightbox has to trap attention without trapping people. Enhanced pagination still has to be pagination when the enhancement quits.

    The old slogan was pointing in the right direction, but it was too austere for the machinery underneath it.

    It’s amazing how much you can do without JS or CSS in a WordPress theme — without bringing in any extra CSS and JS in the time, that is. There’s no getting around either when you’re building with WordPress, but you’ll learn a lot if try to strip down to the simplest starting point.

    The honest truth is duller and stronger, but it’s not great for slogans: Dirtbag does not add its own front-end JavaScript dependency. Dirtbag does not enqueue its own stylesheet file. WordPress core may still bring CSS and JavaScript when core blocks need them. The browser may still do browser work. The document still has to stand when the extras fall away.

    That sentence will not fit on a bumpsticker, sadly.

    2. Core has weight

    Using core is not the same as using nothing.

    theme.json becomes CSS. Core block support becomes markup and styles. The Interactivity API is JavaScript, even when it arrives as an OEM part from WordPress instead of an aftermarket box from the parts store.

    That does not make it bad. It makes it reliable.

    The lesson is not “never use it.” The lesson is “name the owner.” If WordPress core owns the behavior and the fallback is a plain link, button, image, or document, the part might earn its keep. If Dirtbag has to import a second runtime, invent a private convention, or explain why the page is useless until hydration, the truck is overloaded.

    3. Exports lie in small ways

    The Site Editor is useful, but it is far from perfect. Exported templates can come back with local URLs, hard-coded theme slugs, or odd block bindings stuck to the bumper. Seed content can carry yesterday’s idea after today’s philosophy changed.

    So we learned to treat exports like parts from a swap meet: inspect them before installation.

    The package check is not glamour work. It looks for stale local addresses, broken block comments, bad nesting, and things that should have been cleaned before release. That is exactly the kind of boring tool a small theme needs.

    Not a build system. Not a dashboard. A flashlight!

    4. Accessibility is where philosophy pays rent

    A plain theme can still fail people.

    A caption can sit in a scrollable region that keyboard users cannot reach. A menu can look simple and still mishandle focus. A clever visual shortcut can flatten the document for somebody reading it a different way.

    That is why “we did not write the JavaScript” is not a release gate. It is trivia. The gate is whether the page works with a keyboard, a screen reader, a small viewport, slow patience, and scripts that may or may not arrive.

    Plain is not automatically accessible. Plain is just easier to audit when you have not buried the page under theater.

    5. The page is the product

    The best thing we learned is also the oldest thing on the web: the page is already the product.

    Not the framework. Not the tooling story. Not the perfect screenshot. Not the promise that a future version might carry a shinier library if the vibes are right.

    A page with a title, a date, a byline, some links, a feed, comments, and honest markup is not a fallback. It is the thing.

    Everything else is a part in the glovebox. Useful sometimes. Dead weight often. Worth carrying only when it gets the page farther down the road without leaving anybody in the ditch.