Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 80de28f

Browse files
authored
Clarify diff view controls (#3906)
1 parent bf01d21 commit 80de28f

6 files changed

Lines changed: 86 additions & 24 deletions

File tree

packages/ui/src/features/command/CommandMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
340340
? [
341341
{
342342
id: "open-review-panel",
343-
label: "Open review panel",
343+
label: "Open diff view",
344344
icon: (
345345
<ViewVerticalIcon className="h-3 w-3 rotate-180 text-gray-11" />
346346
),

packages/ui/src/features/command/keyboard-shortcuts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [
194194
{
195195
id: "toggle-review-panel",
196196
keys: SHORTCUTS.TOGGLE_REVIEW_PANEL,
197-
description: "Toggle review panel",
197+
description: "Toggle diff view",
198198
category: "navigation",
199199
},
200200
{

packages/ui/src/features/panels/components/LeafNodeRenderer.tsx

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Cloud as CloudIcon } from "@phosphor-icons/react";
2+
import {
3+
Empty,
4+
EmptyDescription,
5+
EmptyHeader,
6+
EmptyMedia,
7+
EmptyTitle,
8+
} from "@posthog/quill";
29
import type { Task } from "@posthog/shared/domain-types";
3-
import { Flex, Text } from "@radix-ui/themes";
410
import type React from "react";
511
import { useMemo } from "react";
612
import { useHostCapabilities } from "../../../shell/useHostCapabilities";
@@ -56,23 +62,29 @@ export const LeafNodeRenderer: React.FC<LeafNodeRendererProps> = ({
5662
const activeTabId = tabs.some((t) => t.id === node.content.activeTabId)
5763
? node.content.activeTabId
5864
: (tabs[0]?.id ?? node.content.activeTabId);
65+
const hiddenTabIds = useMemo(() => {
66+
const visibleTabIds = new Set(tabs.map((tab) => tab.id));
67+
const hiddenIds: string[] = [];
68+
for (const tab of node.content.tabs) {
69+
if (!visibleTabIds.has(tab.id)) hiddenIds.push(tab.id);
70+
}
71+
return hiddenIds;
72+
}, [node.content.tabs, tabs]);
5973

6074
const cloudEmptyState = useMemo(
6175
() =>
6276
isCloud ? (
63-
<Flex
64-
align="center"
65-
justify="center"
66-
height="100%"
67-
className="bg-(--gray-2)"
68-
>
69-
<Flex direction="column" align="center" gap="2">
70-
<CloudIcon size={24} className="text-gray-10" />
71-
<Text color="gray" className="text-sm">
72-
Cloud runs are read-only
73-
</Text>
74-
</Flex>
75-
</Flex>
77+
<Empty className="h-full border-0 bg-(--gray-2)">
78+
<EmptyHeader>
79+
<EmptyMedia variant="icon">
80+
<CloudIcon size={24} className="text-gray-10" />
81+
</EmptyMedia>
82+
<EmptyTitle>Cloud runs are read-only</EmptyTitle>
83+
<EmptyDescription>
84+
Local workspace tools are unavailable for this run.
85+
</EmptyDescription>
86+
</EmptyHeader>
87+
</Empty>
7688
) : undefined,
7789
[isCloud],
7890
);
@@ -95,8 +107,20 @@ export const LeafNodeRenderer: React.FC<LeafNodeRendererProps> = ({
95107
onPanelFocus={onPanelFocus}
96108
draggingTabId={draggingTabId}
97109
draggingTabPanelId={draggingTabPanelId}
110+
allowPanelSplit={!isCloud}
98111
onAddTerminal={hideTerminal ? undefined : () => onAddTerminal(node.id)}
99-
onSplitPanel={(direction) => onSplitPanel(node.id, direction)}
112+
onSplitPanel={
113+
isCloud ? undefined : (direction) => onSplitPanel(node.id, direction)
114+
}
115+
onClosePanel={
116+
tabs.length === 0 && hiddenTabIds.length > 0
117+
? () => {
118+
for (const tabId of hiddenTabIds) {
119+
closeTab(taskId, node.id, tabId);
120+
}
121+
}
122+
: undefined
123+
}
100124
emptyState={cloudEmptyState}
101125
/>
102126
);

packages/ui/src/features/panels/components/TabbedPanel.test.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { render, screen } from "@testing-library/react";
33
import { describe, expect, it, vi } from "vitest";
44
import type { PanelContent } from "../panelTypes";
55

6+
const panelDropZonesSpy = vi.hoisted(() =>
7+
vi.fn((_props: { allowSplit?: boolean }) => null),
8+
);
9+
610
vi.mock("@dnd-kit/react", () => ({
711
useDroppable: () => ({ ref: vi.fn() }),
812
}));
@@ -16,7 +20,7 @@ vi.mock("@posthog/host-router/react", () => ({
1620
}));
1721

1822
vi.mock("./PanelDropZones", () => ({
19-
PanelDropZones: () => null,
23+
PanelDropZones: panelDropZonesSpy,
2024
}));
2125

2226
vi.mock("./PanelTab", () => ({
@@ -52,6 +56,26 @@ function content(activeTabId: string): PanelContent {
5256
}
5357

5458
describe("TabbedPanel", () => {
59+
it("disables split drop zones when panel splitting is unavailable", () => {
60+
const droppableContent = { ...content("logs"), droppable: true };
61+
62+
render(
63+
<Theme>
64+
<TabbedPanel
65+
panelId="main"
66+
mountScopeKey="task-a"
67+
content={droppableContent}
68+
draggingTabId="logs"
69+
allowPanelSplit={false}
70+
/>
71+
</Theme>,
72+
);
73+
74+
expect(panelDropZonesSpy.mock.lastCall?.[0]).toEqual(
75+
expect.objectContaining({ allowSplit: false }),
76+
);
77+
});
78+
5579
it("retains visited tabs within a task and resets them for another task", () => {
5680
const { rerender } = render(
5781
<Theme>

packages/ui/src/features/panels/components/TabbedPanel.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useDroppable } from "@dnd-kit/react";
2-
import { Plus, SquareSplitHorizontalIcon } from "@phosphor-icons/react";
2+
import { Plus, SquareSplitHorizontalIcon, X } from "@phosphor-icons/react";
33
import { useHostTRPCClient } from "@posthog/host-router/react";
44
import { PanelDropZones } from "@posthog/ui/features/panels/components/PanelDropZones";
55
import type { SplitDirection } from "@posthog/ui/features/panels/panelLayoutStore";
@@ -65,8 +65,10 @@ interface TabbedPanelProps {
6565
onPanelFocus?: (panelId: string) => void;
6666
draggingTabId?: string | null;
6767
draggingTabPanelId?: string | null;
68+
allowPanelSplit?: boolean;
6869
onAddTerminal?: () => void;
6970
onSplitPanel?: (direction: SplitDirection) => void;
71+
onClosePanel?: () => void;
7072
rightContent?: React.ReactNode;
7173
emptyState?: React.ReactNode;
7274
}
@@ -82,8 +84,10 @@ export const TabbedPanel: React.FC<TabbedPanelProps> = ({
8284
onPanelFocus,
8385
draggingTabId = null,
8486
draggingTabPanelId = null,
87+
allowPanelSplit = true,
8588
onAddTerminal,
8689
onSplitPanel,
90+
onClosePanel,
8791
rightContent,
8892
emptyState,
8993
}) => {
@@ -230,12 +234,21 @@ export const TabbedPanel: React.FC<TabbedPanelProps> = ({
230234
<Box flexShrink="0" className="h-[32px] min-w-[90px]" />
231235
)}
232236
</Flex>
233-
{(rightContent || (content.droppable && onSplitPanel)) && (
237+
{(rightContent ||
238+
onClosePanel ||
239+
(content.droppable && onSplitPanel)) && (
234240
<Flex
235241
align="center"
236242
className="absolute top-0 right-0 h-[32px] border-b border-b-(--gray-6) border-l border-l-(--gray-6) bg-(--color-background)"
237243
>
238244
{rightContent}
245+
{onClosePanel && (
246+
<Tooltip content="Close panel" side="bottom">
247+
<TabBarButton ariaLabel="Close panel" onClick={onClosePanel}>
248+
<X size={14} />
249+
</TabBarButton>
250+
</Tooltip>
251+
)}
239252
{content.droppable && onSplitPanel && (
240253
<Tooltip content="Split panel" side="bottom">
241254
<TabBarButton
@@ -296,11 +309,12 @@ export const TabbedPanel: React.FC<TabbedPanelProps> = ({
296309
panelId={panelId}
297310
isDragging={!!draggingTabId}
298311
allowSplit={
299-
// Allow split if:
312+
allowPanelSplit &&
313+
// Within a splittable layout, allow the edge drop zones if:
300314
// 1. Current panel has > 1 tab (same-panel split), OR
301315
// 2. Dragging from a different panel (cross-panel split)
302-
content.tabs.length > 1 ||
303-
(draggingTabPanelId !== null && draggingTabPanelId !== panelId)
316+
(content.tabs.length > 1 ||
317+
(draggingTabPanelId !== null && draggingTabPanelId !== panelId))
304318
}
305319
/>
306320
)}

packages/ui/src/features/task-detail/components/TaskHeaderActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function TaskDiffStatsBadge({ task }: { task: Task }) {
113113
useDiffStatsToggle(task, "split");
114114
return (
115115
<Tooltip
116-
content={isOpen ? "Close review panel" : "Open review panel"}
116+
content={isOpen ? "Close diff view" : "Open diff view"}
117117
shortcut={formatHotkey(SHORTCUTS.TOGGLE_REVIEW_PANEL)}
118118
side="bottom"
119119
>

0 commit comments

Comments
 (0)