fix(app): reconcile find-in-pane with main after rebase

Resolve drift where main evolved the same areas this feature touches:

- view.tsx: keep main's agent-stream/ layout refactor, drop the
  working-indicator/turn-copy helpers main relocated, fix moved import paths
- terminal-emulator.tsx: expose terminal find on the typed ref handle only
  (main's DOMImperativeFactory is JSON-serializable-only)
- file-pane.tsx: render syntax highlighting and find highlighting together,
  pass lineSelection through the searchable preview
- tests/e2e: track main's PaneContextValue.openImportSheet,
  TerminalEmulatorHandle (restoreOutput/blur), and the seed-client helper
This commit is contained in:
Mohamed Boudra
2026-05-31 12:45:29 +07:00
parent 7c4f8580e9
commit 0a960f9ae0
10 changed files with 33 additions and 154 deletions
+3 -6
View File
@@ -1,10 +1,7 @@
import { expect, test, type Page } from "./fixtures";
import { waitForTabBar } from "./helpers/launcher";
import {
connectTerminalClient,
setupDeterministicPrompt,
waitForTerminalContent,
} from "./helpers/terminal-perf";
import { setupDeterministicPrompt, waitForTerminalContent } from "./helpers/terminal-perf";
import { connectSeedClient } from "./helpers/seed-client";
import { createTempGitRepo } from "./helpers/workspace";
import { openFileExplorer, openFileFromExplorer } from "./helpers/file-explorer";
import {
@@ -51,7 +48,7 @@ test.describe("in-pane find", () => {
test.setTimeout(120_000);
const client = await connectWorkspaceSetupClient();
const agentClient = await connectTerminalClient();
const agentClient = await connectSeedClient();
const repo = await createTempGitRepo("find-pane-qa-", {
files: [
{
+9 -126
View File
@@ -63,7 +63,7 @@ import {
buildAgentStreamSearchModel,
findAgentStreamSearchMatches,
type AgentStreamSearchMatch,
} from "./agent-stream-search-model";
} from "@/components/agent-stream-search-model";
import {
type BottomAnchorLocalRequest,
type BottomAnchorRouteRequest,
@@ -87,11 +87,6 @@ import {
type UsePaneFindResult,
usePaneFind,
} from "@/panels/pane-find";
import {
getWorkingIndicatorDotStrength,
WORKING_INDICATOR_CYCLE_MS,
WORKING_INDICATOR_OFFSETS,
} from "@/utils/working-indicator";
import { isWeb } from "@/constants/platform";
import type { Theme } from "@/styles/theme";
@@ -601,25 +596,6 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
};
});
}, [searchModel]);
const inlineWorkingIndicatorItemId = useMemo(() => {
if (agent.status !== "running") {
return null;
}
const footerItem = baseRenderModel.segments.liveHead.find((item, index, items) => {
if (item.kind !== "assistant_message") {
return false;
}
return (
getStreamNeighborItem({
strategy: streamRenderStrategy,
items,
index,
relation: "below",
}) === undefined
);
});
return footerItem?.id ?? null;
}, [agent.status, baseRenderModel.segments.liveHead, streamRenderStrategy]);
useImperativeHandle(
ref,
() => ({
@@ -699,7 +675,14 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
</AssistantFileLinkResolverProvider>
);
},
[client, findHighlightsByItemId, handleInlinePathPress, resolvedServerId, toast, workspaceRoot],
[
client,
findHighlightsByItemId,
handleInlinePathPress,
resolvedServerId,
toast,
workspaceRoot,
],
);
const renderThoughtItem = useCallback(
@@ -998,110 +981,10 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
export const AgentStreamView = memo(AgentStreamViewComponent);
AgentStreamView.displayName = "AgentStreamView";
function WorkingIndicator({ variant = "auxiliary" }: { variant?: "auxiliary" | "inline" }) {
const progress = useSharedValue(0);
useEffect(() => {
progress.value = 0;
progress.value = withRepeat(
withTiming(1, {
duration: WORKING_INDICATOR_CYCLE_MS,
easing: Easing.linear,
}),
-1,
false,
);
return () => {
cancelAnimation(progress);
progress.value = 0;
};
}, [progress]);
const translateDistance = -2;
const dotOneStyle = useAnimatedStyle(() => {
const strength = getWorkingIndicatorDotStrength(progress.value, WORKING_INDICATOR_OFFSETS[0]);
return {
opacity: 0.3 + strength * 0.7,
transform: [{ translateY: strength * translateDistance }],
};
});
const dotTwoStyle = useAnimatedStyle(() => {
const strength = getWorkingIndicatorDotStrength(progress.value, WORKING_INDICATOR_OFFSETS[1]);
return {
opacity: 0.3 + strength * 0.7,
transform: [{ translateY: strength * translateDistance }],
};
});
const dotThreeStyle = useAnimatedStyle(() => {
const strength = getWorkingIndicatorDotStrength(progress.value, WORKING_INDICATOR_OFFSETS[2]);
return {
opacity: 0.3 + strength * 0.7,
transform: [{ translateY: strength * translateDistance }],
};
});
const dotOneCombinedStyle = useMemo(() => [stylesheet.workingDot, dotOneStyle], [dotOneStyle]);
const dotTwoCombinedStyle = useMemo(() => [stylesheet.workingDot, dotTwoStyle], [dotTwoStyle]);
const dotThreeCombinedStyle = useMemo(
() => [stylesheet.workingDot, dotThreeStyle],
[dotThreeStyle],
);
const containerStyle =
variant === "inline"
? stylesheet.inlineWorkingIndicatorFrame
: stylesheet.workingIndicatorBubble;
return (
<View style={containerStyle}>
<View style={stylesheet.workingDotsRow}>
<Animated.View style={dotOneCombinedStyle} />
<Animated.View style={dotTwoCombinedStyle} />
<Animated.View style={dotThreeCombinedStyle} />
</View>
</View>
);
}
function InlineWorkingIndicatorSlot() {
return (
<View style={stylesheet.inlineTurnFooter} testID="turn-working-indicator">
<WorkingIndicator variant="inline" />
</View>
);
}
function AgentStreamFindBarSlot({ paneFind }: { paneFind: UsePaneFindResult }) {
return paneFind.isOpen ? <FindBar {...paneFind.findBarProps} /> : null;
}
// Permission Request Card Component
type TurnContentStrategy = Parameters<
typeof collectAssistantTurnContentForStreamRenderStrategy
>[0]["strategy"];
interface TurnCopyButtonSlotProps {
strategy: TurnContentStrategy;
items: StreamItem[];
startIndex: number;
}
function TurnCopyButtonSlot({ strategy, items, startIndex }: TurnCopyButtonSlotProps) {
const getContent = useCallback(
() =>
collectAssistantTurnContentForStreamRenderStrategy({
strategy,
items,
startIndex,
}),
[strategy, items, startIndex],
);
return <TurnCopyButton getContent={getContent} />;
}
interface ToolCallSlotProps extends Omit<
ComponentProps<typeof ToolCall>,
"onInlineDetailsExpandedChange"
@@ -3,7 +3,7 @@ import {
findMountedWindowStart,
getWebMountedRecentStreamItems,
getWebPartialVirtualizationThreshold,
} from "./agent-stream-web-virtualization";
} from "@/agent-stream/web-virtualization";
type AgentStreamSearchSource = "historyVirtualized" | "historyMounted" | "liveHead";
@@ -236,6 +236,7 @@ const paneContextValue: PaneContextValue = {
closeCurrentTab: vi.fn(),
retargetCurrentTab: vi.fn(),
openFileInWorkspace: vi.fn(),
openImportSheet: vi.fn(),
};
function installWebviewElementFactory(): void {
@@ -216,6 +216,7 @@ const paneContext: PaneContextValue = {
closeCurrentTab: () => {},
retargetCurrentTab: () => {},
openFileInWorkspace: () => {},
openImportSheet: () => {},
};
const paneFocus = createPaneFocusContextValue({
isPaneFocused: true,
@@ -248,12 +249,12 @@ afterEach(() => {
vi.unstubAllGlobals();
});
function renderFilePane(filePath = "src/example.ts") {
function renderFilePane(location: { path: string } = { path: "src/example.ts" }) {
act(() => {
root?.render(
<PaneProvider value={paneContext}>
<PaneFocusProvider value={paneFocus}>
<FilePane serverId="server" workspaceRoot="/repo" filePath={filePath} />
<FilePane serverId="server" workspaceRoot="/repo" location={location} />
</PaneFocusProvider>
</PaneProvider>,
);
@@ -325,7 +326,7 @@ describe("FilePane preview rendering", () => {
isFetching: false,
};
renderFilePane("README.md");
renderFilePane({ path: "README.md" });
expect(container?.querySelector('[data-testid="markdown-preview"]')?.textContent).toBe(
"# Guide",
@@ -342,7 +343,7 @@ describe("FilePane preview rendering", () => {
isFetching: false,
};
renderFilePane("logo.png");
renderFilePane({ path: "logo.png" });
expect(container?.querySelector("img")?.getAttribute("src")).toBe("blob:preview");
});
@@ -362,7 +363,7 @@ describe("FilePane preview rendering", () => {
isFetching: false,
};
renderFilePane("tool");
renderFilePane({ path: "tool" });
expect(container?.textContent).toContain("Binary preview unavailable");
expect(container?.textContent).toContain("2.0 KB");
+5 -3
View File
@@ -238,7 +238,10 @@ interface CodeLineTokenProps {
function CodeLineToken({ backgroundColor, style, text }: CodeLineTokenProps) {
const tokenStyle = useMemo(
() => [style ? syntaxTokenStyleFor(style) : undefined, backgroundColor ? { backgroundColor } : null],
() => [
style ? syntaxTokenStyleFor(style) : undefined,
backgroundColor ? { backgroundColor } : null,
],
[backgroundColor, style],
);
return <Text style={tokenStyle}>{text}</Text>;
@@ -748,12 +751,11 @@ function FilePreviewBody({
} else if (preview.kind === "text" && textRenderData) {
content = (
<FilePaneSearchableTextPreview
baseColor={baseColor}
colorMap={colorMap}
currentMatchBackgroundColor={currentMatchBackgroundColor}
gutterWidth={gutterWidth}
isMarkdownFile={isMarkdownFile}
isMobile={isMobile}
lineSelection={lineSelection}
markdownParser={markdownParser}
markdownStyles={markdownStyles}
matchBackgroundColor={matchBackgroundColor}
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { getNativeScrollToIndexFallbackOffset } from "./stream-strategy-native";
import { getNativeScrollToIndexFallbackOffset } from "@/agent-stream/strategy-native";
describe("getNativeScrollToIndexFallbackOffset", () => {
it("approximates the offset for an unmeasured native FlatList row", () => {
@@ -312,18 +312,6 @@ export default function TerminalEmulator({
blur: () => {
runtimeRef.current?.blur();
},
findNext: (input: { query: string }) => {
return runtimeRef.current?.findNext(input) ?? false;
},
findPrevious: (input: { query: string }) => {
return runtimeRef.current?.findPrevious(input) ?? false;
},
clearFindDecorations: () => {
runtimeRef.current?.clearFindDecorations();
},
onFindResultsChanged: (listener: (event: TerminalFindResultChangeEvent) => void) => {
return runtimeRef.current?.onFindResultsChanged(listener) ?? (() => {});
},
}),
[],
);
@@ -28,8 +28,10 @@ const { client, findListeners, terminalProps, theme, terminalHandle, resetTermin
const listeners: Array<(event: FindResultChange) => void> = [];
const handle = {
writeOutput: vi.fn(),
restoreOutput: vi.fn(),
renderSnapshot: vi.fn(),
clear: vi.fn(),
blur: vi.fn(),
findNext: vi.fn(() => true),
findPrevious: vi.fn(() => true),
clearFindDecorations: vi.fn(),
@@ -278,6 +280,7 @@ const paneContext: PaneContextValue = {
closeCurrentTab: () => {},
retargetCurrentTab: () => {},
openFileInWorkspace: () => {},
openImportSheet: () => {},
};
const paneFocus = createPaneFocusContextValue({
isPaneFocused: true,
@@ -323,6 +326,7 @@ function renderTerminalPane(): void {
isWorkspaceFocused
isPaneFocused
onOpenFileExplorer={vi.fn()}
onOpenWorkspaceFile={vi.fn()}
/>
</PaneFocusProvider>
</PaneProvider>,
@@ -222,6 +222,7 @@ const harnessPaneContext: PaneContextValue = {
closeCurrentTab: () => {},
retargetCurrentTab: () => {},
openFileInWorkspace: () => {},
openImportSheet: () => {},
};
const harnessPaneFocus = createPaneFocusContextValue({
isPaneFocused: true,
@@ -451,6 +452,7 @@ describe("FindBar", () => {
onCloseCurrentTab: vi.fn(),
onRetargetCurrentTab: vi.fn(),
onOpenWorkspaceFile: vi.fn(),
onOpenImportSheet: vi.fn(),
});
const rightContent = buildWorkspacePaneContentModel({
@@ -462,6 +464,7 @@ describe("FindBar", () => {
onCloseCurrentTab: vi.fn(),
onRetargetCurrentTab: vi.fn(),
onOpenWorkspaceFile: vi.fn(),
onOpenImportSheet: vi.fn(),
});
const focusLeft = vi.fn();
const focusRight = vi.fn();