Skip to content

Space agent-markdown blocks through Streamdown's dir wrappers - #239

Merged
hardbeat920 merged 2 commits into
hardbeat920:mainfrom
bluzername:fix/agent-markdown-paragraph-spacing
Sep 15, 2026
Merged

hardbeat920 merged 2 commits into
hardbeat920:mainfrom
bluzername:fix/agent-markdown-paragraph-spacing

Conversation

@bluzername

@bluzername bluzername commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #218

Updated after @hardbeat920 could not see a difference. He was right: the first patch on this branch changed nothing. Details and screenshots are in the comment below.

What happens

A reply with several "\n\n"-separated sections shows up as one dense block in the transcript, no gap between the paragraphs, even though the breaks are really there in the stored text.

Cause

AgentMarkdown passes dir="auto" to Streamdown. With that, Streamdown puts every block in its own wrapper:

<div class="space-y-4 ... agent-markdown ...">
  <div dir="ltr" style="display:contents"><p>First paragraph.</p></div>
  <div dir="ltr" style="display:contents"></div>
  <div dir="ltr" style="display:contents"><p>Second paragraph.</p></div>
</div>

A display: contents box drops its own margins. Streamdown's space-y-4 on the root targets exactly those wrappers, so it paints no gap at all. Headings, blockquotes and rules still look fine because their margin sits on the element itself (mt-6 mb-2, my-4, my-6). Paragraphs and lists carry no margin, so they sit flush.

The same wrapper is why my first patch did nothing: every <p> is the only child of its wrapper, so .agent-markdown p:last-child matched all of them and zeroed the margin-bottom it had just added.

Fix

Put the gap on the block inside the wrapper, top side only, so a reply never ends with a trailing gap:

.agent-markdown > div + div > p,
.agent-markdown > div + div > [data-streamdown="unordered-list"],
.agent-markdown > div + div > [data-streamdown="ordered-list"] {
  margin-top: 1rem;
}

Lists are in there because they have the same problem, no margin of their own. Headings, blockquotes and rules are left alone, they already space themselves.

Measured in Chromium at 860px wide, dark theme, rendering the real AgentMarkdown with the real src/index.css:

boundary main this branch
paragraph to paragraph 0px 16px
paragraph to list 0px 16px
above the first block 0px 0px
below the last block 0px 0px
heading to paragraph 8px 16px

The heading row moves because the paragraph's new 16px collapses with the heading's own 8px bottom margin. That is the 1rem Streamdown meant to have there anyway.

Test

src/surfaces/agentMarkdownSpacing.test.ts now renders real Streamdown output (react-dom/server, with // @vitest-environment happy-dom in the file so the repo-wide node environment stays as it is) and checks the shipped selectors against that DOM. So a rule that matches nothing, or matches every paragraph like my first one did, fails. It is red on main, red on my first patch, green now.

Ran vitest run (2223 tests green) and tsc --noEmit (clean). Did not run the cargo half of npm run check, nothing here touches Rust.

Summary by CodeRabbit

  • Style

    • Improved readability of agent-rendered Markdown by adding spacing between consecutive paragraphs, ordered lists, and unordered lists.
    • Prevented extra spacing after the final content block for a cleaner layout.
  • Tests

    • Added coverage for spacing between consecutive content blocks.
    • Verified that the first block has no preceding spacing and the final block has no trailing spacing.
    • Added checks for different margin declarations and invalid selectors.

A reply with several sections separated by blank lines show up as one
dense block, no visible gap between them. The paragraph breaks are
real in the text (each becomes its own <p>), they just have no space
under them.

Cause: Streamdown own heading, blockquote and hr components each carry
a margin class already (mt-6/mb-2, my-4, my-6), but its default
paragraph component is a plain <p> with no spacing class at all. So
with margin reset by Tailwind preflight, every paragraph sit flush
against the next one.

Add margin-bottom to .agent-markdown p, and zero it on the last
paragraph in a block so it does not add extra space before the next
message.

Test: no jsdom/component test exist in this repo yet (vitest only
picks up src/**/*.test.ts and the harness modules are tested as pure
functions), so the test check the shipped CSS rule directly against
the stylesheet source instead of a rendered DOM. It fails without the
fix and pass with it.

Fixes hardbeat920#218
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 3001c98d-645f-4bdf-ba16-65b6bfc45922

📥 Commits

Reviewing files that changed from the base of the PR and between ff04dc2 and 89bc268.

📒 Files selected for processing (2)
  • src/index.css
  • src/surfaces/agentMarkdownSpacing.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The agent Markdown stylesheet now adds 1rem above paragraphs and lists that follow another block. The tests render Streamdown output and verify spacing between paragraphs, without spacing before the first or after the last block.

Changes

Markdown rendering

Layer / File(s) Summary
Paragraph spacing and validation
src/index.css, src/surfaces/agentMarkdownSpacing.test.ts
The stylesheet uses wrapper-aware top margins for paragraphs and ordered or unordered lists. The tests inspect rendered Streamdown output, display:contents wrappers, matching CSS rules, shorthand and logical properties, invalid selectors, and first/last block spacing.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: hardbeat920

Merge Risk: ⚪ Minimal · up to 89bc2

The Markdown spacing change preserves separation across paragraph, list, and heading transitions without adding a final trailing gap. No current merge-blocking risk was identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 1 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies the coding objective in issue #218. The .agent-markdown CSS applies spacing to the rendered block elements inside Streamdown display: contents wrappers. The top-side spacing separ…
Out of Scope Changes check ✅ Passed The changes stay within issue #218. The CSS change fixes Markdown block spacing. The rendering-based test verifies the Streamdown structure and the shipped selectors. No unrelated change is demonstrat…
Title check ✅ Passed The title clearly describes the main change: adding spacing between Agent Markdown blocks through Streamdown wrappers.
Description check ✅ Passed The description provides detailed context for the bug, cause, fix, visual impact, and tests. It does not use the template headings exactly and omits the Checklist section, but it is mostly complete an…
Full details: Docstring Coverage

Explanation

Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 1 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@hardbeat920

Copy link
Copy Markdown
Owner

@bluzername can you share some screenshots to see the difference. I honestly couldn't find any but maybe i am missing sth 🙏

The first patch on this branch did nothing. I rendered it and measured it:
the gap between paragraphs stayed 0px and the two screenshots came out byte
identical.

Cause: AgentMarkdown passes dir="auto", so Streamdown puts every block in its
own <div dir="..." style="display: contents">. A display:contents box drops
its own margins, so Streamdown's space-y-4 on the root, which targets exactly
those wrappers, paints no gap. Headings, blockquotes and rules still space
correctly because their margin sits on the element itself. Paragraphs and
lists carry no margin at all, so they sit flush.

That same wrapper is why the first patch was useless: every <p> is the only
child of its wrapper, so .agent-markdown p:last-child matched all of them and
zeroed the margin-bottom it had just added.

Put the gap on the block inside the wrapper instead, top side only, so a reply
never ends with a trailing gap. Measured in Chromium at 860px wide, dark theme:
paragraph gap 0px before, 16px after, nothing added above the first block or
below the last.

Test: renders real Streamdown output (react-dom/server under happy-dom) and
checks the shipped selectors against that DOM, so a rule that matches nothing,
or matches every paragraph, fails. It is red on main and red on the first
patch, green now.

Fixes hardbeat920#218
@bluzername bluzername changed the title Give agent-markdown paragraphs a bottom margin Space agent-markdown blocks through Streamdown's dir wrappers Sep 15, 2026
@bluzername

Copy link
Copy Markdown
Contributor Author

thanks for checking. You are right, and I owe you an apology: my first patch did nothing at all. I rendered both and the two screenshots came out byte identical (same file size, cmp says no difference). So there was nothing to see.

Here is why, and what I found.

AgentMarkdown passes dir="auto" to Streamdown. With that set, Streamdown puts every block in its own wrapper:

<div class="space-y-4 ... agent-markdown ...">
  <div dir="ltr" style="display:contents"><p>First paragraph.</p></div>
  <div dir="ltr" style="display:contents"></div>
  <div dir="ltr" style="display:contents"><p>Second paragraph.</p></div>
</div>

Two things follow from that wrapper:

  1. A display: contents box drops its own margins. Streamdown's space-y-4 on the root targets exactly those wrappers, so it paints no gap. That is the real cause of Replies render as one dense block: blank lines between paragraphs are lost #218. Headings, blockquotes and rules still space fine because their margin sits on the element itself.
  2. Every <p> is the only child of its wrapper, so p:last-child matches all of them. My .agent-markdown p:last-child { margin-bottom: 0 } cancelled the margin-bottom: 1rem I had just added on the line above. A perfect no-op. That is what you were looking at.

New commit on the branch puts the gap on the block inside the wrapper instead, top side only, so no trailing gap:

.agent-markdown > div + div > p,
.agent-markdown > div + div > [data-streamdown="unordered-list"],
.agent-markdown > div + div > [data-streamdown="ordered-list"] {
  margin-top: 1rem;
}

Before (main, and also my first patch, identical):

before

After (this branch now):

after

What is in the screenshots: the real AgentMarkdown component with the real src/index.css and the real Tailwind build, served by vite, driven with Playwright in Chromium. 860px viewport, default dark theme, 760px content width, the three-paragraph sample from #218. Only src/index.css differs between the two shots.

Measured with getBoundingClientRect on the <p> elements:

main first patch now
gap between paragraph 1 and 2 0px 0px 16px
gap between paragraph 2 and 3 0px 0px 16px
computed margin on each <p> 0px / 0px 0px / 0px 16px top
block height 144px 144px 176px

The test is rewritten too. It now renders real Streamdown output with react-dom/server and checks the shipped selectors against that DOM, so a rule that matches nothing, or matches every paragraph like my first one, fails. It is red on main, red on my first patch, green now. It uses // @vitest-environment happy-dom in the file, so the repo-wide node environment in vitest.config.ts stays as it is. vitest run 2223 green, tsc --noEmit clean, prettier clean.

Sorry for the round trip. Thank you for pushing back instead of merging it.

@hardbeat920
hardbeat920 merged commit 998fff0 into hardbeat920:main Sep 15, 2026
4 checks passed
@hardbeat920

Copy link
Copy Markdown
Owner

@bluzername thank you this looks good. Merged :)

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.

Replies render as one dense block: blank lines between paragraphs are lost

2 participants