Skip to content

Commit 306e1bf

Browse files
authored
fix/minor issues (#167)
1 parent f0d2800 commit 306e1bf

File tree

28 files changed

+216
-461
lines changed

28 files changed

+216
-461
lines changed

apps/array/src/main/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ function createWindow(): void {
8787
contextIsolation: true,
8888
preload: path.join(__dirname, "preload.js"),
8989
enableBlinkFeatures: "GetDisplayMedia",
90+
partition: "persist:main",
9091
},
9192
});
9293

apps/array/src/main/preload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
437437
ipcRenderer.invoke("remove-task-association", taskId),
438438
clearTaskWorktree: (taskId: string): Promise<void> =>
439439
ipcRenderer.invoke("clear-task-worktree", taskId),
440+
cleanupOrphanedWorktrees: (
441+
mainRepoPath: string,
442+
): Promise<{
443+
deleted: string[];
444+
errors: Array<{ path: string; error: string }>;
445+
}> => ipcRenderer.invoke("cleanup-orphaned-worktrees", mainRepoPath),
440446
},
441447
// Worktree API
442448
worktree: {

apps/array/src/main/services/folders.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from "node:path";
2+
import { WorktreeManager } from "@posthog/agent";
23
import { type IpcMainInvokeEvent, ipcMain } from "electron";
34
import type {
45
RegisteredFolder,
@@ -144,6 +145,26 @@ async function clearTaskWorktree(taskId: string): Promise<void> {
144145
}
145146
}
146147

148+
async function cleanupOrphanedWorktreesForFolder(
149+
mainRepoPath: string,
150+
): Promise<{
151+
deleted: string[];
152+
errors: Array<{ path: string; error: string }>;
153+
}> {
154+
const manager = new WorktreeManager({ mainRepoPath });
155+
156+
const associations = foldersStore.get("taskAssociations", []);
157+
const associatedWorktreePaths: string[] = [];
158+
159+
for (const assoc of associations) {
160+
if (assoc.worktree?.worktreePath) {
161+
associatedWorktreePaths.push(assoc.worktree.worktreePath);
162+
}
163+
}
164+
165+
return await manager.cleanupOrphanedWorktrees(associatedWorktreePaths);
166+
}
167+
147168
export function registerFoldersIpc(): void {
148169
ipcMain.handle(
149170
"get-folders",
@@ -292,4 +313,28 @@ export function registerFoldersIpc(): void {
292313
}
293314
},
294315
);
316+
317+
ipcMain.handle(
318+
"cleanup-orphaned-worktrees",
319+
async (
320+
_event: IpcMainInvokeEvent,
321+
mainRepoPath: string,
322+
): Promise<{
323+
deleted: string[];
324+
errors: Array<{ path: string; error: string }>;
325+
}> => {
326+
try {
327+
return await cleanupOrphanedWorktreesForFolder(mainRepoPath);
328+
} catch (error) {
329+
log.error(
330+
`Failed to cleanup orphaned worktrees for ${mainRepoPath}:`,
331+
error,
332+
);
333+
return {
334+
deleted: [],
335+
errors: [{ path: mainRepoPath, error: String(error) }],
336+
};
337+
}
338+
},
339+
);
295340
}

apps/array/src/renderer/features/logs/components/LogEventRenderer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type { AgentEvent } from "@posthog/agent";
1313
import { Code, ContextMenu } from "@radix-ui/themes";
1414
import { IS_DEV } from "@/constants/environment";
1515

16-
// biome-ignore lint/suspicious/noExplicitAny: Components are type-safe internally, map handles dispatch
1716
const EVENT_COMPONENT_MAP: Record<
1817
string,
1918
React.ComponentType<{ event: any }>

apps/array/src/renderer/features/panels/store/panelLayoutStore.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe("panelLayoutStore", () => {
274274
const storedData = localStorage.getItem("panel-layout-store");
275275
expect(storedData).not.toBeNull();
276276

277-
const parsed = JSON.parse(storedData!);
277+
const parsed = JSON.parse(storedData ?? "");
278278
expect(parsed.state.taskLayouts["task-1"]).toBeDefined();
279279
expect(parsed.state.taskLayouts["task-1"].openFiles).toContain(
280280
"src/App.tsx",

apps/array/src/renderer/features/sidebar/components/SidebarMenu.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersSto
99
import { useWorktreeStore } from "@renderer/stores/worktreeStore";
1010
import type { Task } from "@shared/types";
1111
import { useNavigationStore } from "@stores/navigationStore";
12-
import { memo, useEffect, useRef } from "react";
12+
import { memo } from "react";
1313
import { useSidebarData } from "../hooks/useSidebarData";
1414
import { useSidebarStore } from "../stores/sidebarStore";
1515
import { HomeItem } from "./items/HomeItem";
@@ -29,15 +29,13 @@ function SidebarMenuComponent() {
2929
const { data: allTasks = [] } = useTasks();
3030
const { folders, removeFolder } = useRegisteredFoldersStore();
3131

32-
const expandedSections = useSidebarStore((state) => state.expandedSections);
32+
const collapsedSections = useSidebarStore((state) => state.collapsedSections);
3333
const toggleSection = useSidebarStore((state) => state.toggleSection);
3434
const taskWorktrees = useWorktreeStore((state) => state.taskWorktrees);
3535

3636
const { showContextMenu, renameTask, renameDialogOpen, setRenameDialogOpen } =
3737
useTaskContextMenu();
3838

39-
const seenFoldersRef = useRef<Set<string>>(new Set());
40-
4139
const sidebarData = useSidebarData({
4240
activeView: view,
4341
activeFilters,
@@ -49,21 +47,6 @@ function SidebarMenuComponent() {
4947
taskMap.set(task.id, task);
5048
}
5149

52-
useEffect(() => {
53-
const newFolders = sidebarData.folders.filter(
54-
(folder) =>
55-
!seenFoldersRef.current.has(folder.id) &&
56-
!expandedSections.has(folder.id),
57-
);
58-
59-
if (newFolders.length > 0) {
60-
for (const folder of newFolders) {
61-
seenFoldersRef.current.add(folder.id);
62-
toggleSection(folder.id);
63-
}
64-
}
65-
}, [sidebarData.folders, expandedSections, toggleSection]);
66-
6750
const handleHomeClick = () => {
6851
navigateToTaskInput();
6952
};
@@ -169,7 +152,7 @@ function SidebarMenuComponent() {
169152
id={folder.id}
170153
label={folder.name}
171154
icon={<FolderIcon size={14} weight="fill" />}
172-
isExpanded={expandedSections.has(folder.id)}
155+
isExpanded={!collapsedSections.has(folder.id)}
173156
onToggle={() => toggleSection(folder.id)}
174157
addSpacingBefore={index === 0}
175158
onContextMenu={(e) => handleFolderContextMenu(folder.id, e)}

apps/array/src/renderer/features/sidebar/components/items/ViewItem.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ListNumbersIcon } from "@phosphor-icons/react";
1+
import { ListNumbersIcon, UserIcon } from "@phosphor-icons/react";
22
import { SidebarItem } from "../SidebarItem";
33

44
interface ViewItemProps {
@@ -12,7 +12,11 @@ export function ViewItem({ label, isActive, onClick }: ViewItemProps) {
1212
<SidebarItem
1313
depth={0}
1414
icon={
15-
<ListNumbersIcon size={12} weight={isActive ? "fill" : "regular"} />
15+
label === "My tasks" ? (
16+
<UserIcon size={12} weight={isActive ? "fill" : "regular"} />
17+
) : (
18+
<ListNumbersIcon size={12} weight={isActive ? "fill" : "regular"} />
19+
)
1620
}
1721
label={label}
1822
isActive={isActive}

apps/array/src/renderer/features/sidebar/stores/sidebarStore.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface SidebarStoreState {
55
open: boolean;
66
width: number;
77
isResizing: boolean;
8-
expandedSections: Set<string>;
8+
collapsedSections: Set<string>;
99
}
1010

1111
interface SidebarStoreActions {
@@ -23,39 +23,39 @@ export const useSidebarStore = create<SidebarStore>()(
2323
open: true,
2424
width: 256,
2525
isResizing: false,
26-
expandedSections: new Set<string>(),
26+
collapsedSections: new Set<string>(),
2727
setOpen: (open) => set({ open }),
2828
setWidth: (width) => set({ width }),
2929
setIsResizing: (isResizing) => set({ isResizing }),
3030
toggleSection: (sectionId) =>
3131
set((state) => {
32-
const newExpandedSections = new Set(state.expandedSections);
33-
if (newExpandedSections.has(sectionId)) {
34-
newExpandedSections.delete(sectionId);
32+
const newCollapsedSections = new Set(state.collapsedSections);
33+
if (newCollapsedSections.has(sectionId)) {
34+
newCollapsedSections.delete(sectionId);
3535
} else {
36-
newExpandedSections.add(sectionId);
36+
newCollapsedSections.add(sectionId);
3737
}
38-
return { expandedSections: newExpandedSections };
38+
return { collapsedSections: newCollapsedSections };
3939
}),
4040
}),
4141
{
4242
name: "sidebar-storage",
4343
partialize: (state) => ({
4444
open: state.open,
4545
width: state.width,
46-
expandedSections: Array.from(state.expandedSections),
46+
collapsedSections: Array.from(state.collapsedSections),
4747
}),
4848
merge: (persisted, current) => {
4949
const persistedState = persisted as {
5050
open?: boolean;
5151
width?: number;
52-
expandedSections?: string[];
52+
collapsedSections?: string[];
5353
};
5454
return {
5555
...current,
5656
open: persistedState.open ?? current.open,
5757
width: persistedState.width ?? current.width,
58-
expandedSections: new Set(persistedState.expandedSections ?? []),
58+
collapsedSections: new Set(persistedState.collapsedSections ?? []),
5959
};
6060
},
6161
},

apps/array/src/renderer/features/task-detail/components/FileTreePanel.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ export function FileTreePanel({ taskId, task }: FileTreePanelProps) {
9393
error,
9494
} = useQuery({
9595
queryKey: ["directory", repoPath],
96-
queryFn: () => window.electronAPI.listDirectory(repoPath!),
96+
queryFn: () => {
97+
if (!repoPath) throw new Error("repoPath is required");
98+
return window.electronAPI.listDirectory(repoPath);
99+
},
97100
enabled: !!repoPath,
98101
staleTime: Infinity,
99102
});

apps/array/src/renderer/features/task-detail/components/TaskShellPanel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ShellTerminal } from "@features/terminal/components/ShellTerminal";
44
import { useTerminalStore } from "@features/terminal/stores/terminalStore";
55
import { Box } from "@radix-ui/themes";
66
import type { Task } from "@shared/types";
7+
import { useWorktreeStore } from "@stores/worktreeStore";
78
import { useEffect } from "react";
89

910
interface TaskShellPanelProps {
@@ -17,6 +18,10 @@ export function TaskShellPanel({ taskId, task, shellId }: TaskShellPanelProps) {
1718
const stateKey = shellId ? `${taskId}-${shellId}` : taskId;
1819
const tabId = shellId || "shell";
1920

21+
const worktreePath = useWorktreeStore((state) =>
22+
state.getWorktreePathForTask(taskId),
23+
);
24+
2025
const processName = useTerminalStore(
2126
(state) => state.terminalStates[stateKey]?.processName,
2227
);
@@ -35,9 +40,11 @@ export function TaskShellPanel({ taskId, task, shellId }: TaskShellPanelProps) {
3540
}
3641
}, [processName, taskId, tabId, updateTabLabel]);
3742

43+
const effectiveCwd = worktreePath || taskData.repoPath || undefined;
44+
3845
return (
3946
<Box height="100%">
40-
<ShellTerminal cwd={taskData.repoPath || undefined} stateKey={stateKey} />
47+
<ShellTerminal cwd={effectiveCwd} stateKey={stateKey} />
4148
</Box>
4249
);
4350
}

0 commit comments

Comments
 (0)