fix: reduce footer flicker in Ghostty by memoizing high-frequency renders (#508)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-09 14:56:52 -08:00
committed by GitHub
parent 6f5a5bf109
commit 3342ab0d06
2 changed files with 100 additions and 53 deletions

View File

@@ -5,7 +5,14 @@ import { stdin } from "node:process";
import chalk from "chalk";
import { Box, Text, useInput } from "ink";
import SpinnerLib from "ink-spinner";
import { type ComponentType, useEffect, useRef, useState } from "react";
import {
type ComponentType,
memo,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { LETTA_CLOUD_API_URL } from "../../auth/oauth";
import {
ELAPSED_DISPLAY_THRESHOLD_MS,
@@ -30,6 +37,69 @@ const Spinner = SpinnerLib as ComponentType<{ type?: string }>;
// Window for double-escape to clear input
const ESC_CLEAR_WINDOW_MS = 2500;
/**
* Memoized footer component to prevent re-renders during high-frequency
* shimmer/timer updates. Only updates when its specific props change.
*/
const InputFooter = memo(function InputFooter({
ctrlCPressed,
escapePressed,
isBashMode,
modeName,
modeColor,
showExitHint,
agentName,
currentModel,
isAnthropicProvider,
}: {
ctrlCPressed: boolean;
escapePressed: boolean;
isBashMode: boolean;
modeName: string | null;
modeColor: string | null;
showExitHint: boolean;
agentName: string | null | undefined;
currentModel: string | null | undefined;
isAnthropicProvider: boolean;
}) {
return (
<Box justifyContent="space-between" marginBottom={1}>
{ctrlCPressed ? (
<Text dimColor>Press CTRL-C again to exit</Text>
) : escapePressed ? (
<Text dimColor>Press Esc again to clear</Text>
) : isBashMode ? (
<Text>
<Text color={colors.bash.prompt}> bash mode</Text>
<Text color={colors.bash.prompt} dimColor>
{" "}
(backspace to exit)
</Text>
</Text>
) : modeName && modeColor ? (
<Text>
<Text color={modeColor}> {modeName}</Text>
<Text color={modeColor} dimColor>
{" "}
(shift+tab to {showExitHint ? "exit" : "cycle"})
</Text>
</Text>
) : (
<Text dimColor>Press / for commands</Text>
)}
<Text>
<Text color={colors.footer.agentName}>{agentName || "Unnamed"}</Text>
<Text
dimColor={!isAnthropicProvider}
color={isAnthropicProvider ? "#FFC787" : undefined}
>
{` [${currentModel ?? "unknown"}]`}
</Text>
</Text>
</Box>
);
});
// Increase max listeners to accommodate multiple useInput hooks
// (5 in this component + autocomplete components)
stdin.setMaxListeners(20);
@@ -586,7 +656,8 @@ export function Input({
};
// Get display name and color for permission mode (ralph modes take precedence)
const getModeInfo = () => {
// Memoized to prevent unnecessary footer re-renders
const modeInfo = useMemo(() => {
// Check ralph pending first (waiting for task input)
if (ralphPending) {
if (ralphPendingYolo) {
@@ -635,9 +706,7 @@ export function Input({
default:
return null;
}
};
const modeInfo = getModeInfo();
}, [ralphPending, ralphPendingYolo, ralphActive, currentMode]);
const estimatedTokens = charsToTokens(tokenCount);
const shouldShowTokenCount =
@@ -647,8 +716,8 @@ export function Input({
const elapsedMinutes = Math.floor(elapsedMs / 60000);
// Build the status hint text (esc to interrupt · 2m · 1.2k ↑)
// In ralph mode, also show "shift+tab to exit"
const statusHintText = (() => {
// Memoized to prevent unnecessary re-renders during shimmer updates
const statusHintText = useMemo(() => {
const hintColor = chalk.hex(colors.subagent.hint);
const hintBold = hintColor.bold;
const suffix =
@@ -661,10 +730,17 @@ export function Input({
return (
hintColor(" (") + hintBold("esc") + hintColor(` to interrupt${suffix}`)
);
})();
}, [
shouldShowElapsed,
elapsedMinutes,
shouldShowTokenCount,
estimatedTokens,
interruptRequested,
]);
// Create a horizontal line using box-drawing characters
const horizontalLine = "─".repeat(columns);
// Memoized since it only changes when terminal width changes
const horizontalLine = useMemo(() => "─".repeat(columns), [columns]);
// If not visible, render nothing but keep component mounted to preserve state
if (!visible) {
@@ -749,46 +825,17 @@ export function Input({
workingDirectory={process.cwd()}
/>
<Box justifyContent="space-between" marginBottom={1}>
{ctrlCPressed ? (
<Text dimColor>Press CTRL-C again to exit</Text>
) : escapePressed ? (
<Text dimColor>Press Esc again to clear</Text>
) : isBashMode ? (
<Text>
<Text color={colors.bash.prompt}> bash mode</Text>
<Text color={colors.bash.prompt} dimColor>
{" "}
(backspace to exit)
</Text>
</Text>
) : modeInfo ? (
<Text>
<Text color={modeInfo.color}> {modeInfo.name}</Text>
<Text color={modeInfo.color} dimColor>
{" "}
(shift+tab to {ralphActive || ralphPending ? "exit" : "cycle"})
</Text>
</Text>
) : (
<Text dimColor>Press / for commands</Text>
)}
<Text>
<Text color={colors.footer.agentName}>
{agentName || "Unnamed"}
</Text>
<Text
dimColor={currentModelProvider !== ANTHROPIC_PROVIDER_NAME}
color={
currentModelProvider === ANTHROPIC_PROVIDER_NAME
? "#FFC787"
: undefined
}
>
{` [${currentModel ?? "unknown"}]`}
</Text>
</Text>
</Box>
<InputFooter
ctrlCPressed={ctrlCPressed}
escapePressed={escapePressed}
isBashMode={isBashMode}
modeName={modeInfo?.name ?? null}
modeColor={modeInfo?.color ?? null}
showExitHint={ralphActive || ralphPending}
agentName={agentName}
currentModel={currentModel}
isAnthropicProvider={currentModelProvider === ANTHROPIC_PROVIDER_NAME}
/>
</Box>
</Box>
);

View File

@@ -1,6 +1,6 @@
import chalk from "chalk";
import { Text } from "ink";
import type React from "react";
import { memo } from "react";
import { colors } from "./colors.js";
interface ShimmerTextProps {
@@ -10,12 +10,12 @@ interface ShimmerTextProps {
shimmerOffset: number;
}
export const ShimmerText: React.FC<ShimmerTextProps> = ({
export const ShimmerText = memo(function ShimmerText({
color = colors.status.processing,
boldPrefix,
message,
shimmerOffset,
}) => {
}: ShimmerTextProps) {
const fullText = `${boldPrefix ? `${boldPrefix} ` : ""}${message}`;
const prefixLength = boldPrefix ? boldPrefix.length + 1 : 0; // +1 for space
@@ -37,4 +37,4 @@ export const ShimmerText: React.FC<ShimmerTextProps> = ({
.join("");
return <Text>{shimmerText}</Text>;
};
});