@@ -232,13 +232,50 @@ private fun AnnotatedString.Builder.appendLinkedText(
232232 // Bold is the outermost inline layer: split on ** first so a bold span can wrap
233233 // inline code and links inside it. Odd-indexed segments sit between a pair of **
234234 // markers, so they're bold (markers dropped); even segments are ordinary prose.
235+ // Only the non-bold (even) segments get single-* italic handling — splitting a bold
236+ // segment on "*" would misfire on the leftover asterisks from the ** markers.
235237 val segments = text.split("**")
238+ // An even segment count means an odd number of ** markers, so the final ** is unclosed:
239+ // its trailing segment isn't bold. Restore the literal ** and render it as ordinary prose.
240+ val unclosed = segments.size % 2 == 0
236241 segments.forEachIndexed { index, segment ->
237− if (index % 2 == 1) {
242+ val isUnclosedTail = unclosed && index == segments.size - 1
243+ if (index % 2 == 1 && !isUnclosedTail) {
238244 withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
239245 appendInlineCode(segment, linkColor, codeBackground, context)
240246 }
241247 } else {
248+ if (isUnclosedTail) append("**")
249+ appendItalicText(segment, linkColor, codeBackground, context)
250+ }
251+ }
252+}
253+
254+/**
255+ * Append [text] to the builder, rendering single-asterisk pairs as italic. Double
256+ * asterisks are already consumed as bold upstream in [appendLinkedText], so only lone
257+ * `*` markers reach here. Each segment then flows through inline-code and URL handling.
258+ */
259+private fun AnnotatedString.Builder.appendItalicText(
260+ text: String,
261+ linkColor: Color,
262+ codeBackground: Color,
263+ context: Context,
264+) {
265+ // Odd-indexed segments sit between a pair of single asterisks, so they're italic
266+ // (markers dropped); even segments are ordinary prose.
267+ val segments = text.split("*")
268+ // An even segment count means an odd number of * markers, so the final * is unclosed:
269+ // its trailing segment isn't italic. Restore the literal * and render it as prose.
270+ val unclosed = segments.size % 2 == 0
271+ segments.forEachIndexed { index, segment ->
272+ val isUnclosedTail = unclosed && index == segments.size - 1
273+ if (index % 2 == 1 && !isUnclosedTail) {
274+ withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
275+ appendInlineCode(segment, linkColor, codeBackground, context)
276+ }
277+ } else {
278+ if (isUnclosedTail) append("*")
242279 appendInlineCode(segment, linkColor, codeBackground, context)
243280 }
244281 }
@@ -258,12 +295,17 @@ private fun AnnotatedString.Builder.appendInlineCode(
258295 // Odd-indexed segments sit between a pair of single backticks, so they're inline
259296 // code. Even segments are ordinary prose and get URL linking applied.
260297 val segments = text.split("`")
298+ // An even segment count means an odd number of backtick markers, so the final backtick
299+ // is unclosed: its trailing segment isn't code. Restore the literal ` and treat as prose.
300+ val unclosed = segments.size % 2 == 0
261301 segments.forEachIndexed { index, segment ->
262− if (index % 2 == 1) {
302+ val isUnclosedTail = unclosed && index == segments.size - 1
303+ if (index % 2 == 1 && !isUnclosedTail) {
263304 withStyle(SpanStyle(fontFamily = FontFamily.Monospace, background = codeBackground)) {
264305 append(segment)
265306 }
266307 } else {
308+ if (isUnclosedTail) append("`")
267309 appendUrls(segment, linkColor, context)
268310 }
269311 }