Skip to content

~~strike~~ no longer exports as bold + strike (#1373) - #1382

Merged
rbuergi merged 4 commits into
mainfrom
fix/1373-strikethrough-not-bold
Aug 13, 2026
Merged

rbuergi merged 4 commits into
mainfrom
fix/1373-strikethrough-not-bold

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Closes #1373.

The cause

DocumentBuilder.ReadInlinesInto decided a span was strong from DelimiterCount >= 2.

Markdig's EmphasisExtras — enabled here by UseAdvancedExtensions — folds six different
constructs into the one EmphasisInline node and tells them apart by the delimiter character,
never the count. Probed against the export's own pipeline:

markdown DelimiterChar DelimiterCount
**b** / __b__ * / _ 2
*i* / _i_ * / _ 1
~~s~~ ~ 2
~sub~ ~ 1
^sup^ ^ 1
++ins++ + 2
==mark== = 2

~~ is doubled, so strikethrough satisfied "strong" as well and the run was emitted bold and
struck
. The same slip mis-styled four more nobody had noticed:

  • ++ins++ → bold, ==mark== → bold (doubled delimiter)
  • ~sub~ → struck through (shares ~ with strikethrough)
  • ^sup^ → italic (fell through to "not strong, not strike")

Emphasis is now keyed on DelimiterChar. Not post-processed, not special-cased for ~~: the
character was always the discriminator and the code was reading the wrong field.

The four with nowhere to go

Subscript, superscript, inserted and marked have no representation in the document model, which
carries bold / italic / strike / code and nothing else. They render as their plain text — every
word kept, no emphasis invented. That is a deliberate change from today's behaviour, where they wear
a style at random; giving them styles of their own means extending the model and both renderers,
which is a feature and not this fix.

Verified on the artefact

The complaint is that a word came out bold on a page, so a model assertion cannot close it alone.

Fail-before, read straight out of the produced files:

  • PDF — the struck word's glyphs were drawn with DAAAAA+Arial-BoldMT. Chromium embedded the
    bold face for it; that is the browser's own answer to "did you print this bold?".
  • DOCX — the run for the struck word carried <w:b/>.

And by rasterising both prints and looking at them:

~~Retired~~ ~sub~ ^sup^ ++ins++ ==mark==
before bold + struck struck / italic / bold / bold
after struck, normal weight plain text

Real **bold** is still bold, *italic* still italic, ***both*** still both, and
**bold with ~~strike~~ inside** keeps both on the inner phrase — the fix removes a phantom, not a
style.

Both artefact tests carry a genuinely bold word in the same document as their control, so
"not bold" is a finding rather than an absence: if the pipeline had stopped producing bold at all,
the control fails first. The DOCX test reads run properties out and asserts them non-null before
inspecting children — a ?. chain there would short-circuit on a run without properties and make
the assertion unfailable. (-warnaserror caught the null-deref that led me to it.)

Verification

  • MeshWeaver.Markdown.Export.Test 113/113 — 8 red before the fix (both artefact tests + 6
    AST cases), 0 after.
  • Export e2e suites in MeshWeaver.Hosting.Monolith.Test 16/16 · MeshWeaver.Documentation.Test
    20/20.
  • -c Release -warnaserror clean on MeshWeaver.Markdown.Export and the test project.

The email/HTML export is unaffected and always was: it renders markdown through Markdig's own HTML
renderer rather than the document model, so ~~ has always produced <del> there. That matches the
issue's scope — PDF and DOCX only.

Overlap with #1372

Both touch Ast/DocumentBuilder.cs, but not the same code: #1372 changes the WalkBlocks
signature and AnchorFromInlines; this changes the EmphasisInline case in ReadInlinesInto and
adds a private helper before ExtractPlainText. The nearest hunks are ~11 lines apart in the
original file, so a 3-way merge takes both. Either order works — no sequencing needed.

🤖 Generated with Claude Code

ReadInlinesInto decided an emphasis span was strong from `DelimiterCount >= 2`.
Markdig's EmphasisExtras — on here via UseAdvancedExtensions — folds SIX
constructs into the one EmphasisInline node and tells them apart by the
delimiter CHARACTER, not the count. `~~` is doubled, so strikethrough satisfied
"strong" too and the run was emitted bold AND struck, in the PDF print
(<strong><s>…</s></strong>) and in Word (<w:b/> beside <w:strike/>) alike.

The same slip mis-styled four more, all of them invisible until you look:

  ++ins++   -> bold      (doubled delimiter)
  ==mark==  -> bold      (doubled delimiter)
  ~sub~     -> struck    (shares '~' with strikethrough)
  ^sup^     -> italic    (fell through to "not strong, not strike")

Emphasis is now keyed on DelimiterChar. Subscript, superscript, inserted and
marked have no representation in the document model (bold/italic/strike/code),
so they render as their plain text — content kept, invented emphasis dropped.
Giving them styles of their own is a model change, not this fix.

Verified on the ARTEFACT, not just the model — the complaint is about a word on
a page. Fail-before, read out of the produced files:

  PDF   the struck word's glyphs were drawn with DAAAAA+Arial-BoldMT
  DOCX  the struck run carried <w:b/>

and by rasterising both prints and looking at them: before, "Retired" is bold
and struck while sub/sup/ins/mark each wear a style; after, "Retired" is struck
at normal weight, real bold is still bold, bold-containing-strike keeps both,
and the four unrepresentable flavours print as plain text.

Both artefact tests carry a genuinely bold word in the SAME document as their
control, so "not bold" is a finding rather than an absence, and the DOCX
assertions read run properties out first instead of through `?.` — a
short-circuit there would have made them unfailable.

MeshWeaver.Markdown.Export.Test 113/113 (8 red before, 0 after), the export e2e
suites 16/16, MeshWeaver.Documentation.Test 20/20, -c Release -warnaserror clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 04:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Markdown export emphasis handling so strikethrough (~~) no longer incorrectly renders as bold + strike in PDF/DOCX by keying emphasis styling off Markdig’s EmphasisInline.DelimiterChar (not DelimiterCount). This aligns the export model with Markdig’s “EmphasisExtras” semantics and prevents other doubled-delimiter constructs (++, ==) from being misinterpreted as bold.

Changes:

  • Update DocumentBuilder.ReadInlinesInto to derive bold/italic/strike from DelimiterChar via a dedicated EmphasisStyle helper.
  • Extend AST-level tests to cover strikethrough plus the other emphasis-extra delimiters and nesting (***, **...~~...**).
  • Add artifact-level regression tests that assert styling in produced DOCX/PDF outputs; add a What’s New entry documenting the fix and behavioral change (unsupported emphasis types now render as plain text).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/MeshWeaver.Markdown.Export/Ast/DocumentBuilder.cs Fixes emphasis mapping by switching from count-based inference to delimiter-character-based styling (and centralizes the logic).
test/MeshWeaver.Markdown.Export.Test/DocumentBuilderTests.cs Adds model-level coverage for ~~, ++, ==, ~, ^, and nesting behaviors to prevent regressions.
test/MeshWeaver.Markdown.Export.Test/StrikethroughRenderTests.cs Adds end-to-end assertions on generated DOCX run properties and PDF font usage to validate the user-visible output.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-13-crossed-out-text-is-no-longer-also-bold.md Documents the fix and clarifies how unsupported emphasis constructs are now rendered in exports.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

723 tests  ±0   723 ✅ ±0   10m 53s ⏱️ -53s
 10 suites ±0     0 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   11 files  ±0     11 suites  ±0   5m 19s ⏱️ -30s
1 767 tests ±0  1 662 ✅ ±0  105 💤 ±0  0 ❌ ±0 
2 072 runs  +3  1 967 ✅ +3  105 💤 ±0  0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 973 tests  +14   1 965 ✅ +14   8m 28s ⏱️ -1s
   11 suites ± 0       8 💤 ± 0 
   11 files   ± 0       0 ❌ ± 0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 906 tests  ±0   1 714 ✅ ±0   5m 40s ⏱️ -8s
   11 suites ±0     192 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 453 tests  ±0   1 453 ✅ ±0   7m 43s ⏱️ +35s
   11 suites ±0       0 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

2 409 tests  ±0   2 408 ✅ ±0   6m 18s ⏱️ -24s
   12 suites ±0       1 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    66 files  ± 0      66 suites  ±0   44m 23s ⏱️ - 1m 19s
10 231 tests +14   9 925 ✅ +14  306 💤 ±0  0 ❌ ±0 
10 536 runs  +17  10 230 ✅ +17  306 💤 ±0  0 ❌ ±0 

Results for commit a0f776a. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@rbuergi

rbuergi commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

CI red on an unrelated live-model test — recorded so it is not re-derived

Run 31667783996 (shard 0),
one failing test, on the tree that already has origin/main @ 44c0a510d merged in:

MeshWeaver.Hosting.Orleans.Test.OrleansSubThreadAutoResumeTest
  .SubThreadDelegation_LiveProgressOnToolCall_TerminalSuccess_AndFcReentry

Expected 2 to be greater than or equal to 3 because sub-thread response cell must emit
≥3 distinct growing Text snapshots during streaming (saw 2).

It cannot be this diff. The assertion counts how many intermediate streaming writes a live
model
produced for one sub-thread round — a function of that run's chunking and timing. This PR
changes one private static method (DocumentBuilder.EmphasisStyle) reachable only from
DocumentBuilder.Build(...); the test never builds an export document. (MeshWeaver.Markdown.Export
is on the Orleans test project's reference list, so the assembly loads — but nothing calls into it
here.)

It belongs to an existing population, not to this branch. Scanning the last 10 failed
Build-and-Test runs across all branches: six failed, each on a different single integration test,
none related to the PR it ran on —
SilentReadNackTest, FrameworkStaleProactiveRebuildTest (×2), CodeCellOutputCaptureTest,
ThreadAgentIntegrationTest, LayoutAreaRetrievalTest / StaleActivationDurableFirstSeedTest /
OrleansGrainTeardownStragglerTest. The sibling PR #1381 hit the same shape on its first run
(DelegationSubThreadUsageTest, TimeoutException against claude-haiku-4-5) and went fully
green
on the next run after merging main — same tests, same diff.

Deliberately not re-run. Re-running a red test to see whether it goes green hides races
(AGENTS.md → "No band-aids"), and origin/main has already moved past what this branch merged
(e23dd02b3). The strict-required-status-checks policy requires this branch to be up to date at
merge time anyway, so the next git merge origin/main is both required and a genuinely changed
tree — that is the legitimate re-run, and it should be taken as part of sequencing the merge train
rather than manufactured now.

Local verification on the merged tree is unchanged: MeshWeaver.Markdown.Export.Test 113/113,
export e2e suites 16/16, MeshWeaver.Documentation.Test 20/20,
-c Release -warnaserror clean.

rbuergi and others added 2 commits August 13, 2026 13:32
…h-not-bold

# Conflicts:
#	test/MeshWeaver.Markdown.Export.Test/DocumentBuilderTests.cs
…w requires

A semantic conflict the 3-way merge could not see. #1372 gave PdfDocumentRenderer
an IoPoolRegistry parameter for the two-pass contents-page-number print; this
branch's artefact test predates it and constructed the renderer with the browser
alone, so the merged tree failed to compile (CS7036) while every text hunk merged
clean.

The test already owns a pools field for HeadlessChromiumPdfRenderer, so it passes
that one — the same shape RendererOutputTests and PdfHeaderLogoTests use.

The artefact assertion itself is untouched and still real: Chrome resolves on this
host, so Pdf_prints_the_word_struck_but_not_bold printed and read the fonts back
out rather than taking the no-browser branch.

MeshWeaver.Markdown.Export.Test 135/135, MeshWeaver.Documentation.Test 20/20,
-c Release -warnaserror clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi merged commit 9228a6d into main Aug 13, 2026
19 checks passed
@rbuergi
rbuergi deleted the fix/1373-strikethrough-not-bold branch August 19, 2026 10:33
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.

DocumentBuilder renders ~~strike~~ as bold + strike (affects PDF and DOCX export)

2 participants