Skip to content

fix: render Djot in feeds instead of shipping the raw source - #58

Open
dereuromark wants to merge 2 commits into
mainfrom
feed-renders-djot
Open

dereuromark wants to merge 2 commits into
mainfrom
feed-renders-djot

Conversation

@dereuromark

@dereuromark dereuromark commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #57.

shouldFilterContent() returned false for is_feed(), so Djot was never rendered in RSS or Atom and the subscriber received the markup - headings as # , emphasis as asterisks, definition lists as their markers.

Worse, the fallback was not even faithful. Because nothing rendered the string it reached wptexturize, which curls the fence characters: the opening backtick run arrived as a left double quote followed by a backtick. Six curly quotes where the source had none. So the reader was not getting plain source, they were getting corrupted source - in the one place they cannot click through to see the real page.

The feed context

Feeds render now, through a 'feed' context that differs from 'article' only in what it leaves out.

Two of those omissions are free: the table of contents and the heading permalinks are already gated on 'article', and both are meaningless in a reader - a TOC is anchors into a page the reader is not on, and a permalink is a hover affordance needing CSS a feed does not carry.

Mermaid is the third, and it is a smaller point than an earlier draft of this description claimed. It is worth being accurate about: the container already carries the source as visible text, so nothing is lost either way.

with extension : <pre class="mermaid">graph TD; A-->B</pre>
without        : <pre><code class="language-mermaid">graph TD; A--&gt;B </code></pre>

Two modest reasons to keep the gate rather than drop it. The code-block form declares what the block is, so a reader that styles code styles it. And the interactive form leaves --> unescaped inside the element, which is an HTML comment closer that a feed sanitizer may treat unpredictably; the fallback escapes it.

Not a rescue, then - a tidier degradation. The rendering fix above is the substance.

Why only Mermaid

Because it is the only client-rendered fence djot-php ships. Checked rather than assumed: the extension list has no chart, vega-lite, graphviz, d2, plantuml or wavedrom equivalent.

The other JS-adjacent constructs this plugin registers do not need a gate either. Tabs and CodeGroup use a CSS-only radio technique - no script, no hidden, no inline display: none - so without a stylesheet a feed shows every panel rather than hiding all but one. Verbose, but nothing is lost, which is the bar here.

Verified on a running site

before after
feed renders HTML no yes
raw # markers in feed present gone
curly quotes (texturize damage) 6 0
mermaid in feed n/a (unrendered) code block
mermaid on the post's own page container container (unchanged)

convertArticle and the new convertFeedOrArticle share one body, so the two contexts cannot drift apart.

phpunit, phpstan and phpcs green.

shouldFilterContent() returned false for is_feed(), so Djot was never rendered
in RSS or Atom and the subscriber received the markup: headings as `# `,
emphasis as asterisks, definition lists as their markers.

Worse, the fallback was not even faithful. Because nothing rendered the string
it reached wptexturize, which curls the fence characters - the opening backtick
run arrived as a left double quote followed by a backtick. Measured on a live
feed: six curly quotes where the source had none. So the reader was not getting
plain source, they were getting corrupted source, in the one place they cannot
click through to see the real page.

Feeds are rendered now, through a 'feed' context that differs from 'article'
only in what it leaves out. Two of those omissions are free: the table of
contents and the heading permalinks are already gated on 'article', and a table
of contents is a list of anchors into a page the reader is not on while a
permalink is a hover affordance needing CSS a feed does not carry.

The third is Mermaid, which needed a gate of its own. Its extension emits a
container for a client script to replace, and no script runs in a reader, so
the diagram would arrive as unformatted text with no explanation. Left
unregistered, the same fence falls back to a plain code block. Verified on a
live feed: rendered HTML, no raw markers, zero curly quotes, and the diagram as
a code block - while the post's own page keeps its container.

convertArticle and the new convertFeedOrArticle share one body so the contexts
cannot drift apart.
Copilot AI lite review requested due to automatic review settings August 27, 2026 23:16
@dereuromark dereuromark added the bug Something isn't working label Aug 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates WP-Djot’s content filtering/conversion pipeline so Djot is rendered in RSS/Atom feeds instead of being shipped as raw (and wptexturize-corrupted) source, by introducing a feed-aware rendering context that omits reader-unfriendly features (TOC, heading permalinks, Mermaid hydration).

Changes:

  • Route full post content and {djot}...{/djot} block conversions through a new convertFeedOrArticle() path.
  • Stop excluding feeds in shouldFilterContent() so feed requests get Djot rendering.
  • Add a 'feed' context to converter configuration and disable Mermaid extension for that context (to fall back to readable code blocks).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Plugin.php Enables Djot filtering in feeds and uses the feed-aware conversion entry point for post content processing.
src/Converter.php Introduces a feed context and a convertFeedOrArticle() helper; gates Mermaid extension (and related cache behavior) based on context.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Converter.php
Comment on lines 191 to 193
$roundTripKey = $roundTripMode ? '_rt' : '';
$key = $profileName . ($safeMode ? '_safe' : '_unsafe') . '_' . $softBreakSetting . ($this->markdownMode ? '_md' : '') . $tocKey . $permalinksKey . $smartQuotesKey . $headingShiftKey . $mermaidKey . $roundTripKey;

Comment thread src/Converter.php
Comment on lines +412 to +415
public function convertFeedOrArticle(string $djot): string
{
return $this->convertInContext($djot, is_feed() ? 'feed' : 'article');
}
The comment claimed the diagram would arrive with no formatting and no
explanation. Measured, the interactive container already carries the source as
visible text, so nothing is lost either way - the difference is which element
wraps it.

The gate is still worth keeping: the fallback declares the block is code, and
it escapes the arrow, which sits unescaped inside the interactive form and is
an HTML comment closer for whatever sanitizer a feed reader runs. But that is a
tidier degradation, not a rescue, and the comment should not oversell it.

The rendering fix this branch is really about - feeds got raw, texturize-
corrupted source - is unaffected.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Converter.php:401

  • convertFeedOrArticle() is only used from Plugin, but other render entry points (e.g. Blocks/DjotBlock.php, Shortcodes/DjotShortcode.php, src/functions.php) still call convertArticle(). In a feed request those paths will continue to render with the article context (TOC/permalinks/Mermaid container), so the new feed-specific omissions won’t consistently apply. Consider making convertArticle() automatically pick the feed context when is_feed() is true, and keep convertFeedOrArticle() as an alias to avoid missing call sites.
    public function convertArticle(string $djot): string
    {
        return $this->convertInContext($djot, 'article');
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feeds get raw, texturize-corrupted Djot source instead of rendered content

2 participants