>(new Set())
- // Filter to only show visible notifications for the current workflow
+ // Filter to only show:
+ // 1. Visible notifications for the current workflow
+ // 2. That are either unread OR marked as persistent
+ // 3. And have not been marked for removal
const visibleNotifications = notifications.filter(
- (n) => n.isVisible && n.workflowId === activeWorkflowId
+ (n) =>
+ n.isVisible &&
+ n.workflowId === activeWorkflowId &&
+ (!n.read || n.options?.isPersistent) &&
+ !removedIds.has(n.id)
)
// Handle auto-dismissal of non-persistent notifications
@@ -117,9 +160,14 @@ export function NotificationList() {
setFadingNotifications((prev) => new Set([...prev, notification.id]))
}, NOTIFICATION_TIMEOUT)
- // Hide notification after fade completes
+ // Hide notification after fade completes and mark for removal from DOM
const hideTimer = setTimeout(() => {
hideNotification(notification.id)
+ markAsRead(notification.id)
+
+ // Mark this notification ID as removed to exclude it from rendering
+ setRemovedIds((prev) => new Set([...prev, notification.id]))
+
setFadingNotifications((prev) => {
const next = new Set(prev)
next.delete(notification.id)
@@ -132,28 +180,45 @@ export function NotificationList() {
// Cleanup timers on unmount or when notifications change
return () => timers.forEach(clearTimeout)
- }, [visibleNotifications, hideNotification])
+ }, [visibleNotifications, hideNotification, markAsRead])
// Early return if no notifications to show
if (visibleNotifications.length === 0) return null
return (
-
- {visibleNotifications.map((notification) => (
-
- ))}
-
+ <>
+
+
+ {visibleNotifications.map((notification) => (
+ {
+ hideNotification(id)
+ markAsRead(id)
+ // Start the fade out animation
+ setFadingNotifications((prev) => new Set([...prev, id]))
+ // Remove from DOM after animation completes
+ setTimeout(() => {
+ setRemovedIds((prev) => new Set([...prev, id]))
+ setFadingNotifications((prev) => {
+ const next = new Set(prev)
+ next.delete(id)
+ return next
+ })
+ }, FADE_DURATION)
+ }}
+ />
+ ))}
+
+ >
)
}
@@ -168,13 +233,14 @@ interface NotificationAlertProps {
function NotificationAlert({ notification, isFading, onHide }: NotificationAlertProps) {
const { id, type, message, options, workflowId } = notification
- const Icon = NotificationIcon[type]
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const { setDeploymentStatus } = useWorkflowStore()
const { isDeployed } = useWorkflowStore((state) => ({
isDeployed: state.isDeployed,
}))
+ const Icon = NotificationIcon[type]
+
const handleDeleteApi = async () => {
if (!workflowId) return
@@ -202,8 +268,10 @@ function NotificationAlert({ notification, isFading, onHide }: NotificationAlert
<>
diff --git a/sim/app/w/[id]/workflow.tsx b/sim/app/w/[id]/workflow.tsx
index f9180af53b..64dc04f2de 100644
--- a/sim/app/w/[id]/workflow.tsx
+++ b/sim/app/w/[id]/workflow.tsx
@@ -12,6 +12,7 @@ import ReactFlow, {
} from 'reactflow'
import 'reactflow/dist/style.css'
import { createLogger } from '@/lib/logs/console-logger'
+import { useNotificationStore } from '@/stores/notifications/store'
import { useGeneralStore } from '@/stores/settings/general/store'
import { initializeSyncManagers, isSyncInitialized } from '@/stores/sync-registry'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -51,6 +52,7 @@ function WorkflowContent() {
const { blocks, edges, loops, addBlock, updateBlockPosition, addEdge, removeEdge } =
useWorkflowStore()
const { setValue: setSubBlockValue } = useSubBlockStore()
+ const { markAllAsRead } = useNotificationStore()
// Initialize workflow
useEffect(() => {
@@ -157,16 +159,26 @@ function WorkflowContent() {
if (!isActivelyLoadingFromDB()) {
clearInterval(checkInterval)
setActiveWorkflow(currentId)
+ markAllAsRead(currentId)
}
}, 100)
return
}
setActiveWorkflow(currentId)
+ markAllAsRead(currentId)
}
validateAndNavigate()
- }, [params.id, workflows, setActiveWorkflow, createWorkflow, router, isInitialized])
+ }, [
+ params.id,
+ workflows,
+ setActiveWorkflow,
+ createWorkflow,
+ router,
+ isInitialized,
+ markAllAsRead,
+ ])
// Transform blocks and loops into ReactFlow nodes
const nodes = useMemo(() => {
diff --git a/sim/stores/notifications/store.ts b/sim/stores/notifications/store.ts
index 6f12a3161b..578ef6a6a7 100644
--- a/sim/stores/notifications/store.ts
+++ b/sim/stores/notifications/store.ts
@@ -34,6 +34,7 @@ export const useNotificationStore = create()(
message,
timestamp: Date.now(),
isVisible: true,
+ read: false,
workflowId,
options,
}
@@ -52,7 +53,7 @@ export const useNotificationStore = create()(
hideNotification: (id) =>
set((state) => {
const newNotifications = state.notifications.map((n) =>
- n.id === id ? { ...n, isVisible: false } : n
+ n.id === id ? { ...n, isVisible: false, read: true } : n
)
persistNotifications(newNotifications)
return { notifications: newNotifications }
@@ -67,6 +68,24 @@ export const useNotificationStore = create()(
return { notifications: newNotifications }
}),
+ markAsRead: (id) =>
+ set((state) => {
+ const newNotifications = state.notifications.map((n) =>
+ n.id === id ? { ...n, read: true } : n
+ )
+ persistNotifications(newNotifications)
+ return { notifications: newNotifications }
+ }),
+
+ markAllAsRead: (workflowId) =>
+ set((state) => {
+ const newNotifications = state.notifications.map((n) =>
+ n.workflowId === workflowId ? { ...n, read: true } : n
+ )
+ persistNotifications(newNotifications)
+ return { notifications: newNotifications }
+ }),
+
removeNotification: (id) =>
set((state) => {
const newNotifications = state.notifications.filter((n) => n.id !== id)
diff --git a/sim/stores/notifications/types.ts b/sim/stores/notifications/types.ts
index 64aa7f8abb..d94a17fd80 100644
--- a/sim/stores/notifications/types.ts
+++ b/sim/stores/notifications/types.ts
@@ -7,6 +7,7 @@ export interface Notification {
timestamp: number
isVisible: boolean
workflowId: string | null
+ read: boolean
options?: NotificationOptions
}
@@ -31,6 +32,8 @@ export interface NotificationStore {
) => void
hideNotification: (id: string) => void
showNotification: (id: string) => void
+ markAsRead: (id: string) => void
+ markAllAsRead: (workflowId: string) => void
removeNotification: (id: string) => void
clearNotifications: () => void
getWorkflowNotifications: (workflowId: string) => Notification[]