[3.x] Render fenced code blocks through a composable Blade view - #2577
Open
emmadesilva wants to merge 15 commits into
Open
[3.x] Render fenced code blocks through a composable Blade view#2577emmadesilva wants to merge 15 commits into
emmadesilva wants to merge 15 commits into
Conversation
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 01:54 — with
GitHub Actions
Inactive
The terminal block transformer owned the info string token grammar, but the same grammar is about to back code block titles. Moving it into a trait keeps one parser, one error message shape, and one place to change when a modifier is added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code blocks were the one Markdown construct still assembled by string surgery: a pre-processor rewrote filepath comments into HTML markers, and a post-processor spliced the rendered label back into the finished HTML, which meant the label could only ever be a fragment wedged inside <code>. They now go through the same pipeline as terminal blocks. A CommonMark extension prepares each fenced node and a renderer hands a typed view model to components/markdown/code-block.blade.php, so the whole block is the view's to shape, and future chrome has somewhere to live. The renderer delegates the highlighted markup to the renderers below it, which keeps Torchlight in charge of highlighting when it is enabled, and the listener runs before Torchlight collects its blocks so the filepath comment is stripped from the code it sends off. Labels can now also be set with a title="..." modifier, matching terminal blocks. The attribute is the explicit form, so it wins when a block sets both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label is now positioned against the block wrapper instead of floating inside the code element, which needs a utility the bundled stylesheet had never been asked for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emmadesilva
force-pushed
the
v3/composable-code-blocks
branch
from
August 1, 2026 01:56
fcfd68d to
0c65cff
Compare
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 01:56 — with
GitHub Actions
Inactive
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 01:56 — with
GitHub Actions
Inactive
The page described filepath labels as a fragment spliced into finished HTML by a post-processor, which is no longer how they get there. Code blocks now have their own section covering both label syntaxes, their precedence, the view contract, and the class hooks, and the pipeline and limitations sections are corrected to match. The documentation driven tests follow the page as before, so every claim the new section makes is asserted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The advanced Markdown page is where authors look up how to write these blocks, so it gets the second label syntax, the precedence rule, and a pointer to the views now that the whole block is publishable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Includes the motivation section, since the two decisions most likely to be questioned later are why the view is handed finished markup instead of the raw code, and why the node stays a FencedCode. Both come down to leaving Torchlight in charge of highlighting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The markup change is the part that reaches sites which never touched the feature, so the upgrade guide leads with a before and after of the HTML and points at the class hooks that survive it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sitting inside <code>, the label inherited the prose pre-code colour, which is light against the dark code background in both themes. As a sibling of the <pre> it would have inherited the body text colour instead, leaving it dark on dark in light mode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emmadesilva
force-pushed
the
v3/composable-code-blocks
branch
from
August 1, 2026 01:57
d66709d to
9614813
Compare
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 01:57 — with
GitHub Actions
Inactive
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2577 +/- ##
===========================================
Coverage 100.00% 100.00%
- Complexity 1756 1770 +14
===========================================
Files 180 184 +4
Lines 4395 4410 +15
===========================================
+ Hits 4395 4410 +15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The renderer delegating to the renderers below it is the load-bearing part of this design, and nothing was asserting it. These fake the Torchlight API and check what actually reaches it: that its markup ends up inside the code block view, that the filepath comment is stripped from the code before it is sent off, and that the view is told which renderer produced the markup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label view's contract changed along with the label's position: a copy written for the old one floats the label outside the code block when rendered in the new one. Keeping the name would have quietly broken the layout of every site that published and customized it, since a published view keeps taking precedence. Renaming it makes those copies inert instead. A site that customized the label falls back to the shipped one until the customizations are ported, which loses the customization but never renders a broken page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The release notes listed four changes as equally breaking. Only one is a PHP API removal, and even that had no supported registration point. The rest are changes to rendered output and shipped views, and the first-line comment rule is closer to a fix than a break, since it is what the documentation always described. Sorting them that way makes the one entry that costs a reader real work, porting a customized label view, easier to find rather than lost among notes that do not apply to them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 02:09 — with
GitHub Actions
Inactive
The two lines Codecov flagged are the fall-through taken when no renderer below this one produces markup. Nothing reaches it through a Markdown render, since CommonMark's own fenced code renderer always returns something, so the test drives the renderer against an environment where it is the only one registered. Worth keeping rather than trimming: returning null hands the node back to the document renderer instead of emitting a code block with no code in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 02:16 — with
GitHub Actions
Inactive
Removing CodeblockFilepathProcessor is not a breaking change. The processor list is hardcoded in an internal trait, so there was never a supported way to register the class, and its two static methods only did anything as a pair handing markers to each other. It moves to the cleanup notes, and out of the upgrade guide, where it was giving readers a migration step they cannot need. Renaming the label view is the breaking change, since published views take precedence and a customized copy keeps being used after upgrading. Also rewrites the prose that had drifted into jargon. "Chrome the framework does not ship is a view change" was a sentence I would not want to read either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emmadesilva
temporarily deployed
to
pr-documentation-2577
August 1, 2026 14:59 — with
GitHub Actions
Inactive
The pages I added had drifted into a register the rest of the docs don't
use: portentous fragments, metaphors that weren't carrying anything
("that design decided the feature's ceiling"), and sentences built to
sound significant instead of to be read.
Rewritten against the voice of the surrounding pages, which is plain and
speaks to the reader directly. "Simply add a code comment", "rather
forgiving, by design", "you may have noticed".
Also drops the invented **Breaking:** prefix in the changelog for the
plain "Breaking:" the file already used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fenced code blocks were the last Markdown construct in Hyde still assembled by string surgery. A pre-processor rewrote
// filepath:comments into<!-- HYDE[Filepath] -->marker comments, and a post-processor spliced the rendered label back into the finished HTML with a regex matching<pre><code class="language-…">.That design set the feature's ceiling. The label could only ever be a fragment wedged inside the
<code>element, because that was the only place a string replacement could reach. Anything else a code block might want — a copy button, a header bar, a collapsed state — had nowhere to live. The class even carried a@todosaying as much.Code blocks now go through the same pipeline as terminal blocks: a CommonMark extension prepares each fenced node, and a renderer hands a typed view model to a publishable Blade view. The view owns everything around the code, so block chrome is a view change rather than a framework change.
The Torchlight constraint
The view is handed the finished
<pre><code>markup rather than the raw code, which is what keeps Torchlight working.Torchlight registers its own renderer for fenced code nodes and highlights through its API, so Hyde cannot render the code itself without replacing the highlighter. Instead Hyde's renderer registers above Torchlight's and asks the renderers below it for the markup, exactly as CommonMark's document renderer would, then wraps whatever comes back. Torchlight stays in charge when enabled, CommonMark's own renderer answers when it is not, and the view is told which one it got through
$highlightedByTorchlight.For the same reason the node stays a
FencedCoderather than being replaced with a Hyde node the way terminal blocks are — a replaced node is a node Torchlight's renderer is never asked about. The label is resolved onto the node's data while the document is walked, and the renderer reads it back. That listener runs after the terminal transformer has claimed its nodes and before Torchlight collects the code it sends off, so the filepath comment is stripped from the code that gets highlighted.New: setting the label on the fence
Labels can now also be set with a
title="…"modifier, using the same attribute syntax as terminal block titles:The existing comment syntax is unchanged and still works. When a block uses both, the modifier wins — it is the explicit, structured form, and it lives on the fence rather than in the code, so a block carrying both is almost certainly mid-migration. The comment is stripped from the code either way.
Hyde\Markdown\Extensions\CodeBlockViewModelis public, unlike the terminal one, since a code block is a thing projects reasonably want to render outside Markdown, and the view contract it pins down is the documented interface for anyone publishing the view.What is actually breaking
Renaming the label view is the breaking change.
components/filepath-label.blade.php→components/markdown/code-block-label.blade.php.Published views take precedence over the framework's own, so a project that published and customized this view keeps using its copy after upgrading. The view's contract changed with the label's position, and a copy written for the old contract floats the label outside the code block when rendered in the new one — verified, it lands on the page background with the
<pre>shrunk to make room. Keeping the name would have silently broken the layout of every site that customized it. Renaming leaves those copies unused instead, which costs the customization rather than the page. There is a regression test for exactly this.The HTML changed, which is the part that reaches sites that never touched the feature. Blocks are wrapped in
<div class="hyde-code-block relative">, with the label a sibling of the<pre>. Real impact on CSS that matched the old structure;hyde-code-blockandhyde-code-block-labelare stable hooks.Not breaking
CodeblockFilepathProcessorwas removed. The processor list is hardcoded in an internal trait, so there was never a supported way to register it, and its two static methods only did anything as a pair handing<!-- HYDE[Filepath] -->markers to each other. Internal cleanup, filed under Removed — it is not in the upgrade guide, since there is no migration to do.Torchlight is intact, and there are now tests proving it
The renderer delegating downward is the load-bearing part of this design, so it is now covered by tests that fake the Torchlight API and assert on what actually reaches it:
echo 'Hello World!';, not the comment.$highlightedByTorchlightistrueunder Torchlight andfalsewithout it.Visual check
Rendered before and after with identical content, screenshotted headless, then pixel-diffed:
Everything outside the label's bounding box is pixel-identical — block position, size, spacing, paragraph flow, syntax colours. The label itself renders ~7% wider: it used to inherit
<small>'s UAsmallersize from the 14px<pre>(~11.6px), and now setstext-xs(12px) explicitly, because outside the<pre>there is no code font size to inherit. Happy to pin it to an arbitrary value if you would rather it be exact.The label previously inherited
--tw-prose-pre-codefrom the<pre>it lived in. Outside the<pre>it would have inherited the body text colour, leaving it dark-on-dark in light mode, so the shipped view sets that colour explicitly.Testing
CodeBlocksTest(34 tests, including the Torchlight and stale-view regressions) andCodeBlockViewModelUnitTest(9 tests).ComposableMarkdownBlocksDocumentationTestgains a Code Blocks section, so every claim the rewritten masterclass page makes is asserted (103 tests).FeaturedImageUnitTestfailure that reproduces onmasterand is unrelated to this change.Documentation
docs/digging-deeper/composable-markdown-blocks.md— code blocks get their own section covering both label syntaxes, precedence, the view contract, class hooks, and a customization example. The pipeline and limitations sections are corrected.docs/digging-deeper/advanced-markdown.md— the title modifier, the precedence rule, and how to publish the views.UPGRADE.md,CHANGELOG.md,HYDEPHP_V3_PLANNING.md.🤖 Generated with Claude Code