From 1511c4b783a2d6c4441e3feb9239f4431cf01df1 Mon Sep 17 00:00:00 2001 From: Salastil Date: Fri, 11 Sep 2026 21:38:51 -0400 Subject: [PATCH] An avif link is drawn as the picture it is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A message carrying an https://…/photo.avif showed the URL. Nothing was broken underneath: the content-type probe already accepts any image/*, and the daemon's byte sniffer has known the avif brand from the start. It was the extension list - the one thing that decides whether a *link* is worth embedding - that had been written before avif was a format people posted, and never revisited. So avif joins png, jpeg, gif, webp and bmp there, and three places that had drifted from it come along: - The animated-format check, because an avif can be animated too, and the "don't play animations until asked" preference only means anything for formats it has heard of. - The notification icon's declared type, which had a RIFF/WEBP arm and no ftyp one. Only the avif brand is claimed, not HEIC: Chromium decodes the first and not the second, and naming a file it cannot draw promises a picture that never appears. - The daemon (nobilis 4e3c65d), for Matrix's mimetype map and Sneedchat's attachment list. Checked against the real functions rather than a copy of the regex: a bare link, one with a query string, an uppercase extension, one inside [img], and alongside the formats that always worked - plus that a .pdf is still not a picture. Found while checking: `isImageFile` counts .avif and routes an upload to the images host, which for Sneedchat is postimg, which refuses avif - the exact mismatch the comment above that list warns about. Left alone here because it is the sending side and a separate decision. Co-Authored-By: Claude Opus 5 --- nobilis | 2 +- src/main/notifications.ts | 8 ++++++++ src/renderer/src/components/MediaEmbed.tsx | 2 +- src/renderer/src/lib/format.ts | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nobilis b/nobilis index dfda74d..4e3c65d 160000 --- a/nobilis +++ b/nobilis @@ -1 +1 @@ -Subproject commit dfda74d1e4e2437a5027d0c62246129068f0d98d +Subproject commit 4e3c65dcfe02bf985048d65c2ecd69705eaec38e diff --git a/src/main/notifications.ts b/src/main/notifications.ts index bd30b08..385484a 100644 --- a/src/main/notifications.ts +++ b/src/main/notifications.ts @@ -159,6 +159,14 @@ export class Notifier { if (starts(0x52, 0x49, 0x46, 0x46) && bytes.subarray(8, 12).toString() === 'WEBP') { return 'image/webp' } + // ISO-BMFF: AVIF and HEIC share the ftyp box four bytes in and are told + // apart by the brand after it. Only avif is claimed here - Chromium + // decodes that one and does not decode HEIC, so calling a HEIC file by + // its real name would promise a picture that never appears. + if (bytes.subarray(4, 8).toString() === 'ftyp') { + const brand = bytes.subarray(8, 12).toString() + if (brand === 'avif' || brand === 'avis') return 'image/avif' + } // Something else entirely. Chromium sniffs the bytes of an either // way, so a wrong-but-plausible type still decodes what it can, and what // it cannot decode falls out of the null below rather than here. diff --git a/src/renderer/src/components/MediaEmbed.tsx b/src/renderer/src/components/MediaEmbed.tsx index dccabc7..fde12eb 100644 --- a/src/renderer/src/components/MediaEmbed.tsx +++ b/src/renderer/src/components/MediaEmbed.tsx @@ -225,7 +225,7 @@ export function MediaEmbed({ // Browsers give no pause control over an animated GIF, so "don't autoplay" // is expressed by not loading it until asked. const animated = - attachment?.mimetype === 'image/gif' || /\.(gif|webp)(\?\S*)?$/i.test(fullSrc) + attachment?.mimetype === 'image/gif' || /\.(gif|webp|avif)(\?\S*)?$/i.test(fullSrc) if (animated && !autoplay && !playing) { return (