Skip to content

Nest markdown inside raw-HTML blocks separated by blank lines (#433)#686

Merged
srid merged 7 commits intomasterfrom
empty-group
Apr 29, 2026
Merged

Nest markdown inside raw-HTML blocks separated by blank lines (#433)#686
srid merged 7 commits intomasterfrom
empty-group

Conversation

@srid
Copy link
Copy Markdown
Owner

@srid srid commented Apr 29, 2026

Markdown content between two blank-line-separated raw HTML tags now nests inside the surrounding element instead of escaping to be its sibling. Pandoc emits CommonMark "type 6" HTML blocks like <details> … markdown … </details> as three separate AST blocks (opener RawBlock, Para, closer RawBlock); without grouping, each raw blob ends up wrapped in its own element and the markdown paragraph drifts outside.

The actual fix is upstream in srid/heist-extra#16, which adds a groupRawHtmlBlocks AST-preprocess pass that rewrites those orphan triplets into a B.Div carrying the tag in a directive attribute — the existing Div renderer turns it into the named element with the markdown content as a real DOM child.

This PR pins emanote to that branch and notes the fix under the Bug fixes section of the unreleased changelog.

Closes #433.

Try it locally

nix run github:srid/emanote/empty-group

Generated by /do on Claude Code (model claude-opus-4-7).

srid added 3 commits April 28, 2026 21:14
…nk lines

CommonMark "type 6" HTML blocks (e.g. `<details>`) end at the next blank
line, so

    <details>

    **bold** content

    </details>

reaches the renderer as three blocks: a `RawBlock` for `<details>\n`,
a `Para` for the markdown, another `RawBlock` for `</details>\n`. The
existing `<rawhtml>` wrapper trapped the open and close inside their
own elements, leaving the markdown paragraph a sibling of the (now
empty) details element instead of its child.

Fixed upstream in srid/heist-extra by a new `groupRawHtmlBlocks` AST
pass that walks every block list and rewrites orphan opener/closer
triplets into a `B.Div` with the tag in its `tag` attribute. The
existing Div renderer turns that into the named element, so the
markdown content lands as a real DOM child.

Pointing flake.lock at the heist-extra branch carrying the fix.

Closes #433.
Mirrors the #119 pattern: a fixture (tests/fixtures/notebook/rawhtml-details.md)
exercises the issue's exact shape (a <details> tag with markdown content
between blank lines) and a smoke scenario asserts the marker inside has
a <details> ancestor in the rendered HTML. Without the heist-extra
groupRawHtmlBlocks pass the marker would be a sibling of an empty <details>.
@srid
Copy link
Copy Markdown
Owner Author

srid commented Apr 29, 2026

Evidence

The screenshots below were captured by serving the tests/fixtures/notebook/rawhtml-details.md fixture from a temporary notebook directory using two emanote builds: master (cade1054) and this PR (67ae6800).

Master (broken) This PR (fixed)
before after

Source HTML structure (the load-bearing diff — fetched via fetch() to bypass the browser's lenient parser):

  • Master emits the <details> group as three sibling <rawhtml> wrappers around the opener, the markdown paragraph, and the closer:
    <rawhtml xmlhtmlRaw style='display: contents'><details>
    </rawhtml>
        <p ...>This paragraph ... marker</p>
    <rawhtml xmlhtmlRaw style='display: contents'></details>
    </rawhtml>
  • This PR emits the <details> group as a single bare HTML block, with the markdown paragraph as a real direct child:
    <details>
        <p ...>This paragraph ... marker</p>
    </details>

Structural assertion (document.querySelector('[data-marker="RAWHTML_DETAILS_INNER"]').closest("details") !== null):

  • Master: true
  • This PR: true

Both report true because the browser's HTML parser is lenient enough to recover from the broken master output — the unknown <rawhtml> wrapper around <details> is reparented during DOM construction, so the marker still ends up with a <details> ancestor in the live DOM. The bug is in the emitted HTML, not in the recovered DOM. The before/after source-HTML diff above is therefore the authoritative structural evidence; the closest() assertion is preserved here for completeness but is not load-bearing for this issue.

Visual difference: The "before" screenshot shows the collapsed <details> widget on master — the markdown paragraph is hidden because the browser-recovered DOM does end up with it as a child, and <details> collapses its content when there's no <summary>. The "after" screenshot expands the details (details.setAttribute("open", "")) so the nested paragraph is visible: "This paragraph must render as a child of the <details> element. marker". The visible body text confirms the markdown paragraph renders as a child of <details> in the fix, with cleanly emitted HTML — no more interleaved <rawhtml> siblings around the opener/closer.

…e bug)

The first cut of this scenario asserted closest('details') on the
parsed DOM. Browser HTML5 parsers recover the broken master output
into a DOM that *does* nest the marker under <details>, so a DOM-level
check passes either way. The bug is in the emitted HTML, not in the
recovered DOM.

Switch to fetching the raw HTTP response and verifying that the
opener and closer <details> tags have no immediately-adjacent
<rawhtml> wrapper. Confirmed: regex matches the broken pattern on
master (`<rawhtml ...><details>` + `<rawhtml ...></details>`),
fails to match on the fix.
@srid
Copy link
Copy Markdown
Owner Author

srid commented Apr 29, 2026

/do results

Step Status Duration Verification
sync 0s git fetch ok; forge=github
research 11m 43s Issue #433: orphan RawBlock html pairs in Pandoc AST get wrapped individually by heist-extra's rawNode. Fix belongs upstream in heist-extra.
branch 15s Working on existing empty-group branch; created group-orphan-rawhtml-blocks in heist-extra for the dependency fix.
implement 7m 34s Wrote groupRawHtmlBlocks pass in heist-extra (RawHtmlGroup module) + dropTagAttr fix in Render Div arm; 12 unit + 1 integration test. Updated emanote flake to point at it.
check 9s cabal build all green.
docs 18s Bug-fix entry under Unreleased in CHANGELOG.
fmt 1m 12s just fmt clean on emanote; hlint+fourmolu also clean on heist-extra.
commit 39s Heist-extra 970c9a3 pushed; emanote 8bca7a37 pushed.
hickey+lowy 9m 41s 1 Hickey fix + 1 hoist (defer flipped); 1 Lowy fix + 3 hoist/doc (defers flipped). 3 follow-up commits on heist-extra.
police 10m 46s All 3 passes clean after applying: extracted parseTagAfterPrefix, rehomed tag-directive helpers, trimmed comments.
test 53s Emanote unit tests: 47/47. Heist-extra: 54 tests pinning AST + rendering.
create-pr 2m 25s heist-extra#16 + emanote#686 (drafts); Hickey/Lowy analysis on the upstream PR.
ci 2m 32s vira ci PASSED on aarch64-darwin + x86_64-linux. E2e-static/live/morph all green.
evidence 10m 3s Before/after screenshots + emitted-HTML diff posted. Tightened e2e regression to fetch raw HTTP and verify no <rawhtml> adjacent to <details> (confirmed it fails on master, passes on fix).
Total 58m 27s

Slowest step: research (11m 43s)

Optimization suggestions

  • Research dominated (20% of wall time). Most of that was reading the heist-extra Render.hs / Render/Internal.hs to understand the getTag-as-directive convention before designing the grouping pass. For future bug fixes that touch a downstream library, pre-reading the relevant module(s) before invoking /do would shave the initial map step.
  • Evidence step ran two sequential nix builds (master + this PR). The sub-agent could parallelise them (master build runs while feature build cache-hits), reclaiming roughly 2-3 minutes on bug-fix evidence captures.
  • Police's elegance pass landed two real refactors (extract parseTagAfterPrefix, rehome the tag-directive helpers) that the initial draft missed. Worth running /simplify (or its equivalent) earlier in implement for non-trivial new modules — would catch the same findings before the first commit, avoiding the police-phase rework cycle.
  • Re-running vira ci after the evidence-driven test tightening added ~3 minutes. For follow-up runs on the same branch where only test code changed, /do --from ci-only skips research → police and goes straight to CI.

Workflow completed at 2026-04-28 21:50 UTC.

srid added 2 commits April 29, 2026 14:50
… .agency/do.md

The fix in this PR makes <details> blocks separated by blank lines work
as a disclosure widget with parsed markdown inside. Add a Raw HTML
section to docs/guide/markdown.md showing the live syntax (and pointing
back to callouts for the Emanote-native foldable variant), then update
.agency/do.md so future Markdown-syntax features land their demo in the
same file as a matter of convention.
Now that the upstream PR has landed, drop the branch ref so flake.nix
tracks heist-extra master again. The locked rev advances from 3bd7aba
to f496d0c (same tree — the merge was a fast-forward).
@srid srid marked this pull request as ready for review April 29, 2026 18:54
@srid srid merged commit 1f3d9ad into master Apr 29, 2026
5 checks passed
@srid srid deleted the empty-group branch April 29, 2026 18:55
srid added a commit that referenced this pull request Apr 29, 2026
Brings in #686 (raw-HTML / markdown nesting fix for #433), which also
landed the matching srid/heist-extra master commit this PR's
flake.lock now points at via the dependency PR's merge with master.

# Conflicts:
#	flake.lock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong rendering of HTML blocks

1 participant