Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c90daa1
πŸ€– refactor: extract IMAGE_TOOL_EXCESS_COUNT_SETUP_HINT constant
mux-bot[bot] May 15, 2026
266c6c4
πŸ€– refactor: hoist mermaid sanitizer URL tables to module scope
ammar-agent May 16, 2026
4076c37
refactor: extract tool-part phase lookup to module-level constant
ammar-agent May 16, 2026
e28b671
refactor: extract createSlashCommandExperimentResolver helper
mux-bot[bot] May 17, 2026
02d80c0
refactor: dedupe Runtime type import in cli/run.ts
mux-bot[bot] May 17, 2026
609a559
refactor: dedupe built-in skill name list in agentSkills test
ammar-agent May 18, 2026
05a939a
refactor: extract AdvisorSwitch component in TasksSection
mux-bot[bot] May 18, 2026
350f919
refactor: share TEXT_OUTPUT_DELTA_FIELDS constant
mux-bot[bot] May 18, 2026
f3e8216
refactor: extract StatTileHeader from goal-tab budget/turns tiles
May 18, 2026
07a066e
refactor: hoist GoalInterventionPolicy to common/orpc/types
mux-bot[bot] May 19, 2026
bc190cd
refactor: convert synthesizeSilentContinuationSummary to free function
ammar-agent May 19, 2026
8403a11
refactor: use loop index for review_pane dedup seed map
mux-bot[bot] May 19, 2026
f868624
refactor: drop unused __resetAdditionalSystemContextFocusForTests helper
mux-bot[bot] May 20, 2026
7fb8b0b
refactor: extract CLI_DISABLED_TOOL_NAMES to dedupe mux run toolPolicy
mux-bot[bot] May 20, 2026
d22bcf7
refactor: extract section-pill className helper in AskUserQuestionToo…
May 20, 2026
3de402a
refactor: extract restoreEnvVar helper in signingService.test.ts
ammar-agent May 21, 2026
1ef426d
refactor: relocate test-only buildReviewDiffPathFilter wrapper
ammar-agent May 21, 2026
6058110
refactor: use template literals in goal flag parser errors
mux-bot[bot] May 21, 2026
e1db195
refactor: extract neutral lifecycle button className in GoalTab
mux-bot[bot] May 21, 2026
965b4ee
refactor: dedupe UNRESOLVED_DELTA_PUSH_PATTERNS into PROJECT_SYNC_RET…
mux-bot[bot] May 21, 2026
ee41d1c
refactor: extract matchesCommand helper in bashCollapsedSummary
mux-bot[bot] May 22, 2026
f202d4a
refactor: extract filterDismissedAssistedHunks helper
mux-bot[bot] May 22, 2026
d1dde90
refactor: extract persistChatTranscriptFullWidth helper
May 22, 2026
e478c8a
refactor: dedupe plain-text HighlightedChunk construction
mux-bot[bot] May 23, 2026
6fb7a34
refactor: dedupe listener-set lazy-init in additionalSystemContextStore
ammar-agent May 24, 2026
bf00b71
refactor: extract parseOptionalIsoTimestamp helper in recency
mux-bot[bot] May 26, 2026
f02ab23
refactor: hoist OPENROUTER_ROUTING_OPTIONS to module scope
May 27, 2026
e66623a
refactor: extract isLinkLocalAddress helper for IPv4/IPv6 filtering
May 28, 2026
e8b542c
refactor: use isSyntheticSnapshotUserMessage in exec hard-restart see…
May 28, 2026
cb48bfb
refactor: dedupe isHttpProtocol guard into shared localhostProxyUrl h…
mux-bot[bot] May 28, 2026
82c5add
refactor: dedupe affirmative env value check in env.ts
mux-bot[bot] May 29, 2026
619dc3c
refactor: extract thinkingLevelIndex helper in thinking policy
mux-bot[bot] May 30, 2026
18ab9a5
refactor: dedupe advisor status presentation shape in AdvisorToolCall
mux-bot[bot] May 30, 2026
b29d4e9
refactor: collapse duplicate FILE_SUGGESTION_KEYS branches in ChatInp…
mux-bot[bot] Jun 1, 2026
bc8cca6
refactor: dedupe tip-rotation interval constant in placeholderTips test
mux-bot[bot] Jun 2, 2026
4e4a0e4
refactor: dedup immersive review reveal-completion scheduling
mux-bot[bot] Jun 3, 2026
2de4a60
refactor: dedup minimap getThumbMetrics scroll-container call sites
mux-bot[bot] Jun 3, 2026
55a0a9c
refactor: dedupe PowerModeEngine clamps into shared clamp helper
mux-bot[bot] Jun 3, 2026
228bbc4
refactor: extract resolveRoutedThroughGateway helper in StreamManager
mux-bot[bot] Jun 4, 2026
c234f9c
refactor: derive loopback host union from shared tuple in localhostPr…
mux-bot[bot] Jun 4, 2026
560c6b3
refactor: dedupe "workspace not found in config" error in snapshot se…
mux-bot[bot] Jun 4, 2026
bd60418
refactor: dedupe "API not connected" guard message in WorkspaceContext
mux-bot[bot] Jun 5, 2026
3531076
refactor: dedupe "Mux API not connected." guard in OnePasswordPicker
mux-bot[bot] Jun 5, 2026
1682dc2
πŸ€– refactor: dedupe ACP trunk-branch resolution into helper
mux-bot[bot] Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/browser/components/CommandPalette/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
matchesKeybind,
} from "@/browser/utils/ui/keybinds";
import { stopKeyboardPropagation } from "@/browser/utils/events";
import { resolveSlashCommandExperimentValue } from "@/browser/utils/slashCommands/experimentVisibility";
import { createSlashCommandExperimentResolver } from "@/browser/utils/slashCommands/experimentVisibility";
import { getSlashCommandSuggestions } from "@/browser/utils/slashCommands/suggestions";
import { CUSTOM_EVENTS, createCustomEvent } from "@/common/constants/events";
import { EXPERIMENT_IDS } from "@/common/constants/experiments";
Expand Down Expand Up @@ -290,10 +290,9 @@ export const CommandPalette: React.FC<CommandPaletteProps> = ({ getSlashContext
const suggestions = getSlashCommandSuggestions(q, {
agentSkills,
variant: ctx.workspaceId ? "workspace" : "creation",
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});
const section = "Slash Commands";
const groups: PaletteGroup[] = [
Expand Down
16 changes: 10 additions & 6 deletions src/browser/contexts/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ import type { APIClient } from "@/browser/contexts/API";
import { getErrorMessage } from "@/common/utils/errors";
import type { WorkspaceCreationScope } from "@/common/utils/subProjects";

// Single source of truth for the guard used when the IPC API client is unavailable, so the
// message can't drift between the workspace-context operations that surface it.
const API_NOT_CONNECTED_ERROR = "API not connected";

/**
* One-time best-effort migration: if the backend doesn't have model preferences yet,
* persist explicit localStorage values so future port/origin changes keep them.
Expand Down Expand Up @@ -1290,7 +1294,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
trunkBranch: string,
runtimeConfig?: RuntimeConfig
) => {
if (!api) throw new Error("API not connected");
if (!api) throw new Error(API_NOT_CONNECTED_ERROR);
console.assert(
typeof trunkBranch === "string" && trunkBranch.trim().length > 0,
"Expected trunk branch to be provided when creating a workspace"
Expand Down Expand Up @@ -1333,7 +1337,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
workspaceId: string,
options?: { force?: boolean }
): Promise<{ success: boolean; error?: string }> => {
if (!api) return { success: false, error: "API not connected" };
if (!api) return { success: false, error: API_NOT_CONNECTED_ERROR };

// Capture state before the async operation.
// We check currentWorkspaceId (from URL) rather than selectedWorkspace
Expand Down Expand Up @@ -1403,7 +1407,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
workspaceId: string,
newTitle: string
): Promise<{ success: boolean; error?: string }> => {
if (!api) return { success: false, error: "API not connected" };
if (!api) return { success: false, error: API_NOT_CONNECTED_ERROR };
try {
const result = await api.workspace.updateTitle({ workspaceId, title: newTitle });
if (result.success) {
Expand All @@ -1428,7 +1432,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
async (
workspaceId: string
): Promise<{ success: boolean; error?: string; data?: ArchivePreflightResult }> => {
if (!api) return { success: false, error: "API not connected" };
if (!api) return { success: false, error: API_NOT_CONNECTED_ERROR };

try {
const result = await api.workspace.preflightArchive({ workspaceId });
Expand All @@ -1448,7 +1452,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
workspaceId: string,
options?: { acknowledgedUntrackedPaths?: string[] }
): Promise<{ success: boolean; error?: string; data?: ArchiveWorkspaceResult }> => {
if (!api) return { success: false, error: "API not connected" };
if (!api) return { success: false, error: API_NOT_CONNECTED_ERROR };

try {
const result = await api.workspace.archive({
Expand Down Expand Up @@ -1500,7 +1504,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {

const unarchiveWorkspace = useCallback(
async (workspaceId: string): Promise<{ success: boolean; error?: string }> => {
if (!api) return { success: false, error: "API not connected" };
if (!api) return { success: false, error: API_NOT_CONNECTED_ERROR };
try {
const result = await api.workspace.unarchive({ workspaceId });
if (result.success) {
Expand Down
28 changes: 13 additions & 15 deletions src/browser/features/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ import {
getSlashCommandSuggestions,
type SlashSuggestion,
} from "@/browser/utils/slashCommands/suggestions";
import { resolveSlashCommandExperimentValue } from "@/browser/utils/slashCommands/experimentVisibility";
import { createSlashCommandExperimentResolver } from "@/browser/utils/slashCommands/experimentVisibility";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip";
import { AgentModePicker } from "@/browser/components/AgentModePicker/AgentModePicker";
import { ContextUsageIndicatorButton } from "@/browser/components/ContextUsageIndicatorButton/ContextUsageIndicatorButton";
Expand Down Expand Up @@ -1427,10 +1427,9 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
const suggestions = getSlashCommandSuggestions(input, {
agentSkills: agentSkillDescriptors,
variant,
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});
setCommandSuggestions((prev) => replaceSuggestions(prev, suggestions));
setShowCommandSuggestions(suggestions.length > 0);
Expand All @@ -1440,10 +1439,9 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
// Show only when suggestions are hidden and the input is exactly "/command " with no args yet.
const commandGhostHint = getCommandGhostHint(input, showCommandSuggestions, {
variant,
isExperimentEnabled: (experimentId) =>
resolveSlashCommandExperimentValue(experimentId, {
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
isExperimentEnabled: createSlashCommandExperimentResolver({
workspaceHeartbeats: workspaceHeartbeatsExperimentEnabled,
}),
});

// Load agent skills for suggestions
Expand Down Expand Up @@ -2849,13 +2847,13 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
onDrop={handleDrop}
onEscapeInNormalMode={handleEscapeInNormalMode}
suppressKeys={
showAtMentionSuggestions
// @file and $skill menus share the same key set; collapse the
// duplicate FILE_SUGGESTION_KEYS branches into one condition.
showAtMentionSuggestions || showSkillSuggestions
? FILE_SUGGESTION_KEYS
: showSkillSuggestions
? FILE_SUGGESTION_KEYS
: showCommandSuggestions
? COMMAND_SUGGESTION_KEYS
: undefined
: showCommandSuggestions
? COMMAND_SUGGESTION_KEYS
: undefined
}
placeholder={placeholder}
disabled={!editingMessageForUi && (disabled || sendInFlightBlocksInput)}
Expand Down
19 changes: 9 additions & 10 deletions src/browser/features/ChatInput/placeholderTips.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { afterEach, describe, expect, test } from "bun:test";
import { PLACEHOLDER_TIPS, getPlaceholderTip } from "./placeholderTips";
import { PLACEHOLDER_TIPS, TIP_ROTATION_INTERVAL_MS, getPlaceholderTip } from "./placeholderTips";

interface StorybookGlobal {
__MUX_STORYBOOK__?: boolean;
}

const TWENTY_MIN_MS = 20 * 60 * 1000;

describe("PLACEHOLDER_TIPS", () => {
test("every tip references a slash command", () => {
for (const tip of PLACEHOLDER_TIPS) {
Expand Down Expand Up @@ -43,25 +41,26 @@ describe("getPlaceholderTip", () => {
// to the same tip. If they don't, switching workspaces / re-rendering
// inside the same bucket would reshuffle the tip β€” which is the exact
// flicker we're trying to prevent.
const bucketStart = TWENTY_MIN_MS * 100; // arbitrary aligned anchor
const bucketStart = TIP_ROTATION_INTERVAL_MS * 100; // arbitrary aligned anchor
const tip = getPlaceholderTip(bucketStart);
expect(getPlaceholderTip(bucketStart + 1)).toBe(tip);
expect(getPlaceholderTip(bucketStart + TWENTY_MIN_MS - 1)).toBe(tip);
expect(getPlaceholderTip(bucketStart + TIP_ROTATION_INTERVAL_MS - 1)).toBe(tip);
});

test("advances to the next tip when the bucket boundary crosses", () => {
// Crossing the boundary must rotate β€” otherwise the carousel is silently
// stuck and the discoverability rationale is broken.
const bucketStart = TWENTY_MIN_MS * 100;
const bucketStart = TIP_ROTATION_INTERVAL_MS * 100;
const before = getPlaceholderTip(bucketStart);
const after = getPlaceholderTip(bucketStart + TWENTY_MIN_MS);
const after = getPlaceholderTip(bucketStart + TIP_ROTATION_INTERVAL_MS);
expect(after).not.toBe(before);
});

test("wraps with modulo so long-running clocks never lose the placeholder", () => {
// Far-future timestamps should still resolve to a tip rather than
// undefined / out-of-bounds.
const bigFuture = TWENTY_MIN_MS * PLACEHOLDER_TIPS.length * 5 + TWENTY_MIN_MS * 3;
const bigFuture =
TIP_ROTATION_INTERVAL_MS * PLACEHOLDER_TIPS.length * 5 + TIP_ROTATION_INTERVAL_MS * 3;
expect(PLACEHOLDER_TIPS).toContain(getPlaceholderTip(bigFuture));
});

Expand All @@ -87,9 +86,9 @@ describe("getPlaceholderTip", () => {
// Explicit nowMs must still rotate even with the flag set, otherwise
// unit tests that depend on rotation math would silently no-op when
// someone forgets to clear the flag.
const bucketStart = TWENTY_MIN_MS * 100;
const bucketStart = TIP_ROTATION_INTERVAL_MS * 100;
const before = getPlaceholderTip(bucketStart);
const after = getPlaceholderTip(bucketStart + TWENTY_MIN_MS);
const after = getPlaceholderTip(bucketStart + TIP_ROTATION_INTERVAL_MS);
expect(after).not.toBe(before);
});
});
5 changes: 3 additions & 2 deletions src/browser/features/ChatInput/placeholderTips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
* sure the command you're surfacing isn't gated.
*/

/** Bucket length for tip rotation. */
const TIP_ROTATION_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
/** Bucket length for tip rotation. Exported so tests anchor their bucket math
* to the real interval instead of a hardcoded copy that silently drifts. */
export const TIP_ROTATION_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes

/**
* Tip index pinned for Storybook/Chromatic snapshots.
Expand Down
5 changes: 4 additions & 1 deletion src/browser/features/ChatInput/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import type { Review } from "@/common/types/review";
import type { EditingMessageState, PendingUserMessage } from "@/browser/utils/chatEditing";
import type { SendMessageOptions } from "@/common/orpc/types";

export type GoalInterventionPolicy = NonNullable<SendMessageOptions["goalInterventionPolicy"]>;
// Re-export so `ChatInput/types` (the existing barrel for ChatInput-local
// types) stays the single import surface for this feature, while the
// canonical declaration lives next to `SendMessageOptions` itself.
export type { GoalInterventionPolicy } from "@/common/orpc/types";
export type QueueDispatchMode = NonNullable<SendMessageOptions["queueDispatchMode"]>;

export interface ChatInputAPI {
Expand Down
12 changes: 7 additions & 5 deletions src/browser/features/Messages/Mermaid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { usePersistedState } from "@/browser/hooks/usePersistedState";
const MIN_HEIGHT = 300;
const MAX_HEIGHT = 1200;

// Sanitizer lookup tables. Hoisted to module scope so they aren't reallocated on
// every sanitizeMermaidSvg() call (which can run per render for large SVGs).
const SANITIZER_URL_ATTRIBUTES = new Set(["href", "xlink:href", "src", "action", "formaction"]);
const SANITIZER_BLOCKED_URL_SCHEMES = ["javascript:", "vbscript:", "data:text/html"];

// Initialize mermaid
mermaid.initialize({
startOnLoad: false,
Expand Down Expand Up @@ -162,9 +167,6 @@ export function sanitizeMermaidSvg(svg: string): string | null {
node.remove();
});

const urlAttributes = new Set(["href", "xlink:href", "src", "action", "formaction"]);
const blockedSchemes = ["javascript:", "vbscript:", "data:text/html"];

const elementsToScan: Element[] = [svgRoot, ...Array.from(svgRoot.querySelectorAll("*"))];
elementsToScan.forEach((element) => {
for (const attribute of Array.from(element.attributes)) {
Expand All @@ -177,8 +179,8 @@ export function sanitizeMermaidSvg(svg: string): string | null {
}

if (
urlAttributes.has(attributeName) &&
blockedSchemes.some((scheme) => canonicalUrlValue.startsWith(scheme))
SANITIZER_URL_ATTRIBUTES.has(attributeName) &&
SANITIZER_BLOCKED_URL_SCHEMES.some((scheme) => canonicalUrlValue.startsWith(scheme))
) {
element.removeAttribute(attribute.name);
}
Expand Down
31 changes: 17 additions & 14 deletions src/browser/features/RightSidebar/CodeReview/ImmersiveMinimap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ const readThemeColor = (cssVariableName: string, fallback: string): string => {
return resolvedColor.length > 0 ? resolvedColor : fallback;
};

// The minimap reads thumb geometry from a scroll container in three places
// (initial draw, thumb press, and drag-follow), each needing the same
// scrollTop/scrollHeight/clientHeight triple. Funnel them through one wrapper
// so the call sites stay in sync; pure forwarding, no behavior change.
const getScrollContainerThumbMetrics = (scrollContainer: HTMLElement, trackHeight: number) =>
getThumbMetrics(
scrollContainer.scrollTop,
scrollContainer.scrollHeight,
scrollContainer.clientHeight,
trackHeight
);

export const ImmersiveMinimap: React.FC<ImmersiveMinimapProps> = (props) => {
// Computed synchronously during render β€” React Compiler memoizes based on
// props.content. Avoids the useState+useEffect flash where the component
Expand Down Expand Up @@ -105,10 +117,8 @@ export const ImmersiveMinimap: React.FC<ImmersiveMinimapProps> = (props) => {

const scrollContainer = props.scrollContainerRef.current;
if (scrollContainer) {
const { thumbTop, thumbHeight } = getThumbMetrics(
scrollContainer.scrollTop,
scrollContainer.scrollHeight,
scrollContainer.clientHeight,
const { thumbTop, thumbHeight } = getScrollContainerThumbMetrics(
scrollContainer,
trackHeight
);

Expand Down Expand Up @@ -196,12 +206,7 @@ export const ImmersiveMinimap: React.FC<ImmersiveMinimapProps> = (props) => {
const pointerY = event.clientY - bounds.top;

if (scrollContainer) {
const thumbMetrics = getThumbMetrics(
scrollContainer.scrollTop,
scrollContainer.scrollHeight,
scrollContainer.clientHeight,
bounds.height
);
const thumbMetrics = getScrollContainerThumbMetrics(scrollContainer, bounds.height);

const isPressingThumb =
thumbMetrics.maxThumbTop > 0 &&
Expand All @@ -223,10 +228,8 @@ export const ImmersiveMinimap: React.FC<ImmersiveMinimapProps> = (props) => {

const latestBounds = latestCanvas.getBoundingClientRect();
const nextPointerY = moveEvent.clientY - latestBounds.top;
const nextMetrics = getThumbMetrics(
latestScrollContainer.scrollTop,
latestScrollContainer.scrollHeight,
latestScrollContainer.clientHeight,
const nextMetrics = getScrollContainerThumbMetrics(
latestScrollContainer,
latestBounds.height
);
const nextThumbTop = clamp(nextPointerY - pointerOffset, 0, nextMetrics.maxThumbTop);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,22 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
const contentChanged = previousContentRef.current !== overlayData.content;
previousContentRef.current = overlayData.content;

// Clear the file-switch reveal gate on the next frame once the initial scroll
// has been issued. Both the no-line-element retry path and the post-scroll path
// need the identical "cancel any pending frame, then schedule a one-shot clear
// guarded by the captured file path" sequence, so keep it in one place.
const scheduleRevealCompletion = (revealFilePath: string) => {
if (revealAnimationFrameRef.current !== null) {
cancelAnimationFrame(revealAnimationFrameRef.current);
}
revealAnimationFrameRef.current = window.requestAnimationFrame(() => {
setPendingRevealFilePath((pendingFilePath) =>
pendingFilePath === revealFilePath ? null : pendingFilePath
);
revealAnimationFrameRef.current = null;
});
};

const previousLineElement = highlightedLineElementRef.current;
if (previousLineElement) {
previousLineElement.style.outline = "";
Expand Down Expand Up @@ -1596,17 +1612,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
return;
}

if (revealAnimationFrameRef.current !== null) {
cancelAnimationFrame(revealAnimationFrameRef.current);
}

const revealFilePath = activeFilePath;
revealAnimationFrameRef.current = window.requestAnimationFrame(() => {
setPendingRevealFilePath((pendingFilePath) =>
pendingFilePath === revealFilePath ? null : pendingFilePath
);
revealAnimationFrameRef.current = null;
});
scheduleRevealCompletion(activeFilePath);
return;
}

Expand All @@ -1627,17 +1633,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
return;
}

if (revealAnimationFrameRef.current !== null) {
cancelAnimationFrame(revealAnimationFrameRef.current);
}

const revealFilePath = activeFilePath;
revealAnimationFrameRef.current = window.requestAnimationFrame(() => {
setPendingRevealFilePath((pendingFilePath) =>
pendingFilePath === revealFilePath ? null : pendingFilePath
);
revealAnimationFrameRef.current = null;
});
scheduleRevealCompletion(activeFilePath);
}, [
activeFilePath,
activeLineIndex,
Expand Down
Loading