Skip to content

Commit 234acf0

Browse files
authored
Declutter task sidebar and folder picker
Remove the worktree and PR chips from sidebar task items, leaving just the title, status icon, timestamp, and hover actions. In the new-task view, the folder picker now lists only main repos when worktree mode is selected, since the app creates its own worktree checkout anyway. Generated-By: PostHog Code Task-Id: 95a9bff0-8006-4988-a47b-46a3b8268410
1 parent 9517ec0 commit 234acf0

5 files changed

Lines changed: 43 additions & 96 deletions

File tree

packages/ui/src/features/folder-picker/FolderPicker.test.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ describe("buildFolderRows", () => {
122122
name: string;
123123
recents: RegisteredFolder[];
124124
all: RegisteredFolder[];
125+
mainReposOnly?: boolean;
125126
expected: Array<{ id: string; isWorktree: boolean; indented: boolean }>;
126127
}>([
127128
{
@@ -160,9 +161,29 @@ describe("buildFolderRows", () => {
160161
all: [wtA, main, standalone],
161162
expected: [{ id: "other", isWorktree: false, indented: false }],
162163
},
163-
])("$name", ({ recents, all, expected }) => {
164+
{
165+
name: "mainReposOnly collapses families to their main clone",
166+
recents: [wtB, standalone],
167+
all: [wtA, main, wtB, standalone],
168+
mainReposOnly: true,
169+
expected: [
170+
{ id: "code", isWorktree: false, indented: false },
171+
{ id: "other", isWorktree: false, indented: false },
172+
],
173+
},
174+
{
175+
name: "mainReposOnly keeps worktrees without a registered main clone",
176+
recents: [wtA],
177+
all: [wtA, wtB, standalone],
178+
mainReposOnly: true,
179+
expected: [
180+
{ id: "code-a", isWorktree: true, indented: false },
181+
{ id: "code-b", isWorktree: true, indented: false },
182+
],
183+
},
184+
])("$name", ({ recents, all, mainReposOnly, expected }) => {
164185
expect(
165-
buildFolderRows(recents, all).map((row) => ({
186+
buildFolderRows(recents, all, mainReposOnly).map((row) => ({
166187
id: row.folder.id,
167188
isWorktree: row.isWorktree,
168189
indented: row.indented,

packages/ui/src/features/folder-picker/FolderPicker.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,15 @@ interface FolderRow {
4545
* Groups recent folders into repo families: a recent checkout pulls in its
4646
* main clone and every registered worktree of the same repo, main first,
4747
* worktrees indented beneath it. Standalone folders stay single rows.
48+
*
49+
* With `mainReposOnly`, worktrees whose main clone is in the list are dropped
50+
* so only one row per repo remains; worktrees without a registered main stay,
51+
* as they are the repo's only selectable entry.
4852
*/
4953
export function buildFolderRows(
5054
recentFolders: RegisteredFolder[],
5155
allFolders: RegisteredFolder[],
56+
mainReposOnly = false,
5257
): FolderRow[] {
5358
const familyKey = (f: RegisteredFolder) => f.mainRepoPath ?? f.path;
5459
const emitted = new Set<string>();
@@ -63,6 +68,7 @@ export function buildFolderRows(
6368
.filter((f) => f.mainRepoPath)
6469
.sort((a, b) => a.name.localeCompare(b.name));
6570
if (main) rows.push({ folder: main, isWorktree: false, indented: false });
71+
if (main && mainReposOnly) continue;
6672
for (const wt of worktrees) {
6773
rows.push({ folder: wt, isWorktree: true, indented: !!main });
6874
}
@@ -76,6 +82,8 @@ interface FolderPickerProps {
7682
placeholder?: string;
7783
variant?: "compact" | "field";
7884
anchor?: RefObject<HTMLElement | null>;
85+
/** Collapse each repo family to its main clone, hiding worktree rows. */
86+
mainReposOnly?: boolean;
7987
}
8088

8189
export function FolderPicker({
@@ -84,6 +92,7 @@ export function FolderPicker({
8492
placeholder = "Select folder...",
8593
variant = "compact",
8694
anchor,
95+
mainReposOnly = false,
8796
}: FolderPickerProps) {
8897
const trpcClient = useHostTRPCClient();
8998
const trpc = useHostTRPC();
@@ -106,8 +115,8 @@ export function FolderPicker({
106115
const [menuOpen, setMenuOpen] = useState(false);
107116

108117
const folderRows = useMemo(
109-
() => buildFolderRows(recentFolders, folders),
110-
[recentFolders, folders],
118+
() => buildFolderRows(recentFolders, folders, mainReposOnly),
119+
[recentFolders, folders, mainReposOnly],
111120
);
112121

113122
// Current branch per visible row, so the picker answers "what is checked

packages/ui/src/features/sidebar/components/TaskListView.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {
1212
TaskGroup,
1313
} from "@posthog/core/sidebar/sidebarData.types";
1414
import { MenuLabel } from "@posthog/quill";
15-
import { getFileName } from "@posthog/shared";
1615
import { builderHog } from "@posthog/ui/assets/hedgehogs";
1716
import { useFolders } from "@posthog/ui/features/folders/useFolders";
1817
import { useArchivingTasksStore } from "@posthog/ui/features/sidebar/archivingTasksStore";
@@ -90,32 +89,9 @@ function TaskRow({
9089
depth?: number;
9190
}) {
9291
const workspace = useWorkspace(task.id);
93-
const { folders } = useFolders();
9492
const effectiveMode =
9593
workspace?.mode ??
9694
(task.taskRunEnvironment === "cloud" ? "cloud" : undefined);
97-
98-
// Chip identifying the checkout the task runs in: app-managed worktrees
99-
// (worktree mode) and local tasks whose registered folder is itself a
100-
// linked worktree of the group's main clone.
101-
const worktreeCheckout = useMemo(() => {
102-
if (workspace?.worktreePath) {
103-
return {
104-
name: workspace.worktreeName ?? getFileName(workspace.worktreePath),
105-
path: workspace.worktreePath,
106-
};
107-
}
108-
if (workspace?.mode === "local" && workspace.folderPath) {
109-
const folder = folders.find((f) => f.path === workspace.folderPath);
110-
if (folder?.mainRepoPath) {
111-
return {
112-
name: getFileName(workspace.folderPath),
113-
path: workspace.folderPath,
114-
};
115-
}
116-
}
117-
return null;
118-
}, [workspace, folders]);
11995
const { prState, hasDiff } = useTaskPrStatus(task);
12096
const isArchiving = useArchivingTasksStore((s) =>
12197
s.archivingTaskIds.has(task.id),
@@ -132,8 +108,6 @@ function TaskRow({
132108
hideHoverActions={hideHoverActions}
133109
isEditing={isEditing}
134110
workspaceMode={effectiveMode}
135-
worktreeName={worktreeCheckout?.name}
136-
worktreePath={worktreeCheckout?.path}
137111
isSuspended={task.isSuspended}
138112
isGenerating={task.isGenerating}
139113
isUnread={task.isUnread}
@@ -144,7 +118,6 @@ function TaskRow({
144118
slackThreadUrl={task.slackThreadUrl}
145119
prState={prState}
146120
hasDiff={hasDiff}
147-
prUrl={task.cloudPrUrl}
148121
timestamp={timestamp}
149122
onClick={onClick}
150123
onDoubleClick={onDoubleClick}

packages/ui/src/features/sidebar/components/items/TaskItem.tsx

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,15 @@
1-
import {
2-
Archive,
3-
GitFork,
4-
GitPullRequest,
5-
PushPin,
6-
} from "@phosphor-icons/react";
7-
import { parseGithubUrl } from "@posthog/git/utils";
1+
import { Archive, PushPin } from "@phosphor-icons/react";
82
import type { WorkspaceMode } from "@posthog/shared";
93
import { formatRelativeTimeShort } from "@posthog/shared";
104
import type { TaskRunStatus } from "@posthog/shared/domain-types";
11-
import { navigateToPullRequestView } from "@posthog/ui/router/navigationBridge";
12-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5+
import { useCallback, useEffect, useRef, useState } from "react";
136
import { DotsCircleSpinner } from "../../../../primitives/DotsCircleSpinner";
147
import { NestedButton } from "../../../../primitives/NestedButton";
158
import { Tooltip } from "../../../../primitives/Tooltip";
169
import type { SidebarPrState } from "../../useTaskPrStatus";
1710
import { SidebarItem } from "../SidebarItem";
1811
import { ICON_SIZE, TaskIcon } from "./TaskIcon";
1912

20-
function PrBadge({ url, number }: { url: string; number: number }) {
21-
return (
22-
<Tooltip content="Open pull request" side="top">
23-
<NestedButton
24-
aria-label={`Open pull request #${number}`}
25-
className="flex h-4 shrink-0 cursor-pointer items-center gap-0.5 rounded bg-gray-3 px-1 text-[11px] text-gray-11 transition-colors hover:bg-gray-4 hover:text-gray-12"
26-
onActivate={() => {
27-
navigateToPullRequestView(url);
28-
}}
29-
>
30-
<GitPullRequest size={10} weight="bold" />
31-
{`#${number}`}
32-
</NestedButton>
33-
</Tooltip>
34-
);
35-
}
36-
37-
function WorktreeChip({ name, path }: { name: string; path?: string }) {
38-
return (
39-
<Tooltip content={path ?? name} side="top">
40-
<span className="flex h-4 max-w-[96px] shrink-0 items-center gap-0.5 rounded bg-gray-3 px-1 text-[11px] text-gray-11">
41-
<GitFork size={10} weight="bold" className="shrink-0" />
42-
<span className="truncate">{name}</span>
43-
</span>
44-
</Tooltip>
45-
);
46-
}
47-
4813
interface TaskItemProps {
4914
depth?: number;
5015
taskId: string;
@@ -55,10 +20,6 @@ interface TaskItemProps {
5520
isArchiving?: boolean;
5621
hideHoverActions?: boolean;
5722
workspaceMode?: WorkspaceMode;
58-
/** Checkout name shown as a chip when the task runs in a git worktree. */
59-
worktreeName?: string;
60-
/** Full path of that checkout, surfaced in the chip's tooltip. */
61-
worktreePath?: string;
6223
isGenerating?: boolean;
6324
isUnread?: boolean;
6425
isPinned?: boolean;
@@ -69,7 +30,6 @@ interface TaskItemProps {
6930
slackThreadUrl?: string;
7031
prState?: SidebarPrState;
7132
hasDiff?: boolean;
72-
prUrl?: string | null;
7333
timestamp?: number;
7434
isEditing?: boolean;
7535
onClick: (e: React.MouseEvent) => void;
@@ -131,8 +91,6 @@ export function TaskItem({
13191
isArchiving = false,
13292
hideHoverActions = false,
13393
workspaceMode,
134-
worktreeName,
135-
worktreePath,
13694
isSuspended = false,
13795
isGenerating,
13896
isUnread,
@@ -143,7 +101,6 @@ export function TaskItem({
143101
slackThreadUrl,
144102
prState,
145103
hasDiff,
146-
prUrl,
147104
timestamp,
148105
isEditing = false,
149106
onClick,
@@ -172,19 +129,11 @@ export function TaskItem({
172129
/>
173130
);
174131

175-
const prRef = useMemo(() => (prUrl ? parseGithubUrl(prUrl) : null), [prUrl]);
176-
const prBadge =
177-
prUrl && prRef?.kind === "pr" ? (
178-
<PrBadge url={prUrl} number={prRef.number} />
179-
) : null;
180-
181-
// The PR badge takes the timestamp's slot, so hide the timestamp when shown.
182-
const timestampNode =
183-
timestamp && !prBadge ? (
184-
<span className="shrink-0 text-[11px] text-gray-11 group-hover:hidden">
185-
{formatRelativeTimeShort(timestamp)}
186-
</span>
187-
) : null;
132+
const timestampNode = timestamp ? (
133+
<span className="shrink-0 text-[11px] text-gray-11 group-hover:hidden">
134+
{formatRelativeTimeShort(timestamp)}
135+
</span>
136+
) : null;
188137

189138
const toolbar =
190139
!isArchiving && !hideHoverActions && (onArchive || onTogglePin) ? (
@@ -195,15 +144,9 @@ export function TaskItem({
195144
/>
196145
) : null;
197146

198-
const worktreeChip = worktreeName ? (
199-
<WorktreeChip name={worktreeName} path={worktreePath} />
200-
) : null;
201-
202147
const endContent =
203-
worktreeChip || prBadge || timestampNode || toolbar ? (
148+
timestampNode || toolbar ? (
204149
<>
205-
{worktreeChip}
206-
{prBadge}
207150
{timestampNode}
208151
{toolbar}
209152
</>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,7 @@ export function TaskInput({
11271127
onChange={setSelectedDirectory}
11281128
placeholder="Select repository..."
11291129
anchor={buttonGroupRef}
1130+
mainReposOnly={workspaceMode === "worktree"}
11301131
/>
11311132
)}
11321133
<BranchSelector

0 commit comments

Comments
 (0)