Merge pull request #3638 from vitorpamplona/claude/post-image-render-shift-b4vx28

Fix media detection for malformed imeta MIME types
This commit is contained in:
Vitor Pamplona
2026-07-18 14:04:00 -04:00
committed by GitHub
2 changed files with 76 additions and 4 deletions
@@ -60,9 +60,9 @@ class RichTextParser {
val contentType = frags[MimeTypeTag.TAG_NAME] ?: tags[MimeTypeTag.TAG_NAME]?.firstOrNull()
val isImage: Boolean
val isVideo: Boolean
val isPdf: Boolean
var isImage = false
var isVideo = false
var isPdf = false
if (contentType != null) {
isImage = contentType.startsWith("image/")
@@ -77,7 +77,17 @@ class RichTextParser {
isImage = fullUrl.startsWith("data:image/")
isVideo = fullUrl.startsWith("data:video/") || fullUrl.startsWith("data:audio/")
isPdf = fullUrl.startsWith("data:application/pdf")
} else {
}
// Fall back to file-extension detection when the type is still unknown. This covers both
// the no-MIME case and a *malformed* imeta MIME — e.g. Primal iOS emits `m jpeg` instead
// of `m image/jpeg`, which matches none of the `startsWith` prefixes above. Without this
// fallback such a URL returns null and drops to a plain link: that discards the imeta
// `dim`/blurhash (so the loading placeholder can't reserve the image's height and the
// feed jumps once the bitmap arrives) and forces a needless URL-preview network
// round-trip just to rediscover the type the imeta already declared. `data:` URIs carry
// their type in the prefix, so a miss there is genuine — don't extension-probe them.
if (!isImage && !isVideo && !isPdf && !fullUrl.startsWith("data:")) {
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
isVideo = videoExtensions.any { removedParamsFromUrl.endsWith(it) }
@@ -66,4 +66,66 @@ class PdfParserTest {
assertTrue(RichTextParser.isPdfUrl("https://example.com/doc.PDF"))
assertTrue(RichTextParser.isPdfUrl("https://example.com/doc.pdf?sig=abc"))
}
// Primal iOS writes a bare subtype (`m jpeg`) instead of a full MIME (`m image/jpeg`).
// The bare subtype matches none of the `image/`/`video/`/`application/pdf` prefixes, so before
// the extension fallback the whole imeta was dropped: the URL rendered as a plain link, losing
// the `dim` needed to reserve the image's height (feed jump) and forcing a URL-preview fetch.
// The `.jpg` extension must still route it to a MediaUrlImage that carries `dim`.
@Test
fun detectsImageFromMalformedImetaMimeWithImageExtension() {
val url = "https://blossom.primal.net/33e7c01afbea894a64e1db44dece460b09a2426108f47143754e1cf4bfdf747c.jpg"
val content = "\n$url"
val tags =
ImmutableListOfLists(
arrayOf(
arrayOf("imeta", "url $url", "m jpeg", "dim 1009.0x680.0"),
arrayOf("client", "Primal iOS"),
),
)
val state = RichTextParser().parseText(content, tags, null)
val imageMedia = state.mediaForPager[url]
assertTrue(imageMedia is MediaUrlImage, "Expected MediaUrlImage despite the malformed `m jpeg` mime")
assertEquals("1009x680", imageMedia.dim?.toString(), "The imeta dim must survive so the loader can reserve space")
assertEquals(1009f / 680f, imageMedia.dim?.aspectRatio())
val segment = state.paragraphs.flatMap { it.words }.first { it.segmentText == url }
assertTrue(segment is ImageSegment, "Expected ImageSegment, got ${segment::class.simpleName}")
}
// A malformed video subtype (`m mp4`) must likewise fall back to the `.mp4` extension.
@Test
fun detectsVideoFromMalformedImetaMimeWithVideoExtension() {
val url = "https://cdn.example.com/clip.mp4"
val tags =
ImmutableListOfLists(
arrayOf(
arrayOf("imeta", "url $url", "m mp4"),
),
)
val state = RichTextParser().parseText(url, tags, null)
val videoMedia = state.mediaForPager[url]
assertTrue(videoMedia is MediaUrlVideo, "Expected MediaUrlVideo despite the malformed `m mp4` mime")
}
// A malformed mime on a URL with *no* recognizable extension can't be recovered — it stays a
// link. This documents the boundary of the fallback so it isn't mistaken for a regression.
@Test
fun malformedImetaMimeWithoutExtensionStaysUnclassified() {
val url = "https://files.example.com/abcd1234"
val tags =
ImmutableListOfLists(
arrayOf(
arrayOf("imeta", "url $url", "m jpeg"),
),
)
val state = RichTextParser().parseText(url, tags, null)
assertEquals(null, state.mediaForPager[url], "No extension to recover from -> not treated as media")
}
}