diff --git a/third_party/muya/src/assets/styles/exportStyle.css b/third_party/muya/src/assets/styles/exportStyle.css
index e362724..187b8f1 100644
--- a/third_party/muya/src/assets/styles/exportStyle.css
+++ b/third_party/muya/src/assets/styles/exportStyle.css
@@ -89,6 +89,21 @@
white-space: pre-wrap;
}
+/* Render soft line breaks (Shift+Enter -> a bare `\n` inside a block) the way
+ the editor does (`.mu-content` is pre-wrap) instead of emitting a
+ non-standard `
`, so exported HTML stays CommonMark-conformant while still
+ showing the break (#3676).
+
+ `li:not(:has(> p))` targets only tight list items, whose soft break is a
+ bare `\n` directly inside the `
`. Loose items wrap their content in
+ `` (handled by the `p` rule); giving them `li` pre-wrap would expose
+ marked's pretty-printing newline between `
` and `` as a stray blank
+ line, so they are deliberately excluded. */
+.markdown-body p,
+.markdown-body li:not(:has(> p)) {
+ white-space: pre-wrap;
+}
+
.markdown-body table {
display: table;
}
diff --git a/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts b/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts
new file mode 100644
index 0000000..95db5e2
--- /dev/null
+++ b/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from 'vitest';
+import { getHighlightHtml } from '../../utils/marked';
+
+const OPTS = { math: false, superSubScript: false, footnote: false, frontMatter: false };
+
+describe('#3676: soft line breaks survive export as conformant newlines', () => {
+ it('keeps a paragraph soft break as a newline, never a
', () => {
+ const html = getHighlightHtml('line one\nline two', OPTS);
+
+ expect(html).toContain('line one\nline two
');
+ expect(html).not.toMatch(/
/);
+ });
+
+ it('keeps a soft break inside a tight list item, never a
', () => {
+ const html = getHighlightHtml('- line A\n line B', OPTS);
+
+ expect(html).toMatch(/line A\nline B<\/li>/);
+ expect(html).not.toMatch(/
/);
+ });
+
+ it('leaves a real hard break as
', () => {
+ const html = getHighlightHtml('line one \nline two', OPTS);
+
+ expect(html).toMatch(/
/);
+ });
+});