From b252cd98d2bbc7dcec6f8eca08c399727543ff71 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 8 May 2026 19:25:20 +0700 Subject: [PATCH] sidebar history: status indicator on session rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar Session rows now mirror the workspace tab descriptor: same provider icon, same loader spinner, same status dot, same attention badge. Reuses WorkspaceTabPresentationResolver + WorkspaceTabIcon directly — no parallel implementation. Status reactivity stays inside the tab descriptor's own per-agent selector, so selectSidebarSessionSlice is unchanged and the parent ids hook keeps its reference stability. Navigation test mocks tab presentation so it doesn't pull the full workspace stack just to assert click behavior; production uses the real shared component. --- .../sidebar-session-row-navigation.test.tsx | 34 ++++++++++++++++++- packages/app/src/sidebar-sessions/view.tsx | 32 ++++++++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/packages/app/src/sidebar-sessions/sidebar-session-row-navigation.test.tsx b/packages/app/src/sidebar-sessions/sidebar-session-row-navigation.test.tsx index 5c16b7cc62..8c97458c75 100644 --- a/packages/app/src/sidebar-sessions/sidebar-session-row-navigation.test.tsx +++ b/packages/app/src/sidebar-sessions/sidebar-session-row-navigation.test.tsx @@ -29,13 +29,21 @@ const { navigateToPreparedWorkspaceTabMock: vi.fn(), prepareWorkspaceTabMock: vi.fn(), testTheme: { - borderRadius: { lg: 8 }, + borderRadius: { full: 999, lg: 8 }, colors: { foreground: "#111111", foregroundMuted: "#666666", + palette: { + amber: { 500: "#f59e0b", 700: "#b45309" }, + blue: { 500: "#3b82f6" }, + green: { 500: "#22c55e" }, + red: { 500: "#ef4444" }, + }, + surface0: "#ffffff", surface2: "#eeeeee", surfaceSidebarHover: "#f5f5f5", }, + colorScheme: "light", fontSize: { sm: 14, xs: 12 }, fontWeight: { medium: "500" }, iconSize: { md: 20, sm: 16 }, @@ -61,10 +69,34 @@ vi.mock("@/components/provider-icons", async () => { }; }); +vi.mock("@/screens/workspace/workspace-tab-presentation", async () => { + const ReactModule = await import("react"); + return { + WorkspaceTabPresentationResolver: ({ children }: { children: (presentation: {}) => unknown }) => + children({}), + WorkspaceTabIcon: () => + ReactModule.createElement("span", { "data-testid": "workspace-tab-icon" }), + }; +}); + +vi.mock("react-native-reanimated", () => ({ + default: { + View: "div", + }, + Easing: { + linear: vi.fn(), + }, + makeMutable: (value: unknown) => ({ value }), + useAnimatedStyle: (factory: () => unknown) => factory(), + withRepeat: (value: unknown) => value, + withTiming: (value: unknown) => value, +})); + vi.mock("react-native-unistyles", () => ({ StyleSheet: { create: (styles: unknown) => (typeof styles === "function" ? styles(testTheme) : styles), }, + withUnistyles: (Component: unknown) => Component, useUnistyles: () => ({ theme: testTheme, rt: {}, diff --git a/packages/app/src/sidebar-sessions/view.tsx b/packages/app/src/sidebar-sessions/view.tsx index 784be8a039..629f04bf63 100644 --- a/packages/app/src/sidebar-sessions/view.tsx +++ b/packages/app/src/sidebar-sessions/view.tsx @@ -10,12 +10,16 @@ import { import { useShallow } from "zustand/shallow"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { SidebarAgentListSkeleton } from "@/components/sidebar-agent-list-skeleton"; -import { getProviderIcon } from "@/components/provider-icons"; import { type AggregatedAgentIdEntry, useAggregatedAgentIds, useAggregatedAgentsInitialLoad, } from "@/hooks/use-aggregated-agents"; +import { + WorkspaceTabIcon, + WorkspaceTabPresentationResolver, +} from "@/screens/workspace/workspace-tab-presentation"; +import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types"; import type { SidebarProjectEntry } from "@/hooks/use-sidebar-workspaces-list"; import { useSessionStore } from "@/stores/session-store"; import { navigateToPreparedWorkspaceTab, prepareWorkspaceTab } from "@/utils/workspace-navigation"; @@ -149,13 +153,20 @@ const SidebarSessionRow = memo(function SidebarSessionRow({ ); const titleStyle = useMemo(() => [styles.title, isHovered && styles.titleHovered], [isHovered]); + const tabDescriptor = useMemo( + () => ({ + key: `agent_${id}`, + tabId: `agent_${id}`, + kind: "agent", + target: { kind: "agent", agentId: id }, + }), + [id], + ); if (!agent || agent.archivedAt) { return null; } - const ProviderIcon = getProviderIcon(agent.provider); - return ( - + + {(presentation) => ( + + )} + {agent.title || "New session"}