stage #4669 (TomBanksAU): wrap markdown source previews — AUTHORITATIVE head 0cee92c9 (#4679)

Re-staged from the contributor's rebased head (0cee92c9) which independently converged on
the same 5-test-file class-tolerance fixups I'd made — shipping the contributor's exact
version, not my private variant. Tags md/markdown/mdx fenced blocks with md-source-block
class in both renderers; CSS wraps them while code/diff/csv keep horizontal-scroll. 90
renderer-family tests pass.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
This commit is contained in:
nesquena-hermes 2026-06-22 00:45:20 -07:00 committed by GitHub
parent de385c2664
commit a30348b2c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 42 additions and 10 deletions

View file

@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.580] — 2026-06-22 — Release UM (wrap markdown source previews)
### Fixed
- **Markdown source previews now wrap normal prose instead of forcing horizontal scroll.** Fenced code blocks tagged `md`, `markdown`, or `mdx` (the inline viewer with the small `MD` header and Copy button) now render in a dedicated wrapping source-preview style, so ordinary markdown text flows to the message width while true code/diff blocks keep their existing horizontal-scroll behavior. Thanks @TomBanksAU. (#4669)
## [v0.51.579] — 2026-06-22 — Release UL (deflake TLS end-to-end tests)
### Fixed

View file

@ -1687,6 +1687,9 @@
.msg-body code{font-family:"SF Mono","Fira Code",ui-monospace,monospace;font-size:var(--message-code-font-size);background:var(--code-inline-bg);padding:1px 5px;border-radius:4px;color:var(--code-text);}
.msg-body pre{background:var(--code-bg);border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:10px 0;}
.msg-body pre code{background:none;padding:0;border-radius:0;color:var(--pre-text);font-size:var(--message-pre-code-font-size);line-height:1.6;}
.msg-body pre.md-source-block,.preview-md pre.md-source-block{white-space:pre-wrap;overflow-x:hidden;overflow-wrap:anywhere;}
.msg-body pre.md-source-block code,.preview-md pre.md-source-block code{display:block;white-space:inherit;overflow-wrap:anywhere;word-break:break-word;}
.msg-body pre.md-source-block code .token,.preview-md pre.md-source-block code .token{white-space:inherit;overflow-wrap:anywhere;word-break:inherit;}
.provider-error-details{margin:12px 0 0;border:1px solid var(--border);border-radius:10px;background:var(--surface);overflow:hidden;}
.provider-error-details>summary{cursor:pointer;color:var(--muted);font-size:12px;font-weight:600;padding:8px 12px;}
.provider-error-details>pre{margin:0;border:0;border-top:1px solid var(--border);border-radius:0;max-height:220px;}

View file

@ -349,6 +349,7 @@ function _renderUserFencedBlocks(text){
if(code.endsWith('\n')) code=code.slice(0,-1);
const h=lang?`<div class="pre-header">${esc(lang)}</div>`:'';
const langAttr=lang?` class="language-${esc(lang)}"`:'';
const preClass=/^(md|markdown|mdx)$/.test(lang)?' class="md-source-block"':'';
if(lang==='diff'||lang==='patch'){
const colored=esc(code).split('\n').map(line=>{
if(line.startsWith('@@')) return `<span class="diff-line diff-hunk">${line}</span>`;
@ -358,7 +359,7 @@ function _renderUserFencedBlocks(text){
}).join('\n');
stash.push(`${h}<pre class="diff-block"><code${langAttr}>${colored}</code></pre>`);
} else {
stash.push(`${h}<pre><code${langAttr}>${esc(code)}</code></pre>`);
stash.push(`${h}<pre${preClass}><code${langAttr}>${esc(code)}</code></pre>`);
}
return lead+'\x00UF'+(stash.length-1)+'\x00';
});
@ -4856,6 +4857,7 @@ function renderMd(raw){
} else {
const h=lang?`<div class="pre-header">${esc(lang)}</div>`:'';
const langAttr=lang?` class="language-${esc(lang)}"`:'';
const preClass=/^(md|markdown|mdx)$/.test(lang)?' class="md-source-block"':'';
// For diff/patch blocks, wrap each line in a colored span
if(lang==='diff'||lang==='patch'){
const colored=esc(code.replace(/\n$/,'')).split('\n').map(line=>{
@ -4881,10 +4883,10 @@ function renderMd(raw){
const body=rows.slice(1).map(r=>'<tr>'+r.split(',').map(c=>`<td>${esc(c.trim())}</td>`).join('')+'</tr>').join('');
_preBlock_stash.push(`${h}<div class="csv-table-wrap"><table class="csv-table"><thead><tr>${headers.map(h=>`<th>${esc(h)}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table></div>`);
} else {
_preBlock_stash.push(`${h}<pre><code${langAttr}>${esc(code.replace(/\n$/,''))}</code></pre>`);
_preBlock_stash.push(`${h}<pre${preClass}><code${langAttr}>${esc(code.replace(/\n$/,''))}</code></pre>`);
}
} else {
_preBlock_stash.push(`${h}<pre><code${langAttr}>${esc(code.replace(/\n$/,''))}</code></pre>`);
_preBlock_stash.push(`${h}<pre${preClass}><code${langAttr}>${esc(code.replace(/\n$/,''))}</code></pre>`);
}
}
return lead+'\x00P'+(_preBlock_stash.length-1)+'\x00';

View file

@ -126,7 +126,7 @@ class TestUserFencedBlocks:
def test_four_backtick_outer_fence_preserves_inner_triple_fence(self):
"""User-message code fences should follow CommonMark fence-length matching too."""
out = _run_user_render("````md\n```inner\nfoo\n```\n````")
assert out.count("<pre>") == 1
assert out.count("<pre class=\"md-source-block\">") == 1
assert out.count("</pre>") == 1
assert '<div class="pre-header">md</div>' in out
assert "```inner" in out

View file

@ -41,7 +41,9 @@ def test_csv_fence_fallback_for_insufficient_rows():
src = f.read()
fence_section = src[src.find("lang==='csv'"):src.find("lang==='csv'") + 800]
assert 'rows.length>=2' in fence_section, "Should check for at least 2 rows"
assert '<pre><code' in fence_section, "Fallback should render as <pre><code>"
assert '<pre${preClass}><code${langAttr}>' in fence_section, (
"Fallback should render via the shared preClass-aware code-block template"
)
def test_csv_media_file_handler():

View file

@ -17,9 +17,10 @@ def test_fenced_code_blocks_add_prism_language_class():
def test_fenced_code_blocks_keep_existing_pre_header_layout():
js = _read_ui_js()
# The fenced code rendering was moved into the stash callback (#1154 fix).
# The template string now uses `lang` instead of `normalizedLang`.
assert '${h}<pre><code${langAttr}>${esc(code.replace(/\\n$/,' in js, (
"The syntax-highlight fix should preserve the existing fenced code block layout"
# The template string now uses `lang` instead of `normalizedLang`, and
# markdown-source fences may add a dedicated md-source-block class.
assert '${h}<pre${preClass}><code${langAttr}>${esc(code.replace(/\\n$/,' in js, (
"The syntax-highlight fix should preserve the shared fenced code block layout"
)
assert '<div class="code-block">' not in js, (
"This fix should not introduce a new wrapper around fenced code blocks"

View file

@ -0,0 +1,18 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
def test_markdown_source_code_blocks_get_dedicated_pre_class_in_both_renderers():
expected = "const preClass=/^(md|markdown|mdx)$/.test(lang)?' class=\"md-source-block\"':'';"
assert UI_JS.count(expected) >= 2, "Expected both fenced-code renderers to tag markdown source blocks"
def test_markdown_source_code_blocks_have_desktop_wrap_css():
assert ".msg-body pre.md-source-block,.preview-md pre.md-source-block{white-space:pre-wrap;overflow-x:hidden;overflow-wrap:anywhere;}" in CSS
assert ".msg-body pre.md-source-block code,.preview-md pre.md-source-block code{display:block;white-space:inherit;overflow-wrap:anywhere;word-break:break-word;}" in CSS
assert ".msg-body pre.md-source-block code .token,.preview-md pre.md-source-block code .token{white-space:inherit;overflow-wrap:anywhere;word-break:inherit;}" in CSS

View file

@ -397,7 +397,7 @@ class TestFencedCodeFenceLength:
"That is much more correct than pretending"
)
out = _render(driver_path, src)
assert out.count("<pre>") == 1
assert out.count("<pre class=\"md-source-block\">") == 1
assert out.count("</pre>") == 1
assert '<div class="pre-header">md</div>' in out
assert "```novelcrafter" in out
@ -408,7 +408,7 @@ class TestFencedCodeFenceLength:
def test_four_backtick_outer_fence_preserves_inner_triple_fence(self, driver_path):
out = _render(driver_path, "````md\n```inner\nfoo\n```\n````\n")
assert out.count("<pre>") == 1
assert out.count("<pre class=\"md-source-block\">") == 1
assert out.count("</pre>") == 1
assert '<div class="pre-header">md</div>' in out
assert "```inner" in out