test-flow-html-preview-hydration
AI-judged verification that the HTML Preview component (`data-zfb-island="HtmlPreviewWrapperInner"`) hydrates into the correct vertical-stack layout — title bar on top, preview iframe below, code togg...
Test flow: html-preview island hydration layout
Background
@takazudo/ renders the interactive HTML
Preview component used by the <HtmlPreview> MDX tag. It is a zfb island
hydrated with when="visible".
Each preview block has this SSR structure:
<div data- zfb- island= "HtmlPreviewWrapperInner" . . . > ← island marker
<div class= "border . . . rounded- lg overflow- hidden"> ← OUTER CONTAINER
<div class= "flex items- center justify- between . . . "> ← TITLE BAR (flex row)
<span>{title}</ span>
<div class= "flex">{Mobile/ Tablet/ Full buttons}</ div>
</ div>
<div class= "bg- surface p- hsp- lg">…<iframe>…</ div> ← PREVIEW AREA (block)
<div class= "border- t . . . ">…Show code…</ div> ← CODE SECTION (block)
</ div>
</ div>The outer container's three children are block <div>s that MUST stack
vertically (title bar on top, preview below, code beneath).
The bug under verification (Astro→zfb migration regression): the bare
inner component carried the outer wrapper's name as its
displayName ("HtmlPreviewWrapper") and was not exported, so the SSG
marker resolved to the exported self-wrapping HtmlPreviewWrapper. On the
client hydrate(<HtmlPreviewWrapper/>, markerDiv) re-emitted another
data-zfb-island wrapper and Preact reused the SSR'd children one level
off — re-parenting the preview + code sections INSIDE the flex title bar.
Result: the title bar + buttons squished on the LEFT, preview iframe
floating on the RIGHT (a broken side-by-side / flex-row layout). The fix
gives the bare inner component its OWN name+marker
(HtmlPreviewWrapperInner) and exports it, while HtmlPreviewWrapper
stays the <Island> wrapper that MDX registers — so the bundle hydrates
the bare component in-place.
The raw SSR DOM (JS disabled) was ALWAYS correct — the breakage appears
only AFTER hydration. So the test MUST run with JS enabled and MUST scroll
the page so the when="visible" islands actually hydrate before measuring.
Scenario
Run against the URL in Inputs.previewUrl (default
http:). Use viewport
1280 x 900. Use / (Playwright CLI / a small Playwright
script) — / single-page computed-style reads are not enough
because this needs scroll-to-hydrate plus per-block DOM-tree walks.
Launch chromium (JS enabled), viewport 1280x900, goto previewUrl,
waitUntil: networkidle.Scroll the full page top→bottom in ~400px steps (≈60ms each), then back to top, and wait ~1.2s. This triggers the IntersectionObserver that hydrates every
when="visible"island.For EVERY
[data-zfb-island="HtmlPreviewWrapperInner"]element on the page (there are 6 on the default page), locate its outer container (:scope > div) and measure (see Measurements).Capture a full-page screenshot AND a cropped screenshot of the FIRST preview block (
element.screenshot()on the island).
Measurements (per preview block, post-hydration)
For each island's outer container C:
childCount= number of element children ofC.child0=C.children[0](expected: the title bar).child0.display=getComputedStyle(child0).display.child0.height=child0.getBoundingClientRect().height.child1=C.children[1](expected: the preview area).child1.display, andchild1.rect.y(top).titleBarBottom=child0.rect.y + child0.rect.height.
Verdict criteria (mechanical first, visual second)
A block PASSES when ALL of:
childCount === 3(the three siblings are NOT collapsed/re-parented).child0.display === "flex"ANDchild0.height <= 80(the title bar is just a bar — in the bug it absorbs the whole component and is 200px+ tall).child1.display === "block"ANDchild1.rect.y >= titleBarBottom - 2(preview area sits BELOW the title bar — vertical stack, not beside it).
The overall run PASSES only when every preview block passes (6/6 on the default page) AND the visual screenshot check confirms: each block shows a horizontal title bar on top (title left, Mobile/Tablet/Full pills right, "Full" highlighted) with the preview area full-width below it — and NONE show the title bar/buttons squished to the left with the iframe on the right.
FAIL if any block has childCount !== 3, a tall title bar, a side-by-side
arrangement, or the screenshot shows the broken left/right split.
Output schema
Return a structured result with exactly these fields:
{
blocksTotal: number, // islands found (expect 6)
blocksPassed: number, // blocks meeting all 3 mechanical criteria
perBlock: [ // one entry per island
{ index, childCount, child0Display, child0Height, child1Display, child1Y, titleBarBottom, pass }
],
mechanicalVerdict: "PASS" | "FAIL",
visualVerdict: "PASS" | "FAIL",
verdict: "PASS" | "FAIL", // PASS only if BOTH mechanical and visual PASS
summary: string, // one-line human verdict
fullPageScreenshot: string, // path
firstBlockScreenshot: string, // path
toolUsed: "headless-browser"
}Notes
If
blocksTotalis 0, the page didn't load or the marker changed — report FAIL with that detail, do not silently pass.Do not measure with JS disabled — the SSR DOM is always correct and would produce a false PASS that misses the hydration regression.