mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
perf(desktop): memoize PlatformAvatar + StatusDot to kill messaging churn
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.
This commit is contained in:
parent
f088fa5070
commit
d76c7f5409
2 changed files with 41 additions and 38 deletions
|
|
@ -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<ComponentPropsWithoutRef<'span'>, '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<HTMLSpanElement, PlatformAvatarProps>(function PlatformAvatar(
|
||||
{ className, platformId, platformName, style, ...rest },
|
||||
ref
|
||||
) {
|
||||
const spec = PLATFORM_ICONS[platformId]
|
||||
export const PlatformAvatar = memo(
|
||||
forwardRef<HTMLSpanElement, PlatformAvatarProps>(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 (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(baseClass, 'bg-(--ui-bg-tertiary) text-(--ui-text-tertiary)')}
|
||||
ref={ref}
|
||||
style={style}
|
||||
{...rest}
|
||||
>
|
||||
{platformName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const { Icon, color } = spec
|
||||
|
||||
if (!spec) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(baseClass, 'bg-(--ui-bg-tertiary) text-(--ui-text-tertiary)')}
|
||||
className={baseClass}
|
||||
ref={ref}
|
||||
style={style}
|
||||
style={{
|
||||
// 16% tint of the brand color so the glyph reads against any surface
|
||||
// without the avatar dominating the row.
|
||||
backgroundColor: `color-mix(in srgb, ${color} 16%, transparent)`,
|
||||
color,
|
||||
...style
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{platformName.charAt(0).toUpperCase()}
|
||||
{Icon ? <Icon className="size-3.5" /> : spec.monogram || platformName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const { Icon, color } = spec
|
||||
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={baseClass}
|
||||
ref={ref}
|
||||
style={{
|
||||
// 16% tint of the brand color so the glyph reads against any surface
|
||||
// without the avatar dominating the row.
|
||||
backgroundColor: `color-mix(in srgb, ${color} 16%, transparent)`,
|
||||
color,
|
||||
...style
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{Icon ? <Icon className="size-3.5" /> : spec.monogram || platformName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
|
@ -23,4 +24,4 @@ export function StatusDot({ className, tone, ...props }: StatusDotProps) {
|
|||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue