diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx index efe053473a8..88630271e33 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx @@ -46,8 +46,10 @@ export function DashboardSidebarWorkspaceItem({ handleDeleted, handleOpenInFinder, handleRemoveFromSidebar, + handleToggleUnread, isActive, isDeleteDialogOpen, + isUnread, isRenaming, moveWorkspaceToSection, renameValue, @@ -110,6 +112,7 @@ export function DashboardSidebarWorkspaceItem({ setIsDeleteDialogOpen(true)} + onToggleUnread={handleToggleUnread} > {content} @@ -176,6 +180,7 @@ export function DashboardSidebarWorkspaceItem({ setIsDeleteDialogOpen(true)} + onToggleUnread={handleToggleUnread} > {expandedContent} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx index c4b799efa0c..e1b4dcad459 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx @@ -20,6 +20,8 @@ import { LuArrowRightLeft, LuArrowUp, LuCopy, + LuEye, + LuEyeOff, LuFolderOpen, LuFolderPlus, LuGitBranch, @@ -34,6 +36,7 @@ interface DashboardSidebarWorkspaceContextMenuProps { projectId: string; isInSection?: boolean; isLocalWorkspace: boolean; + isUnread: boolean; onHoverCardOpen?: () => void; onCreateSection: () => void; onMoveToSection: (sectionId: string | null) => void; @@ -43,6 +46,7 @@ interface DashboardSidebarWorkspaceContextMenuProps { onRemoveFromSidebar: () => void; onRename: () => void; onDelete: () => void; + onToggleUnread: () => void; children: React.ReactNode; } @@ -50,6 +54,7 @@ export function DashboardSidebarWorkspaceContextMenu({ projectId, isInSection, isLocalWorkspace, + isUnread, onHoverCardOpen, hoverCardContent, onCreateSection, @@ -60,6 +65,7 @@ export function DashboardSidebarWorkspaceContextMenu({ onRemoveFromSidebar, onRename, onDelete, + onToggleUnread, children, }: DashboardSidebarWorkspaceContextMenuProps) { const collections = useCollections(); @@ -105,6 +111,20 @@ export function DashboardSidebarWorkspaceContextMenu({ Copy Branch Name + + {isUnread ? ( + <> + + Mark as Read + + ) : ( + <> + + Mark as Unread + + )} + + New group from workspace diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts index 459287e130a..fb1d61cb578 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts @@ -9,7 +9,10 @@ import { useNavigateAwayFromWorkspace } from "renderer/routes/_authenticated/_da import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useOptimisticCollectionActions } from "renderer/routes/_authenticated/hooks/useOptimisticCollectionActions"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; -import { useV2NotificationStore } from "renderer/stores/v2-notifications"; +import { + useV2NotificationStore, + useV2WorkspaceIsUnread, +} from "renderer/stores/v2-notifications"; interface UseDashboardSidebarWorkspaceItemActionsOptions { workspaceId: string; @@ -34,6 +37,8 @@ export function useDashboardSidebarWorkspaceItemActions({ const clearWorkspaceAttention = useV2NotificationStore( (s) => s.clearWorkspaceAttention, ); + const setManualUnread = useV2NotificationStore((s) => s.setManualUnread); + const isUnread = useV2WorkspaceIsUnread(workspaceId); const { createSection, moveWorkspaceToSection, removeWorkspaceFromSidebar } = useDashboardSidebarState(); @@ -128,6 +133,14 @@ export function useDashboardSidebarWorkspaceItemActions({ } }; + const handleToggleUnread = () => { + if (isUnread) { + clearWorkspaceAttention(workspaceId); + } else { + setManualUnread(workspaceId); + } + }; + const handleCopyBranchName = async () => { if (!branch) { toast.error("Branch name is not available"); @@ -152,9 +165,11 @@ export function useDashboardSidebarWorkspaceItemActions({ handleDeleted, handleOpenInFinder, handleRemoveFromSidebar, + handleToggleUnread, isActive, isDeleteDialogOpen, isRenaming, + isUnread, moveWorkspaceToSection, renameValue, setIsDeleteDialogOpen, diff --git a/apps/desktop/src/renderer/stores/v2-notifications/index.ts b/apps/desktop/src/renderer/stores/v2-notifications/index.ts index bc268226195..5917869b71c 100644 --- a/apps/desktop/src/renderer/stores/v2-notifications/index.ts +++ b/apps/desktop/src/renderer/stores/v2-notifications/index.ts @@ -1,5 +1,6 @@ export { getV2ChatNotificationSource, + getV2ManualNotificationSource, getV2NotificationSourceKey, getV2NotificationSourcesForPane, getV2NotificationSourcesForTab, @@ -9,6 +10,7 @@ export { selectV2SourcesNotificationStatus, selectV2TabNotificationStatus, selectV2TerminalNotificationStatus, + selectV2WorkspaceIsUnread, selectV2WorkspaceNotificationStatus, useV2ChatNotificationStatus, useV2NotificationStore, @@ -16,6 +18,7 @@ export { useV2SourcesNotificationStatus, useV2TabNotificationStatus, useV2TerminalNotificationStatus, + useV2WorkspaceIsUnread, useV2WorkspaceNotificationStatus, type V2NotificationPaneLike, type V2NotificationSource, diff --git a/apps/desktop/src/renderer/stores/v2-notifications/store.ts b/apps/desktop/src/renderer/stores/v2-notifications/store.ts index 288e3e80d26..26617b97e97 100644 --- a/apps/desktop/src/renderer/stores/v2-notifications/store.ts +++ b/apps/desktop/src/renderer/stores/v2-notifications/store.ts @@ -10,7 +10,8 @@ export type V2NotificationTabLike = Pick, "panes">; export type V2NotificationSource = | { type: "terminal"; id: string } - | { type: "chat"; id: string }; + | { type: "chat"; id: string } + | { type: "manual"; id: string }; export type V2NotificationSourceType = V2NotificationSource["type"]; export type V2NotificationSourceKey = `${V2NotificationSourceType}:${string}`; @@ -46,6 +47,7 @@ export interface V2NotificationState { status: ActivePaneStatus, occurredAt?: number, ) => void; + setManualUnread: (workspaceId: string) => void; clearSourceStatus: ( source: V2NotificationSourceInput, workspaceId?: string, @@ -99,6 +101,15 @@ export const useV2NotificationStore = create()((set) => ({ occurredAt, ); }, + setManualUnread: (workspaceId) => { + useV2NotificationStore + .getState() + .setSourceStatus( + getV2ManualNotificationSource(workspaceId), + workspaceId, + "review", + ); + }, clearSourceStatus: (source, workspaceId) => { const sourceKey = getV2NotificationSourceKey(source); set((state) => { @@ -194,6 +205,12 @@ export function getV2ChatNotificationSource( return { type: "chat", id: chatId }; } +export function getV2ManualNotificationSource( + workspaceId: string, +): V2NotificationSource { + return { type: "manual", id: workspaceId }; +} + export function getV2NotificationSourcesForPane( pane: V2NotificationPaneLike | null | undefined, ): V2NotificationSource[] { @@ -285,6 +302,21 @@ export function useV2WorkspaceNotificationStatus(workspaceId: string) { ); } +export function selectV2WorkspaceIsUnread(workspaceId: string) { + return (state: V2NotificationState) => { + for (const entry of Object.values(state.sources)) { + if (entry.workspaceId === workspaceId && entry.status === "review") { + return true; + } + } + return false; + }; +} + +export function useV2WorkspaceIsUnread(workspaceId: string) { + return useV2NotificationStore(selectV2WorkspaceIsUnread(workspaceId)); +} + export function useV2TabNotificationStatus( workspaceId: string, tab: V2NotificationTabLike | null | undefined,