Skip to content

Commit b489ea5

Browse files
Improve inline panel, file preview, and MCP session handling (pingdotgg#3121)
1 parent 3a5ec94 commit b489ea5

7 files changed

Lines changed: 490 additions & 31 deletions

File tree

apps/web/src/components/ChatMarkdown.browser.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,13 @@ describe("ChatMarkdown", () => {
288288
).toMatchObject({
289289
isOpen: true,
290290
activeSurfaceId: "file:apps/web/src/components/ChatMarkdown.tsx",
291+
surfaces: [
292+
expect.objectContaining({
293+
relativePath: "apps/web/src/components/ChatMarkdown.tsx",
294+
revealLine: 978,
295+
revealRequestId: 1,
296+
}),
297+
],
291298
});
292299
expect(openInPreferredEditorMock).not.toHaveBeenCalled();
293300
expect(openFileInPreviewMock).not.toHaveBeenCalled();

apps/web/src/components/ChatMarkdown.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ interface MarkdownFileLinkProps {
671671
iconPath: string;
672672
displayPath: string;
673673
workspaceRelativePath: string | null;
674+
line?: number | undefined;
674675
label: string;
675676
copyMarkdown: string;
676677
theme: "light" | "dark";
@@ -995,6 +996,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
995996
iconPath,
996997
displayPath,
997998
workspaceRelativePath,
999+
line,
9981000
label,
9991001
copyMarkdown,
10001002
theme,
@@ -1027,8 +1029,8 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
10271029
handleOpenInEditor();
10281030
return;
10291031
}
1030-
useRightPanelStore.getState().openFile(threadRef, workspaceRelativePath);
1031-
}, [handleOpenInEditor, threadRef, workspaceRelativePath]);
1032+
useRightPanelStore.getState().openFile(threadRef, workspaceRelativePath, line);
1033+
}, [handleOpenInEditor, line, threadRef, workspaceRelativePath]);
10321034

10331035
const handleOpenInBrowser = useCallback(() => {
10341036
if (!threadRef) return;
@@ -1169,6 +1171,7 @@ function areMarkdownFileLinkPropsEqual(
11691171
previous.iconPath === next.iconPath &&
11701172
previous.displayPath === next.displayPath &&
11711173
previous.workspaceRelativePath === next.workspaceRelativePath &&
1174+
previous.line === next.line &&
11721175
previous.label === next.label &&
11731176
previous.copyMarkdown === next.copyMarkdown &&
11741177
previous.theme === next.theme &&
@@ -1331,6 +1334,7 @@ function ChatMarkdown({
13311334
iconPath={fileLinkMeta.filePath}
13321335
displayPath={fileLinkMeta.displayPath}
13331336
workspaceRelativePath={fileLinkMeta.workspaceRelativePath}
1337+
line={fileLinkMeta.line}
13341338
label={labelParts.join(" · ")}
13351339
copyMarkdown={`[${fileLinkMeta.basename}](${normalizedHref})`}
13361340
theme={resolvedTheme}

apps/web/src/components/ChatView.browser.tsx

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,13 +2700,97 @@ describe("ChatView timeline estimator parity (full app)", () => {
27002700
expect(fileTree.shadowRoot?.activeElement).toBe(fileSearchInput);
27012701
expect(useComposerDraftStore.getState().draftsByThreadKey[THREAD_KEY]?.prompt ?? "").toBe("");
27022702

2703-
useRightPanelStore.getState().openFile(THREAD_REF, "src/large.ts");
2704-
const codeVirtualizer = await waitForElement(
2705-
() => document.querySelector<HTMLElement>(".file-preview-virtualizer"),
2706-
"Unable to find the virtualized file preview.",
2707-
);
2703+
const previousCodeVirtualizer = document.querySelector<HTMLElement>(
2704+
".file-preview-virtualizer",
2705+
);
2706+
useRightPanelStore.getState().openFile(THREAD_REF, "src/large.ts", 4_000);
2707+
const codeVirtualizer = await waitForElement(() => {
2708+
const current = document.querySelector<HTMLElement>(".file-preview-virtualizer");
2709+
return current !== previousCodeVirtualizer ? current : null;
2710+
}, "Unable to find the virtualized file preview.");
27082711
expect(codeVirtualizer.querySelector("diffs-container")).not.toBeNull();
27092712
expect(codeVirtualizer.classList.contains("overflow-auto")).toBe(true);
2713+
await vi.waitFor(
2714+
() => {
2715+
const fileHost = codeVirtualizer.querySelector<HTMLElement>("diffs-container");
2716+
const targetLine = fileHost?.shadowRoot?.querySelector<HTMLElement>('[data-line="4000"]');
2717+
const targetLineNumber = fileHost?.shadowRoot?.querySelector<HTMLElement>(
2718+
'[data-column-number="4000"]',
2719+
);
2720+
const previousLine =
2721+
fileHost?.shadowRoot?.querySelector<HTMLElement>('[data-line="3999"]');
2722+
const previousLineNumber = fileHost?.shadowRoot?.querySelector<HTMLElement>(
2723+
'[data-column-number="3999"]',
2724+
);
2725+
expect(codeVirtualizer.scrollTop).toBeGreaterThan(0);
2726+
expect(targetLine).not.toBeNull();
2727+
expect(previousLine).not.toBeNull();
2728+
expect(targetLine?.hasAttribute("data-file-link-reveal")).toBe(true);
2729+
expect(targetLineNumber?.hasAttribute("data-file-link-reveal")).toBe(true);
2730+
expect(targetLine?.hasAttribute("data-selected-line")).toBe(false);
2731+
expect(targetLineNumber?.hasAttribute("data-selected-line")).toBe(false);
2732+
expect(targetLineNumber?.querySelector("[data-gutter-utility-slot]")).toBeNull();
2733+
expect(window.getComputedStyle(targetLine!).backgroundColor).not.toBe(
2734+
window.getComputedStyle(previousLine!).backgroundColor,
2735+
);
2736+
expect(window.getComputedStyle(targetLineNumber!).backgroundColor).not.toBe(
2737+
window.getComputedStyle(previousLineNumber!).backgroundColor,
2738+
);
2739+
2740+
const viewportRect = codeVirtualizer.getBoundingClientRect();
2741+
const lineRect = targetLine!.getBoundingClientRect();
2742+
expect(lineRect.top).toBeGreaterThanOrEqual(viewportRect.top);
2743+
expect(lineRect.bottom).toBeLessThanOrEqual(viewportRect.bottom);
2744+
},
2745+
{ timeout: 8_000, interval: 16 },
2746+
);
2747+
2748+
const fileHost = codeVirtualizer.querySelector<HTMLElement>("diffs-container");
2749+
const targetLineNumber =
2750+
fileHost?.shadowRoot?.querySelector<HTMLElement>('[data-column-number="4000"]') ?? null;
2751+
const previousLineNumber =
2752+
fileHost?.shadowRoot?.querySelector<HTMLElement>('[data-column-number="3999"]') ?? null;
2753+
expect(targetLineNumber).not.toBeNull();
2754+
expect(previousLineNumber).not.toBeNull();
2755+
2756+
targetLineNumber!.dispatchEvent(
2757+
new PointerEvent("pointermove", {
2758+
bubbles: true,
2759+
cancelable: true,
2760+
composed: true,
2761+
pointerType: "mouse",
2762+
}),
2763+
);
2764+
await vi.waitFor(() => {
2765+
expect(targetLineNumber?.querySelector("[data-gutter-utility-slot]")).not.toBeNull();
2766+
});
2767+
2768+
previousLineNumber!.dispatchEvent(
2769+
new PointerEvent("pointermove", {
2770+
bubbles: true,
2771+
cancelable: true,
2772+
composed: true,
2773+
pointerType: "mouse",
2774+
}),
2775+
);
2776+
await vi.waitFor(() => {
2777+
expect(targetLineNumber?.querySelector("[data-gutter-utility-slot]")).toBeNull();
2778+
expect(previousLineNumber?.querySelector("[data-gutter-utility-slot]")).not.toBeNull();
2779+
});
2780+
2781+
codeVirtualizer.scrollTop = 0;
2782+
useRightPanelStore.getState().openFile(THREAD_REF, "src/large.ts", 4_000);
2783+
await vi.waitFor(
2784+
() => {
2785+
const fileHost = codeVirtualizer.querySelector<HTMLElement>("diffs-container");
2786+
const targetLine = fileHost?.shadowRoot?.querySelector<HTMLElement>('[data-line="4000"]');
2787+
expect(targetLine).not.toBeNull();
2788+
expect(targetLine?.hasAttribute("data-file-link-reveal")).toBe(true);
2789+
expect(targetLine?.hasAttribute("data-selected-line")).toBe(false);
2790+
expect(codeVirtualizer.scrollTop).toBeGreaterThan(0);
2791+
},
2792+
{ timeout: 8_000, interval: 16 },
2793+
);
27102794
} finally {
27112795
await mounted.cleanup();
27122796
}

apps/web/src/components/ChatView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,8 @@ function ChatViewContent(props: ChatViewProps) {
12921292
const activeRightPanelSurface = useRightPanelStore((store) =>
12931293
selectActiveRightPanelSurface(store.byThreadKey, activeThreadRef),
12941294
);
1295+
const activeFileSurface =
1296+
activeRightPanelSurface?.kind === "file" ? activeRightPanelSurface : null;
12951297
const activePreviewState = usePreviewStateStore((state) =>
12961298
selectThreadPreviewState(state.byThreadKey, activeThreadRef),
12971299
);
@@ -4768,6 +4770,8 @@ function ChatViewContent(props: ChatViewProps) {
47684770
relativePath={
47694771
activeRightPanelSurface.kind === "file" ? activeRightPanelSurface.relativePath : null
47704772
}
4773+
revealLine={activeFileSurface?.revealLine ?? null}
4774+
revealRequestId={activeFileSurface?.revealRequestId ?? 0}
47714775
onOpenFile={openFileSurface}
47724776
onPendingChange={handleFilePendingChange}
47734777
/>

0 commit comments

Comments
 (0)