From b8a53dcc0900c2c066552940fda275997dd48501 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sat, 30 May 2026 20:58:31 +0300 Subject: [PATCH] perf(renderer): reduce member card render work --- .../components/team/members/MemberCard.tsx | 91 +++++-------------- .../components/team/members/MemberList.tsx | 17 +++- .../team/members/MemberCard.test.ts | 15 ++- 3 files changed, 45 insertions(+), 78 deletions(-) diff --git a/src/renderer/components/team/members/MemberCard.tsx b/src/renderer/components/team/members/MemberCard.tsx index 034b0373..c0b69d2c 100644 --- a/src/renderer/components/team/members/MemberCard.tsx +++ b/src/renderer/components/team/members/MemberCard.tsx @@ -7,13 +7,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useTheme } from '@renderer/hooks/useTheme'; import { cn } from '@renderer/lib/utils'; -import { useStore } from '@renderer/store'; -import { selectResolvedMembersForTeamName } from '@renderer/store/slices/teamSlice'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { renderLinkifiedText } from '@renderer/utils/linkifiedText'; import { agentAvatarUrl, - buildMemberAvatarMap, buildMemberLaunchPresentation, displayMemberName, isOpenCodeRelaunchActionable, @@ -72,8 +69,10 @@ export interface RuntimeTelemetryScale { } interface MemberCardProps { + teamName?: string; member: ResolvedTeamMember; memberColor: string; + avatarUrl?: string; fullBleedSurface?: boolean; runtimeSummary?: string; runtimeEntry?: TeamAgentRuntimeEntry; @@ -301,53 +300,8 @@ function normalizeRuntimeTelemetrySamples(history: unknown): TeamAgentRuntimeRes return (Array.isArray(history) ? history : []).filter(isRuntimeTelemetrySampleLike); } -function buildRuntimeTelemetryTitle( - runtimeEntry: TeamAgentRuntimeEntry | undefined -): string | undefined { - if (!runtimeEntry) { - return undefined; - } - if (normalizeRuntimeTelemetrySamples(runtimeEntry?.resourceHistory).length === 0) { - return undefined; - } - - const lines = [ - 'CPU includes parent + child processes.', - 'Local CPU excludes remote LLM inference.', - ]; - if (runtimeEntry.runtimeLoadScope === 'shared-host') { - lines.push('Shared OpenCode host metric; not exclusive to this member.'); - } - if (runtimeEntry.runtimeLoadTruncated) { - lines.push('Process tree was capped for this sample.'); - } - - const detailParts = [ - runtimeEntry.pid ? `root PID ${runtimeEntry.pid}` : undefined, - runtimeEntry.processCount ? `${runtimeEntry.processCount} processes` : undefined, - runtimeEntry.runtimeLoadScope ? `scope ${runtimeEntry.runtimeLoadScope}` : undefined, - 'sample 5s', - ].filter((part): part is string => Boolean(part)); - if (detailParts.length > 0) { - lines.push(detailParts.join(' · ')); - } - - const aggregateCpuLabel = formatRuntimeTelemetryPercent(runtimeEntry.cpuPercent); - const primaryCpuLabel = formatRuntimeTelemetryPercent(runtimeEntry.primaryCpuPercent); - const childCpuLabel = formatRuntimeTelemetryPercent(runtimeEntry.childCpuPercent); - const rssLabel = formatRuntimeTelemetryBytes(runtimeEntry.rssBytes); - const splitParts = [ - aggregateCpuLabel ? `CPU ${aggregateCpuLabel}` : undefined, - primaryCpuLabel ? `root ${primaryCpuLabel}` : undefined, - childCpuLabel ? `children ${childCpuLabel}` : undefined, - rssLabel ? `RSS ${rssLabel}` : undefined, - ].filter((part): part is string => Boolean(part)); - if (splitParts.length > 0) { - lines.push(splitParts.join(' · ')); - } - - lines.push('RSS is summed process RSS and can include shared pages.'); - return lines.join('\n'); +function hasRuntimeTelemetrySamples(history: unknown): boolean { + return Array.isArray(history) && history.some(isRuntimeTelemetrySampleLike); } const RuntimeTelemetryTooltipContent = ({ @@ -613,8 +567,10 @@ const MemberRuntimeTelemetryStrip = memo(function MemberRuntimeTelemetryStrip({ }); export const MemberCard = memo(function MemberCard({ + teamName, member, memberColor, + avatarUrl, fullBleedSurface = true, runtimeSummary, runtimeEntry, @@ -654,17 +610,12 @@ export const MemberCard = memo(function MemberCard({ // const leadContext = useStore((s) => // member.agentType === 'team-lead' && teamName ? s.leadContextByTeam[teamName] : undefined // ); - const selectedTeamName = useStore((s) => s.selectedTeamName); const [retryingLaunch, setRetryingLaunch] = useState(false); const [retryLaunchError, setRetryLaunchError] = useState(null); const [skippingLaunch, setSkippingLaunch] = useState(false); const [skipLaunchError, setSkipLaunchError] = useState(null); const [restoringMember, setRestoringMember] = useState(false); const [restoreMemberError, setRestoreMemberError] = useState(null); - const teamMembers = useStore((s) => - selectedTeamName ? selectResolvedMembersForTeamName(s, selectedTeamName) : [] - ); - const avatarMap = useMemo(() => buildMemberAvatarMap(teamMembers), [teamMembers]); const bootstrapConfirmedProvisionedButNotAlive = isBootstrapConfirmedProvisionedButNotAliveFailure(spawnEntry); const hasUnsafeBootstrapConfirmedProvisionedButNotAlive = @@ -759,8 +710,10 @@ export const MemberCard = memo(function MemberCard({ : visibleReviewTask ? `Reviewing task: #${deriveTaskDisplayId(visibleReviewTask.id)}` : undefined; - const runtimeTelemetryTitle = buildRuntimeTelemetryTitle(runtimeEntry); - const showRuntimeTelemetryTooltip = Boolean(runtimeTelemetryTitle); + const showRuntimeTelemetryTooltip = useMemo( + () => hasRuntimeTelemetrySamples(runtimeEntry?.resourceHistory), + [runtimeEntry?.resourceHistory] + ); const rowTitle = showRuntimeTelemetryTooltip ? undefined : activityTitle; const runtimeTelemetryTooltipIdRef = useRef(null); if (runtimeTelemetryTooltipIdRef.current == null) { @@ -881,7 +834,7 @@ export const MemberCard = memo(function MemberCard({ const launchDiagnosticsPayload = useMemo( () => buildMemberLaunchDiagnosticsPayload({ - teamName: selectedTeamName, + teamName, runId: runtimeRunId, memberName: member.name, member, @@ -900,7 +853,7 @@ export const MemberCard = memo(function MemberCard({ runtimeAdvisoryLabel, runtimeAdvisoryTitle, runtimeRunId, - selectedTeamName, + teamName, spawnEntry, effectiveSpawnLaunchState, spawnLivenessSource, @@ -1077,7 +1030,7 @@ export const MemberCard = memo(function MemberCard({ }} > {member.name} {cardContent} - - - + {runtimeTelemetryTooltipOpen ? ( + + + + ) : null} ); }); diff --git a/src/renderer/components/team/members/MemberList.tsx b/src/renderer/components/team/members/MemberList.tsx index 5f6ea327..66fa2901 100644 --- a/src/renderer/components/team/members/MemberList.tsx +++ b/src/renderer/components/team/members/MemberList.tsx @@ -7,7 +7,11 @@ import { deriveWorkActivityTimerAnchor, syncMemberActivityTimer, } from '@renderer/utils/memberActivityTimer'; -import { buildMemberColorMap, shouldDisplayMemberCurrentTask } from '@renderer/utils/memberHelpers'; +import { + buildMemberAvatarMap, + buildMemberColorMap, + shouldDisplayMemberCurrentTask, +} from '@renderer/utils/memberHelpers'; import { resolveMemberRuntimeSummary } from '@renderer/utils/memberRuntimeSummary'; import { isDisplayableCurrentTask } from '@renderer/utils/teamTaskDisplayState'; import { isLeadMember } from '@shared/utils/leadDetection'; @@ -480,9 +484,11 @@ function areMemberListPropsEqual( // --------------------------------------------------------------------------- interface MemberCardRowProps { + teamName: string; member: ResolvedTeamMember; isRemoved: boolean; memberColor: string; + avatarUrl?: string; fullBleedSurface: boolean; currentTask: TeamTaskWithKanban | null; reviewTask: TeamTaskWithKanban | null; @@ -516,9 +522,11 @@ interface MemberCardRowProps { } const MemberCardRow = memo(function MemberCardRow({ + teamName, member, isRemoved, memberColor, + avatarUrl, fullBleedSurface, currentTask, reviewTask, @@ -567,8 +575,10 @@ const MemberCardRow = memo(function MemberCardRow({ return ( buildMemberColorMap(members), [members]); + const avatarMap = useMemo(() => buildMemberAvatarMap(members), [members]); const runtimeTelemetryScale = useMemo( () => buildRuntimeTelemetryScale(activeMembers, memberRuntimeEntries), [activeMembers, memberRuntimeEntries] @@ -1012,9 +1023,11 @@ export const MemberList = memo(function MemberList({ return ( ( { React.createElement(MemberCard, { member, memberColor: 'blue', + avatarUrl: 'https://example.com/alice.png', isTeamAlive: true, isTeamProvisioning: false, }) @@ -719,6 +720,7 @@ describe('MemberCard starting-state visuals', () => { const clickableCard = host.querySelector('[role="button"]') as HTMLElement | null; expect(avatarRing).not.toBeNull(); + expect(img?.getAttribute('src')).toBe('https://example.com/alice.png'); expect(avatarRing?.style.borderColor).toBe('#3b82f6'); expect(clickableCard?.style.borderLeft).toBe(''); expect(clickableCard?.style.background).toBe(''); @@ -878,18 +880,13 @@ describe('MemberCard starting-state visuals', () => { const runtimeTooltipContent = Array.from( host.querySelectorAll('[data-testid="tooltip-content"]') ).find((content) => content.className.includes('border-blue-400/20')); - expect(runtimeTooltipContent?.getAttribute('data-side')).toBe('left'); expect(host.querySelector('[data-testid="tooltip-root"]')?.getAttribute('data-open')).toBe( 'false' ); - expect(host.textContent).toContain('Local runtime load'); - expect(host.textContent).toContain('Parent and child processes only.'); - expect(host.textContent).toContain('root PID 222'); - expect(host.textContent).toContain('3 processes'); - expect(host.textContent).toContain('CPU'); - expect(host.textContent).toContain('14%'); - expect(host.textContent).toContain('Memory'); - expect(host.textContent).toContain('238 MB'); + expect(runtimeTooltipContent).toBeUndefined(); + expect(host.textContent).not.toContain('Local runtime load'); + expect(host.textContent).not.toContain('Parent and child processes only.'); + expect(host.textContent).not.toContain('root PID 222'); await act(async () => { root.unmount();