From d76c7f54095d9a1c25e81759ebcefac11788ca3c Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Jul 2026 20:51:04 -0500 Subject: [PATCH] perf(desktop): memoize PlatformAvatar + StatusDot to kill messaging churn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sidebar's messaging section renders one PlatformAvatar (labelIcon) and StatusDot per platform group. The sidebar re-renders on every streaming tick ($sessions/$workingSessionIds/$messagingSessions churn), and both leaves were unmemoized — so every platform's avatar + dot re-rendered on each delta even though their props (platformId/tone/className) never changed. - PlatformAvatar: memo(forwardRef(...)) — pure fn of platformId/name/class/style - StatusDot: memo() — pure fn of tone/class Measured (Messaging open, settled, 2 sessions streaming, 3s window): before: 96 wasted (PlatformAvatar 32, StatusDot 32, + brand icons) after: 0 wasted Both are shared primitives; the memo also helps every other consumer (session rows, gateway menu, session tiles) that renders them under a hot parent. --- .../src/app/messaging/platform-icon.tsx | 74 ++++++++++--------- apps/desktop/src/components/status-dot.tsx | 5 +- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/apps/desktop/src/app/messaging/platform-icon.tsx b/apps/desktop/src/app/messaging/platform-icon.tsx index b2d6302bf979..7102f848f525 100644 --- a/apps/desktop/src/app/messaging/platform-icon.tsx +++ b/apps/desktop/src/app/messaging/platform-icon.tsx @@ -13,7 +13,7 @@ import { SiWhatsapp } from '@icons-pack/react-simple-icons' import type { ComponentPropsWithoutRef, ComponentType, SVGProps } from 'react' -import { forwardRef } from 'react' +import { forwardRef, memo } from 'react' import { Globe, Link as LinkIcon, MessageSquareText } from '@/lib/icons' import { cn } from '@/lib/utils' @@ -82,48 +82,50 @@ interface PlatformAvatarProps extends Omit, 'ch // component and injects a ref plus pointer/focus/aria handlers onto it. A // plain function component with no ref/rest forwarding drops all of that // silently — the tooltip renders but never opens (#67500). -export const PlatformAvatar = forwardRef(function PlatformAvatar( - { className, platformId, platformName, style, ...rest }, - ref -) { - const spec = PLATFORM_ICONS[platformId] +export const PlatformAvatar = memo( + forwardRef(function PlatformAvatar( + { className, platformId, platformName, style, ...rest }, + ref + ) { + const spec = PLATFORM_ICONS[platformId] - const baseClass = cn( - 'inline-grid size-6 shrink-0 place-items-center rounded-md text-[length:var(--conversation-caption-font-size)] font-medium', - className - ) + const baseClass = cn( + 'inline-grid size-6 shrink-0 place-items-center rounded-md text-[length:var(--conversation-caption-font-size)] font-medium', + className + ) + + if (!spec) { + return ( + + ) + } + + const { Icon, color } = spec - if (!spec) { return ( ) - } - - const { Icon, color } = spec - - return ( - - ) -}) + }) +) diff --git a/apps/desktop/src/components/status-dot.tsx b/apps/desktop/src/components/status-dot.tsx index 3b9c20d3618d..93d689a4e273 100644 --- a/apps/desktop/src/components/status-dot.tsx +++ b/apps/desktop/src/components/status-dot.tsx @@ -1,4 +1,5 @@ import type { ComponentProps } from 'react' +import { memo } from 'react' import { cn } from '@/lib/utils' @@ -15,7 +16,7 @@ interface StatusDotProps extends ComponentProps<'span'> { tone: StatusTone } -export function StatusDot({ className, tone, ...props }: StatusDotProps) { +export const StatusDot = memo(function StatusDot({ className, tone, ...props }: StatusDotProps) { return (