From 3cef92e82ffb2967ea283bf2ededaa50170426de Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:13:19 +0300 Subject: [PATCH] Show identicons for breadcrumbs to match with user profile avatar --- app/components/Avatar.tsx | 2 +- app/components/layout/index.tsx | 20 +++++++++++++++++-- .../user-page/routes/u.$identifier.tsx | 1 + app/utils/remix.server.ts | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/components/Avatar.tsx b/app/components/Avatar.tsx index dec646b98..005469895 100644 --- a/app/components/Avatar.tsx +++ b/app/components/Avatar.tsx @@ -42,7 +42,7 @@ function generateColors(hash: number) { }; } -function generateIdenticon(input: string, size = 128, gridSize = 5) { +export function generateIdenticon(input: string, size = 128, gridSize = 5) { const cacheKey = `${input}-${size}-${gridSize}`; const cached = identiconCache.get(cacheKey); if (cached) return cached; diff --git a/app/components/layout/index.tsx b/app/components/layout/index.tsx index e1c634261..b8dee259b 100644 --- a/app/components/layout/index.tsx +++ b/app/components/layout/index.tsx @@ -40,7 +40,7 @@ import { SETTINGS_PAGE, userPage, } from "~/utils/urls"; -import { Avatar } from "../Avatar"; +import { Avatar, generateIdenticon } from "../Avatar"; import { SendouButton } from "../elements/Button"; import { SendouPopover } from "../elements/Popover"; import { FuseZone } from "../fuse/Fuse"; @@ -601,6 +601,9 @@ function SideNavCollapseButton({ } function PageIcon({ crumb }: { crumb: Breadcrumb }) { + const [isErrored, setIsErrored] = React.useState(false); + const isClient = useHydrated(); + if (crumb.type !== "IMAGE") { return null; } @@ -609,15 +612,28 @@ function PageIcon({ crumb }: { crumb: Breadcrumb }) { const isExternal = lastPathSegment.includes("."); const iconClass = clsx(styles.pageIcon, "rounded"); + // an can finish loading (and fail) before React hydrates and attaches onError, so that + // error is missed — re-check on mount and fall back manually so SSR'd icons still heal + const checkAlreadyErrored = (img: HTMLImageElement | null) => { + if (img?.complete && img.naturalWidth === 0) setIsErrored(true); + }; + + const identiconSrc = + isErrored && isClient && crumb.identiconInput + ? generateIdenticon(crumb.identiconInput, 28, 7) + : null; + return (
{isExternal ? ( setIsErrored(true)} /> ) : (