diff --git a/apps/array/src/renderer/features/sidebar/components/HistoryView.tsx b/apps/array/src/renderer/features/sidebar/components/HistoryView.tsx
new file mode 100644
index 000000000..83106aa80
--- /dev/null
+++ b/apps/array/src/renderer/features/sidebar/components/HistoryView.tsx
@@ -0,0 +1,136 @@
+import { useTaskExecutionStore } from "@features/task-detail/stores/taskExecutionStore";
+import { Button, Flex } from "@radix-ui/themes";
+import { useWorkspaceStore } from "@/renderer/features/workspace/stores/workspaceStore";
+import type { HistoryData, HistoryTaskData } from "../hooks/useSidebarData";
+import { useSidebarStore } from "../stores/sidebarStore";
+import { TaskItem } from "./items/TaskItem";
+
+interface HistoryViewProps {
+ historyData: HistoryData;
+ activeTaskId: string | null;
+ onTaskClick: (taskId: string) => void;
+ onTaskContextMenu: (taskId: string, e: React.MouseEvent) => void;
+ onTaskDelete: (taskId: string) => void;
+ onTaskTogglePin: (taskId: string) => void;
+}
+
+function HistorySectionLabel({ label }: { label: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
+interface HistoryTaskItemProps {
+ task: HistoryTaskData;
+ isActive: boolean;
+ onClick: () => void;
+ onContextMenu: (e: React.MouseEvent) => void;
+ onDelete: () => void;
+ onTogglePin: () => void;
+}
+
+function HistoryTaskItem({
+ task,
+ isActive,
+ onClick,
+ onContextMenu,
+ onDelete,
+ onTogglePin,
+}: HistoryTaskItemProps) {
+ const workspaces = useWorkspaceStore.use.workspaces();
+ const taskStates = useTaskExecutionStore((state) => state.taskStates);
+
+ const workspace = workspaces[task.id];
+ const taskState = taskStates[task.id];
+
+ return (
+
+ );
+}
+
+export function HistoryView({
+ historyData,
+ activeTaskId,
+ onTaskClick,
+ onTaskContextMenu,
+ onTaskDelete,
+ onTaskTogglePin,
+}: HistoryViewProps) {
+ const loadMoreHistory = useSidebarStore((state) => state.loadMoreHistory);
+ const { activeTasks, recentTasks, hasMore } = historyData;
+
+ const hasActiveTasks = activeTasks.length > 0;
+ const hasRecentTasks = recentTasks.length > 0;
+
+ return (
+
+ {hasActiveTasks && (
+ <>
+
+ {activeTasks.map((task) => (
+ onTaskClick(task.id)}
+ onContextMenu={(e) => onTaskContextMenu(task.id, e)}
+ onDelete={() => onTaskDelete(task.id)}
+ onTogglePin={() => onTaskTogglePin(task.id)}
+ />
+ ))}
+ {hasRecentTasks && (
+
+ )}
+ >
+ )}
+
+ {hasRecentTasks && (
+ <>
+
+ {recentTasks.map((task) => (
+ onTaskClick(task.id)}
+ onContextMenu={(e) => onTaskContextMenu(task.id, e)}
+ onDelete={() => onTaskDelete(task.id)}
+ onTogglePin={() => onTaskTogglePin(task.id)}
+ />
+ ))}
+ >
+ )}
+
+ {hasMore && (
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/array/src/renderer/features/sidebar/components/SidebarFooter.tsx b/apps/array/src/renderer/features/sidebar/components/SidebarFooter.tsx
index 9e1f3536c..907df20f7 100644
--- a/apps/array/src/renderer/features/sidebar/components/SidebarFooter.tsx
+++ b/apps/array/src/renderer/features/sidebar/components/SidebarFooter.tsx
@@ -5,10 +5,12 @@ import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersSto
import { trpcVanilla } from "@renderer/trpc";
import { useNavigationStore } from "@stores/navigationStore";
import { useCallback } from "react";
+import { useSidebarStore } from "../stores/sidebarStore";
export function SidebarFooter() {
const addFolder = useRegisteredFoldersStore((state) => state.addFolder);
- const { toggleSettings } = useNavigationStore();
+ const { toggleSettings, navigateToTaskInput } = useNavigationStore();
+ const viewMode = useSidebarStore((state) => state.viewMode);
const handleAddRepository = useCallback(async () => {
const selectedPath = await trpcVanilla.os.selectDirectory.query();
@@ -17,6 +19,12 @@ export function SidebarFooter() {
}
}, [addFolder]);
+ const handleNewTask = useCallback(() => {
+ navigateToTaskInput();
+ }, [navigateToTaskInput]);
+
+ const isHistoryView = viewMode === "history";
+
return (
-
+ {isHistoryView ? (
+
+ ) : (
+
+ )}
state.toggleSection);
const folderOrder = useSidebarStore((state) => state.folderOrder);
const reorderFolders = useSidebarStore((state) => state.reorderFolders);
+ const viewMode = useSidebarStore((state) => state.viewMode);
const workspaces = useWorkspaceStore.use.workspaces();
const taskStates = useTaskExecutionStore((state) => state.taskStates);
const markAsViewed = useTaskViewedStore((state) => state.markAsViewed);
@@ -196,87 +199,104 @@ function SidebarMenuComponent() {
onClick={handleHomeClick}
/>
+
+
+
+
-
- {sidebarData.folders.map((folder, index) => {
- const isExpanded = !collapsedSections.has(folder.id);
- return (
-
- {index > 0 && (
-
- )}
-
- ) : (
-
- )
- }
- isExpanded={isExpanded}
- onToggle={() => toggleSection(folder.id)}
- onSettingsClick={() => handleFolderSettings(folder.id)}
- onContextMenu={(e) =>
- handleFolderContextMenu(folder.id, e)
- }
- >
-
handleFolderNewTask(folder.id)}
- />
- {folder.tasks.map((task) => (
- handleTaskClick(task.id)}
- onContextMenu={(e) =>
- handleTaskContextMenu(task.id, e)
- }
- onDelete={() => handleTaskDelete(task.id)}
- onTogglePin={() => handleTaskTogglePin(task.id)}
+ {viewMode === "history" ? (
+
+ ) : (
+
+ {sidebarData.folders.map((folder, index) => {
+ const isExpanded = !collapsedSections.has(folder.id);
+ return (
+
+ {index > 0 && (
+
+ )}
+
+ ) : (
+
+ )
+ }
+ isExpanded={isExpanded}
+ onToggle={() => toggleSection(folder.id)}
+ onSettingsClick={() => handleFolderSettings(folder.id)}
+ onContextMenu={(e) =>
+ handleFolderContextMenu(folder.id, e)
+ }
+ >
+
handleFolderNewTask(folder.id)}
/>
- ))}
-
-
- );
- })}
-
- {(source) =>
- source?.type === "folder" ? (
-
-
- {source.data?.label}
+ {folder.tasks.map((task) => (
+ handleTaskClick(task.id)}
+ onContextMenu={(e) =>
+ handleTaskContextMenu(task.id, e)
+ }
+ onDelete={() => handleTaskDelete(task.id)}
+ onTogglePin={() => handleTaskTogglePin(task.id)}
+ />
+ ))}
+
- ) : null
- }
-
-
+ );
+ })}
+
+ {(source) =>
+ source?.type === "folder" ? (
+
+
+
+ {source.data?.label}
+
+
+ ) : null
+ }
+
+
+ )}
diff --git a/apps/array/src/renderer/features/sidebar/components/ViewModeSelector.tsx b/apps/array/src/renderer/features/sidebar/components/ViewModeSelector.tsx
new file mode 100644
index 000000000..0530a3a7a
--- /dev/null
+++ b/apps/array/src/renderer/features/sidebar/components/ViewModeSelector.tsx
@@ -0,0 +1,62 @@
+import { ClockCounterClockwise, Folder } from "@phosphor-icons/react";
+import { Select, Text } from "@radix-ui/themes";
+import { type SidebarViewMode, useSidebarStore } from "../stores/sidebarStore";
+
+const VIEW_OPTIONS = [
+ { value: "folders" as const, label: "Repositories", Icon: Folder },
+ { value: "history" as const, label: "History", Icon: ClockCounterClockwise },
+];
+
+export function ViewModeSelector() {
+ const viewMode = useSidebarStore((state) => state.viewMode);
+ const setViewMode = useSidebarStore((state) => state.setViewMode);
+ const resetHistoryVisibleCount = useSidebarStore(
+ (state) => state.resetHistoryVisibleCount,
+ );
+
+ const handleChange = (value: SidebarViewMode) => {
+ if (value === "history") {
+ resetHistoryVisibleCount();
+ }
+ setViewMode(value);
+ };
+
+ const currentOption = VIEW_OPTIONS.find((o) => o.value === viewMode);
+ const CurrentIcon = currentOption?.Icon ?? Folder;
+
+ return (
+
+
+
+
+
+ {currentOption?.label}
+
+
+
+
+ {VIEW_OPTIONS.map((option) => {
+ const OptionIcon = option.Icon;
+ return (
+
+
+
+ {option.label}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/array/src/renderer/features/sidebar/hooks/useSidebarData.ts b/apps/array/src/renderer/features/sidebar/hooks/useSidebarData.ts
index 7144b3f38..28d788496 100644
--- a/apps/array/src/renderer/features/sidebar/hooks/useSidebarData.ts
+++ b/apps/array/src/renderer/features/sidebar/hooks/useSidebarData.ts
@@ -46,6 +46,18 @@ export interface TaskData {
isPinned?: boolean;
}
+export interface HistoryTaskData extends TaskData {
+ createdAt: number;
+ folderName?: string;
+}
+
+export interface HistoryData {
+ activeTasks: HistoryTaskData[];
+ recentTasks: HistoryTaskData[];
+ totalCount: number;
+ hasMore: boolean;
+}
+
export interface SidebarData {
userName: string;
isHomeActive: boolean;
@@ -56,6 +68,7 @@ export interface SidebarData {
isLoading: boolean;
folders: FolderData[];
activeTaskId: string | null;
+ historyData: HistoryData;
}
interface ViewState {
@@ -170,6 +183,76 @@ function getActiveRepository(activeFilters: ActiveFilters): string | null {
return repositoryFilters.length === 1 ? repositoryFilters[0].value : null;
}
+function buildHistoryData(
+ allTasks: Task[],
+ workspaces: Record,
+ folders: RegisteredFolder[],
+ sessions: Record,
+ lastViewedAt: Record,
+ localActivityAt: Record,
+ pinnedTaskIds: Set,
+ activeTaskId: string | null,
+ visibleCount: number,
+): HistoryData {
+ const getSessionForTask = (taskId: string): AgentSession | undefined => {
+ return Object.values(sessions).find((s) => s.taskId === taskId);
+ };
+
+ // Transform all tasks to HistoryTaskData
+ const historyTasks: HistoryTaskData[] = allTasks.map((task) => {
+ const session = getSessionForTask(task.id);
+ const workspace = workspaces[task.id];
+ const folder = workspace
+ ? folders.find((f) => f.id === workspace.folderId)
+ : undefined;
+
+ const apiUpdatedAt = new Date(task.updated_at).getTime();
+ const localActivity = localActivityAt[task.id];
+ const lastActivityAt = localActivity
+ ? Math.max(apiUpdatedAt, localActivity)
+ : apiUpdatedAt;
+
+ const taskLastViewedAt = lastViewedAt[task.id];
+ const isCurrentlyViewing = activeTaskId === task.id;
+ const isUnread =
+ !isCurrentlyViewing &&
+ taskLastViewedAt !== undefined &&
+ lastActivityAt > taskLastViewedAt;
+
+ return {
+ id: task.id,
+ title: task.title,
+ lastActivityAt,
+ createdAt: new Date(task.created_at).getTime(),
+ isGenerating: session?.isPromptPending ?? false,
+ isUnread,
+ isPinned: pinnedTaskIds.has(task.id),
+ folderName: folder?.name,
+ };
+ });
+
+ // Partition into active (unread) and inactive tasks
+ const activeTasks = historyTasks
+ .filter((t) => t.isUnread)
+ .sort((a, b) => (b.lastActivityAt ?? 0) - (a.lastActivityAt ?? 0));
+
+ const inactiveTasks = historyTasks
+ .filter((t) => !t.isUnread)
+ .sort((a, b) => b.createdAt - a.createdAt);
+
+ // Apply pagination to inactive tasks only (active always shown)
+ const totalCount = allTasks.length;
+ const recentTasks = inactiveTasks.slice(0, visibleCount);
+ const hasMore = inactiveTasks.length > visibleCount;
+
+ return {
+ activeTasks,
+ recentTasks,
+ totalCount,
+ hasMore,
+ };
+}
+
export function useSidebarData({
activeView,
activeFilters,
@@ -183,6 +266,9 @@ export function useSidebarData({
const localActivityAt = useTaskViewedStore((state) => state.lastActivityAt);
const folderOrder = useSidebarStore((state) => state.folderOrder);
const syncFolderOrder = useSidebarStore((state) => state.syncFolderOrder);
+ const historyVisibleCount = useSidebarStore(
+ (state) => state.historyVisibleCount,
+ );
const pinnedTaskIds = usePinnedTasksStore((state) => state.pinnedTaskIds);
const userName = currentUser?.first_name || currentUser?.email || "Account";
@@ -269,6 +355,18 @@ export function useSidebarData({
};
});
+ const historyData = buildHistoryData(
+ allTasks,
+ workspaces,
+ folders,
+ sessions,
+ lastViewedAt,
+ localActivityAt,
+ pinnedTaskIds,
+ activeTaskId,
+ historyVisibleCount,
+ );
+
return {
userName,
isHomeActive,
@@ -279,5 +377,6 @@ export function useSidebarData({
isLoading,
folders: folderData,
activeTaskId,
+ historyData,
};
}
diff --git a/apps/array/src/renderer/features/sidebar/stores/sidebarStore.ts b/apps/array/src/renderer/features/sidebar/stores/sidebarStore.ts
index a6d851789..aa46e7165 100644
--- a/apps/array/src/renderer/features/sidebar/stores/sidebarStore.ts
+++ b/apps/array/src/renderer/features/sidebar/stores/sidebarStore.ts
@@ -1,6 +1,8 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
+export type SidebarViewMode = "folders" | "history";
+
interface SidebarStoreState {
open: boolean;
hasUserSetOpen: boolean;
@@ -8,6 +10,8 @@ interface SidebarStoreState {
isResizing: boolean;
collapsedSections: Set;
folderOrder: string[];
+ viewMode: SidebarViewMode;
+ historyVisibleCount: number;
}
interface SidebarStoreActions {
@@ -20,6 +24,9 @@ interface SidebarStoreActions {
reorderFolders: (fromIndex: number, toIndex: number) => void;
setFolderOrder: (order: string[]) => void;
syncFolderOrder: (folderIds: string[]) => void;
+ setViewMode: (mode: SidebarViewMode) => void;
+ loadMoreHistory: () => void;
+ resetHistoryVisibleCount: () => void;
}
type SidebarStore = SidebarStoreState & SidebarStoreActions;
@@ -33,6 +40,8 @@ export const useSidebarStore = create()(
isResizing: false,
collapsedSections: new Set(),
folderOrder: [],
+ viewMode: "history" as SidebarViewMode,
+ historyVisibleCount: 25,
setOpen: (open) => set({ open, hasUserSetOpen: true }),
setOpenAuto: (open) =>
set((state) => (state.hasUserSetOpen ? state : { open })),
@@ -74,6 +83,12 @@ export const useSidebarStore = create()(
}
return state;
}),
+ setViewMode: (mode) => set({ viewMode: mode }),
+ loadMoreHistory: () =>
+ set((state) => ({
+ historyVisibleCount: state.historyVisibleCount + 25,
+ })),
+ resetHistoryVisibleCount: () => set({ historyVisibleCount: 25 }),
}),
{
name: "sidebar-storage",
@@ -83,6 +98,8 @@ export const useSidebarStore = create()(
width: state.width,
collapsedSections: Array.from(state.collapsedSections),
folderOrder: state.folderOrder,
+ viewMode: state.viewMode,
+ historyVisibleCount: state.historyVisibleCount,
}),
merge: (persisted, current) => {
const persistedState = persisted as {
@@ -91,6 +108,8 @@ export const useSidebarStore = create()(
width?: number;
collapsedSections?: string[];
folderOrder?: string[];
+ viewMode?: SidebarViewMode;
+ historyVisibleCount?: number;
};
return {
...current,
@@ -100,6 +119,9 @@ export const useSidebarStore = create()(
width: persistedState.width ?? current.width,
collapsedSections: new Set(persistedState.collapsedSections ?? []),
folderOrder: persistedState.folderOrder ?? [],
+ viewMode: persistedState.viewMode ?? current.viewMode,
+ historyVisibleCount:
+ persistedState.historyVisibleCount ?? current.historyVisibleCount,
};
},
},