Commit ede847ef53

ede847ef533caabe59ee413b870879ff3728e31d

parent: 8c99ef194e

Unsigned

cmc <hello@cleberg.net> · 2026-07-16 00:29 UTC

fix: render code span contents literally

processInline protected allowlisted HTML tags before it handled code spans, so
a `<b>` written inside backticks was carried through as a live tag and applied
formatting instead of rendering as text. Every other inline pass ran against
code span contents for the same reason, so `**x**` in backticks was emitted as
bold.

Protect code spans first with their contents escaped, which takes them out of
reach of the tag, emphasis, and link passes.

Layout: unified · split

Hutch/Views/Repositories/ReadmeView.swift +13 −7
@@ -371,8 +371,21 @@ nonisolated func processInline(
371) -> String { 371) -> String {
372 372
373 var protectedFragments: [String: String] = [:] 373 var protectedFragments: [String: String] = [:]
374
375 // Code spans render their contents literally, so they have to be taken out of
376 // the text before any later pass can treat those contents as markup — the tag
377 // pass below would otherwise promote an allowlisted `<b>` into a live tag.
374 var result = protectMatches( 378 var result = protectMatches(
375 in: text, 379 in: text,
380 pattern: #"`([^`]+)`"#,
381 protectedFragments: &protectedFragments
382 ) { match, nsText in
383 let code = nsText.substring(with: match.range(at: 1))
384 return "<code>\(escapeHTML(code))</code>"
385 }
386
387 result = protectMatches(
388 in: result,
376 pattern: #"</?[A-Za-z][^>]*?>"#, 389 pattern: #"</?[A-Za-z][^>]*?>"#,
377 protectedFragments: &protectedFragments 390 protectedFragments: &protectedFragments
378 ) { match, nsText in 391 ) { match, nsText in
@@ -438,13 +451,6 @@ nonisolated func processInline(
438 with: "<em>$1</em>", 451 with: "<em>$1</em>",
439 options: .regularExpression 452 options: .regularExpression
440 ) 453 )
441 // Inline code: `text`
442 result = result.replacingOccurrences(
443 of: #"`([^`]+)`"#,
444 with: "<code>$1</code>",
445 options: .regularExpression
446 )
447
448 for (token, fragment) in protectedFragments { 454 for (token, fragment) in protectedFragments {
449 result = result.replacingOccurrences(of: token, with: fragment) 455 result = result.replacingOccurrences(of: token, with: fragment)
450 } 456 }