mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 12:12:47 +00:00
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:
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user