diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/Markdown.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/Markdown.kt index 91dbf0a..7a2448e 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/Markdown.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/Markdown.kt @@ -3,7 +3,10 @@ package com.example.aiapp import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.TextUnit import com.mikepenz.markdown.m3.Markdown import com.mikepenz.markdown.m3.markdownColor import com.mikepenz.markdown.m3.markdownTypography @@ -13,31 +16,75 @@ import com.mikepenz.markdown.m3.markdownTypography * * The parsing is the library's. Markdown is somebody else's specification, and a hand-written * subset of one disagrees with it at the edges -- which is where the bug reports come from, one - * case at a time. This file's whole job is the mapping onto the app's palette. + * case at a time. This file's whole job is the mapping onto the app's palette and type scale. * * Colours come from the theme rather than from the renderer's defaults, so code, links and rules * are the same Catppuccin values the rest of the app uses. Nothing here picks a colour of its own. */ @Composable fun MarkdownText(text: String, modifier: Modifier = Modifier) { + val body = MaterialTheme.typography.bodyLarge Markdown( content = text, colors = markdownColor( text = MaterialTheme.colorScheme.onSurface, - codeText = codeColor, - inlineCodeText = codeColor, - linkText = linkColor, dividerColor = MaterialTheme.colorScheme.outlineVariant, codeBackground = MaterialTheme.colorScheme.surfaceVariant, inlineCodeBackground = MaterialTheme.colorScheme.surfaceVariant, + // The same tint a code block gets, rather than the renderer's 2%-alpha default: + // two adjacent tints that differ by a fiftieth read as one flat block on a phone, + // so the table would have had a border-less grid and nothing saying where it began. + tableBackground = MaterialTheme.colorScheme.surfaceVariant, ), - // Body text at the size everything else in the transcript uses, and code in a monospace - // face: a code block set in the body font stops looking like code at all. typography = markdownTypography( - text = MaterialTheme.typography.bodyLarge, - code = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace), + // A ladder that starts near the body text and descends, because these are headings + // inside a chat message rather than the top of a document. The renderer's defaults + // are the Material *display* styles -- `#` came out at 57sp and `##` at 45sp, which + // is bigger than this app's own screen titles and reads as the reply shouting. + // + // Every step is a different size, so two levels of nesting never draw the same: + // one clear step per level is the whole job of a heading. + h1 = MaterialTheme.typography.headlineSmall, + h2 = MaterialTheme.typography.titleLarge, + h3 = MaterialTheme.typography.titleMedium, + h4 = MaterialTheme.typography.titleSmall, + h5 = MaterialTheme.typography.labelMedium, + h6 = MaterialTheme.typography.labelSmall, + // Body text at the size everything else in the transcript uses. + text = body, + paragraph = body, + ordered = body, + bullet = body, + list = body, + table = body, + // Code in a monospace face: a code block set in the body font stops looking like + // code at all. The colour rides on the style here rather than in `markdownColor`, + // which stopped carrying `codeText`/`inlineCodeText`/`linkText` when the renderer + // moved them onto the typography. + code = + MaterialTheme.typography.bodyMedium.copy( + fontFamily = FontFamily.Monospace, + color = codeColor, + ), + inlineCode = + body.copy( + fontFamily = FontFamily.Monospace, + // Unspecified so an inline span keeps the size of the line it sits in. + fontSize = TextUnit.Unspecified, + color = codeColor, + ), + textLink = + TextLinkStyles( + style = + body + .copy( + color = linkColor, + textDecoration = TextDecoration.Underline, + ) + .toSpanStyle() + ), ), modifier = modifier, ) diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml index 4d16b50..caf8991 100644 --- a/app/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -16,9 +16,16 @@ zxing-embedded = "4.3.0" # Markdown rendering for assistant replies. The widely-used Compose # Multiplatform renderer; markdown is somebody else's specification and a # hand-written subset disagrees with it at the edges, one bug report at a -# time. Latest stable, checked 2026-08-29 against Maven Central -- 0.27.0 -# exists only as release candidates. -markdown-renderer = "0.26.0" +# time. +# +# Read the version from `maven-metadata.xml`, not from the search API: +# `search.maven.org/solrsearch` still answers 0.26.0 with 0.27.0 only as +# release candidates, which is what pinned this nineteen releases behind +# and cost us table support -- tables arrived in 0.30.0 and simply +# rendered as run-together text until then. +# https://repo1.maven.org/maven2/com/mikepenz/multiplatform-markdown-renderer-m3/maven-metadata.xml +# Latest stable there, checked 2026-08-29. +markdown-renderer = "0.45.0" # Syntax highlighting for a tool call's input. Same reasoning as the markdown # renderer: a language's lexical rules are somebody else's specification. # Latest stable, checked 2026-08-29 against Maven Central.