From 225b28199a6f23adfebca40c9843c2dc0174ef2d Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 30 Aug 2026 23:04:17 -0700 Subject: [PATCH] perf(renderer): avoid remote PTY selector allocations (#17454) --- .../src/lib/tab-agent-remote-pty-selector.ts | 23 +++++++ .../use-tab-agent-remote-pty-selector.test.ts | 60 +++++++++++++++++++ src/renderer/src/lib/use-tab-agent.ts | 16 +++-- 3 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/lib/tab-agent-remote-pty-selector.ts create mode 100644 src/renderer/src/lib/use-tab-agent-remote-pty-selector.test.ts diff --git a/src/renderer/src/lib/tab-agent-remote-pty-selector.ts b/src/renderer/src/lib/tab-agent-remote-pty-selector.ts new file mode 100644 index 00000000000..69091611d47 --- /dev/null +++ b/src/renderer/src/lib/tab-agent-remote-pty-selector.ts @@ -0,0 +1,23 @@ +import { parseRemoteRuntimePtyId } from '@/runtime/runtime-terminal-stream' + +/** Checks both PTY projections without allocating a dedupe set on each store notification. */ +export function hasRemoteRuntimePtyForTab( + tabPtyIds: readonly string[] | undefined, + leafPtyIdsById: Readonly> | undefined +): boolean { + if (tabPtyIds?.some((ptyId) => parseRemoteRuntimePtyId(ptyId) !== null)) { + return true + } + if (!leafPtyIdsById) { + return false + } + for (const leafId in leafPtyIdsById) { + if ( + Object.hasOwn(leafPtyIdsById, leafId) && + parseRemoteRuntimePtyId(leafPtyIdsById[leafId]!) !== null + ) { + return true + } + } + return false +} diff --git a/src/renderer/src/lib/use-tab-agent-remote-pty-selector.test.ts b/src/renderer/src/lib/use-tab-agent-remote-pty-selector.test.ts new file mode 100644 index 00000000000..8f7c870fc91 --- /dev/null +++ b/src/renderer/src/lib/use-tab-agent-remote-pty-selector.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it, vi } from 'vitest' +import { hasRemoteRuntimePtyForTab } from './tab-agent-remote-pty-selector' + +const REMOTE_PTY = 'remote:environment-1@@terminal-1' + +describe('hasRemoteRuntimePtyForTab', () => { + it('short-circuits on a tab-level remote PTY', () => { + let leafReads = 0 + const leafPtyIdsById = new Proxy( + { leaf: REMOTE_PTY }, + { + ownKeys: () => { + leafReads += 1 + return ['leaf'] + } + } + ) + + expect(hasRemoteRuntimePtyForTab([REMOTE_PTY], leafPtyIdsById)).toBe(true) + expect(leafReads).toBe(0) + }) + + it('finds a remote PTY that is present only in the layout projection', () => { + expect( + hasRemoteRuntimePtyForTab(['local-pty'], { first: 'local-pty', second: REMOTE_PTY }) + ).toBe(true) + }) + + it('ignores inherited layout properties like Object.values did', () => { + const inherited = { inherited: REMOTE_PTY } + const leafPtyIdsById = Object.create(inherited) as Record + leafPtyIdsById.local = 'local-pty' + + expect(hasRemoteRuntimePtyForTab(['local-pty'], leafPtyIdsById)).toBe(false) + }) + + it('avoids transient Set and Object.values allocations across repeated checks', () => { + const originalSet = globalThis.Set + const valuesSpy = vi.spyOn(Object, 'values').mockImplementation(() => { + throw new Error('unexpected Object.values allocation') + }) + vi.stubGlobal( + 'Set', + class UnexpectedSet { + constructor() { + throw new Error('unexpected Set allocation') + } + } + ) + + try { + for (let check = 0; check < 1_000; check += 1) { + expect(hasRemoteRuntimePtyForTab(['local-pty'], { first: 'local-pty-2' })).toBe(false) + } + } finally { + valuesSpy.mockRestore() + vi.stubGlobal('Set', originalSet) + } + }) +}) diff --git a/src/renderer/src/lib/use-tab-agent.ts b/src/renderer/src/lib/use-tab-agent.ts index fef059ad616..21c00f4cc37 100644 --- a/src/renderer/src/lib/use-tab-agent.ts +++ b/src/renderer/src/lib/use-tab-agent.ts @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { useAppStore } from '@/store' import { isShellProcess } from '../../../shared/agent-detection' import { worktreeUsesRemoteConnection } from '@/store/terminals/terminal-workspace-routing' -import { parseRemoteRuntimePtyId } from '@/runtime/runtime-terminal-stream' +import { hasRemoteRuntimePtyForTab } from './tab-agent-remote-pty-selector' import { isTerminalLeafId, makePaneKey } from '../../../shared/stable-pane-id' import { resolveFocusedCompletedTabAgent, @@ -254,14 +254,12 @@ export function useTabAgent(tab: TerminalTab): TuiAgent | null { } return (s.ptyIdsByTabId[tab.id] ?? []).length <= 1 }) - const hasRemoteRuntimePty = useAppStore((s) => { - const layout = s.terminalLayoutsByTabId[tab.id] - const ptyIds = new Set(s.ptyIdsByTabId[tab.id] ?? []) - for (const ptyId of Object.values(layout?.ptyIdsByLeafId ?? {})) { - ptyIds.add(ptyId) - } - return [...ptyIds].some((ptyId) => parseRemoteRuntimePtyId(ptyId) !== null) - }) + const hasRemoteRuntimePty = useAppStore((s) => + hasRemoteRuntimePtyForTab( + s.ptyIdsByTabId[tab.id], + s.terminalLayoutsByTabId[tab.id]?.ptyIdsByLeafId + ) + ) const isRemoteWorktree = useAppStore((s) => worktreeUsesRemoteConnection(s, tab.worktreeId)) const isRemoteLike = isRemoteWorktree || hasRemoteRuntimePty