🐛 Fixed header cards losing their layout when imported from HTML - #29706
Conversation
fixes TryGhost#29268 - the v2 header parser inferred the layout purely from the presence of a background image, so every card with an image was imported as `split` - this flattened regular, wide and full cards into split whenever rendered HTML was sent back to Ghost, e.g. via the Admin API with `?source=html` - the layout is already encoded in the card's classes by the renderer's `getCardClasses`, so the parser now reverses that mapping rather than guessing from the image - for hand-authored HTML that omits the width classes, it falls back to the image's position in the DOM, which is the only structural difference between split and the other layouts - the existing importDOM test asserted the buggy behaviour (a card with no width classes and the image as a direct child was expected to be `split`), so it now expects `full` Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughThe v2 header parser now infers layouts from card classes and image placement. It supports 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ref TryGhost#29268 This verifies every v2 layout survives HTML rendering and parsing with and without background images.
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run ghost:test:ci:integration |
✅ Succeeded | 3m 19s | View ↗ |
nx run @tryghost/admin:test:acceptance |
✅ Succeeded | 6m 16s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 2m 31s | View ↗ |
nx run @tryghost/koenig-lexical:test:acceptance |
✅ Succeeded | 2m 18s | View ↗ |
nx run ghost:test:legacy |
✅ Succeeded | 2m 53s | View ↗ |
nx run ghost:test:e2e |
✅ Succeeded | 2m 9s | View ↗ |
nx run-many -t test:unit -p @tryghost/kg-defaul... |
✅ Succeeded | 42s | View ↗ |
nx run @tryghost/activitypub:test:acceptance |
✅ Succeeded | 43s | View ↗ |
Additional runs (5) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-08-03 17:43:57 UTC
ref TryGhost#29268 This ensures the parser fix is included in the next kg-default-nodes patch release.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #29706 +/- ##
==========================================
+ Coverage 75.42% 75.46% +0.03%
==========================================
Files 1611 1611
Lines 141912 141912
Branches 17549 17563 +14
==========================================
+ Hits 107037 107090 +53
+ Misses 33803 33752 -51
+ Partials 1072 1070 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|

Fixes #29268.
Why
The v2 header card parser decided the card's layout from a single heuristic:
Any header card that had a background image was imported as
split, regardless of what the HTML actually said. So when rendered Ghost HTML is sent back to Ghost — the documented Admin API flow ofPOST /posts/?source=html— everyregular,wideandfullheader card came back assplit, and the image that was meant to be a full-bleed background ended up side-by-side with the text. Authors had to fix each card by hand in the editor.What
The layout is not something we have to guess at: the renderer already encodes it in the card's classes in
getCardClasses. The parser now reverses that mapping instead:kg-layout-splitsplitkg-width-fullfullkg-width-widewidekg-width-regularregularkg-layout-splitis checked first because split cards also carrykg-width-full.For hand-authored HTML that omits the width classes there is still a structural signal to fall back on — the renderer only nests the image inside
.kg-header-card-contentfor split layouts, so an image in the content wrapper means split, and anything else falls back to the node'sfulldefault.Note on the changed test
parses a header card V2assertedlayout === 'split'for markup with no width classes and the image as a direct child of the card — which is the exact shape of a full-width card, and the bug being reported here. That expectation now readsfull.Tests
Added to
test/nodes/header.test.ts:parses the layout from the card classes— round-trips all four layoutsdoes not force split layout when a full-width card has a background image— the reproduction from the issue verbatimfalls back to the image position when the layout classes are missing— covers the structural fallbackAll three fail on
mainand pass with this change. Full package suite: 63 files / 851 tests passing,tsc --noEmitclean, eslint clean on both touched files.