diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx
index 9c28d989a1..76ad557516 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -13,7 +13,6 @@ import {
getSettingsPrefixAtom,
getTabIndicatorAtom,
globalStore,
- isDev,
} from "@/store/global";
import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel";
import { getElemAsStr } from "@/util/focusutil";
@@ -29,7 +28,6 @@ import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { AppBackground } from "./app-bg";
import { CenteredDiv } from "./element/quickelems";
-import { NotificationBubbles } from "./notification/notificationbubbles";
import "./app.scss";
@@ -267,7 +265,6 @@ const AppInner = () => {
- {isDev() ? : null}
);
};
diff --git a/frontend/app/notification/notificationbubbles.scss b/frontend/app/notification/notificationbubbles.scss
deleted file mode 100644
index 345fa40e8e..0000000000
--- a/frontend/app/notification/notificationbubbles.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2024, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-.notification-bubbles {
- display: flex;
- width: 380px;
- flex-direction: column;
- align-items: flex-start;
- row-gap: 8px;
-}
diff --git a/frontend/app/notification/notificationbubbles.tsx b/frontend/app/notification/notificationbubbles.tsx
deleted file mode 100644
index d5f612efc5..0000000000
--- a/frontend/app/notification/notificationbubbles.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { atoms } from "@/store/global";
-import { FloatingPortal, useFloating, useInteractions } from "@floating-ui/react";
-import clsx from "clsx";
-import { useAtomValue } from "jotai";
-import { useEffect, useState } from "react";
-import "./notificationbubbles.scss";
-import { NotificationItem } from "./notificationitem";
-import { useNotification } from "./usenotification";
-
-const NotificationBubbles = () => {
- const {
- notifications,
- hoveredId,
- hideNotification,
- copyNotification,
- handleActionClick,
- formatTimestamp,
- setHoveredId,
- } = useNotification();
- const [isOpen, setIsOpen] = useState(notifications.length > 0);
- const notificationPopoverMode = useAtomValue(atoms.notificationPopoverMode);
-
- useEffect(() => {
- setIsOpen(notifications.length > 0);
- }, [notifications.length]);
-
- const { refs, strategy } = useFloating({
- open: isOpen,
- onOpenChange: setIsOpen,
- strategy: "fixed",
- });
-
- const { getFloatingProps } = useInteractions();
-
- const floatingStyles = {
- position: strategy,
- right: "58px",
- bottom: "10px",
- top: "auto",
- left: "auto",
- };
-
- if (!isOpen || notificationPopoverMode) {
- return null;
- }
-
- return (
-
- e.stopPropagation(),
- })}
- >
- {notifications.map((notif) => {
- if (notif.hidden) return null;
- return (
- setHoveredId(notif.id)}
- onMouseLeave={() => setHoveredId(null)}
- isBubble={true}
- />
- );
- })}
-
-
- );
-};
-
-export { NotificationBubbles };
diff --git a/frontend/app/notification/notificationitem.scss b/frontend/app/notification/notificationitem.scss
deleted file mode 100644
index 2dcf2a3a21..0000000000
--- a/frontend/app/notification/notificationitem.scss
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2024, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-.notification {
- &.hovered {
- background: #292929;
- }
-
- .notification-title {
- font-size: 13px;
- font-style: normal;
- font-weight: 500;
- line-height: 18px;
- margin-bottom: 3px;
-
- &.green {
- color: var(--success-color);
- }
-
- &.red {
- color: var(--error-color);
- }
-
- &.yellow {
- color: var(--warning-color);
- }
- }
-
- .notification-message {
- font-size: 13px;
- font-style: normal;
- font-weight: 400;
- line-height: 18px;
- opacity: 0.7;
- }
-
- .notification-actions {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-top: 10px;
-
- i {
- margin-left: 3px;
- font-size: 11px;
- }
- }
-
- .close-btn {
- position: absolute;
- top: 5px;
- right: 5px;
- }
-
- .lock-btn {
- position: absolute;
- top: 5px;
- right: 5px;
- padding: 10px 8px;
- font-size: 11px;
- color: rgb(from var(--main-text-color) r g b / 0.5);
- }
-
- .notification {
- width: 100%;
- color: var(--main-text-color);
- padding: 12px 10px;
- display: flex;
- flex-direction: column;
- position: relative;
- }
-
- .notification-bubble {
- position: relative;
- display: flex;
- width: 380px;
- padding: 16px 24px 16px 16px;
- align-items: flex-start;
- gap: 12px;
- border-radius: 8px;
- border: 0.5px solid rgba(255, 255, 255, 0.12);
- background: #232323;
- box-shadow: 0px 8px 32px 0px rgba(0, 0, 0, 0.25);
- }
-
- .notification-inner {
- display: flex;
- align-items: flex-start;
- column-gap: 6px;
-
- .notification-icon {
- margin-right: 5px;
- margin-top: 1px;
-
- i {
- font-size: 16px;
- }
-
- i.green {
- color: var(--success-color);
- }
-
- i.red {
- color: var(--error-color);
- }
-
- i.yellow {
- color: var(--warning-color);
- }
- }
-
- .notification-timestamp {
- font-size: 12px;
- font-weight: 400;
- line-height: 18px;
- opacity: 0.5;
- margin-bottom: 7px;
- }
- }
-}
diff --git a/frontend/app/notification/notificationitem.tsx b/frontend/app/notification/notificationitem.tsx
deleted file mode 100644
index 738b3f0c99..0000000000
--- a/frontend/app/notification/notificationitem.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Button } from "@/element/button";
-import { makeIconClass } from "@/util/util";
-import clsx from "clsx";
-
-import "./notificationitem.scss";
-interface NotificationItemProps {
- notification: NotificationType;
- onRemove: (id: string) => void;
- onCopy: (id: string) => void;
- onActionClick: (e: React.MouseEvent, action: NotificationActionType, id: string) => void;
- formatTimestamp: (timestamp: string) => string;
- isBubble: boolean;
- className?: string;
- onMouseEnter?: () => void;
- onMouseLeave?: () => void;
-}
-
-const NotificationItem = ({
- notification,
- onRemove,
- onCopy,
- onActionClick,
- formatTimestamp,
- isBubble,
- className,
- onMouseEnter,
- onMouseLeave,
-}: NotificationItemProps) => {
- const { id, title, message, icon, type, timestamp, persistent, actions } = notification;
- const color = type === "error" ? "red" : type === "warning" ? "yellow" : "green";
- const nIcon = icon ? icon : "bell";
-
- const renderCloseButton = () => {
- if (!isBubble && persistent) {
- return (
-
-
-
- );
- }
- return (
-
- );
- };
-
- return (
-
onCopy(id)}
- title="Click to Copy Notification Message"
- >
- {renderCloseButton()}
-
- {nIcon && (
-
-
-
- )}
-
- {title &&
{title}
}
- {timestamp && !isBubble && (
-
{formatTimestamp(timestamp)}
- )}
- {message &&
{message}
}
- {actions && actions.length > 0 && (
-
- {actions.map((action, index) => (
-
- ))}
-
- )}
-
-
-
- );
-};
-
-export { NotificationItem };
diff --git a/frontend/app/notification/notificationpopover.tsx b/frontend/app/notification/notificationpopover.tsx
deleted file mode 100644
index b62c8c4986..0000000000
--- a/frontend/app/notification/notificationpopover.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Button } from "@/element/button";
-import { Popover, PopoverButton, PopoverContent } from "@/element/popover";
-import { atoms } from "@/store/global";
-import { makeIconClass } from "@/util/util";
-import clsx from "clsx";
-import { useAtom } from "jotai";
-import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
-import { Fragment, useCallback } from "react";
-import { NotificationItem } from "./notificationitem";
-import { useUpdateNotifier } from "./updatenotifier";
-import { useNotification } from "./usenotification";
-
-const NotificationPopover = () => {
- useUpdateNotifier();
- const {
- notifications,
- removeNotification,
- removeAllNotifications,
- hideAllNotifications,
- copyNotification,
- handleActionClick,
- formatTimestamp,
- hoveredId,
- setHoveredId,
- } = useNotification();
- const [notificationPopoverMode, setNotificationPopoverMode] = useAtom(atoms.notificationPopoverMode);
-
- const handleTogglePopover = useCallback(() => {
- if (notificationPopoverMode) {
- hideAllNotifications();
- }
- setNotificationPopoverMode(!notificationPopoverMode);
- }, [notificationPopoverMode]);
-
- const hasErrors = notifications.some((n) => n.type === "error");
- const hasUpdate = notifications.some((n) => n.type === "update");
-
- const addOnClassNames = hasUpdate ? "solid green" : hasErrors ? "solid red" : "ghost grey";
-
- const getIcon = () => {
- if (hasUpdate) {
- return ;
- }
- return ;
- };
-
- return (
-
- i]:text-[17px] px-[6px] py-[4px]",
- addOnClassNames
- )}
- disabled={notifications.length === 0}
- onClick={handleTogglePopover}
- >
- {getIcon()}
-
- {notifications.length > 0 && (
-
-
- Notifications
-
-
-
- {notifications.map((notif, index) => (
-
- setHoveredId(notif.id)}
- onMouseLeave={() => setHoveredId(null)}
- />
- {index !== notifications.length - 1 && }
-
- ))}
-
-
- )}
-
- );
-};
-
-export { NotificationPopover };
diff --git a/frontend/app/notification/updatenotifier.tsx b/frontend/app/notification/updatenotifier.tsx
deleted file mode 100644
index 40392bb0e8..0000000000
--- a/frontend/app/notification/updatenotifier.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { atoms, isDev, pushNotification } from "@/store/global";
-import { useAtomValue } from "jotai";
-import { useEffect } from "react";
-
-export const useUpdateNotifier = () => {
- const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
-
- useEffect(() => {
- let notification: NotificationType | null = null;
-
- switch (appUpdateStatus) {
- case "ready":
- notification = {
- id: "update-notification",
- icon: "arrows-rotate",
- title: "Update Available",
- message: "A new update is available and ready to be installed.",
- timestamp: new Date().toLocaleString(),
- type: "update",
- actions: [
- {
- label: "Install Now",
- actionKey: "installUpdate",
- color: "green",
- disabled: false,
- },
- ],
- };
- break;
-
- case "downloading":
- notification = {
- id: "update-notification",
- icon: "arrows-rotate",
- title: "Downloading Update",
- message: "The update is currently being downloaded.",
- timestamp: new Date().toLocaleString(),
- type: "update",
- actions: [
- {
- label: "Downloading...",
- actionKey: "",
- color: "green",
- disabled: true,
- },
- ],
- };
- break;
-
- case "installing":
- notification = {
- id: "update-notification",
- icon: "arrows-rotate",
- title: "Installing Update",
- message: "The update is currently being installed.",
- timestamp: new Date().toLocaleString(),
- type: "update",
- actions: [
- {
- label: "Installing...",
- actionKey: "",
- color: "green",
- disabled: true,
- },
- ],
- };
- break;
-
- case "error":
- notification = {
- id: "update-notification",
- icon: "circle-exclamation",
- title: "Update Error",
- message: "An error occurred during the update process.",
- timestamp: new Date().toLocaleString(),
- type: "update",
- actions: [
- {
- label: "Retry Update",
- actionKey: "retryUpdate",
- color: "green",
- disabled: false,
- },
- ],
- };
- break;
- }
-
- if (!isDev()) return;
-
- if (notification) {
- pushNotification(notification);
- }
- }, [appUpdateStatus]);
-};
diff --git a/frontend/app/notification/usenotification.tsx b/frontend/app/notification/usenotification.tsx
deleted file mode 100644
index b98d2fd1b5..0000000000
--- a/frontend/app/notification/usenotification.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { atoms, getApi } from "@/store/global";
-import { useAtom, useAtomValue } from "jotai";
-import { useCallback, useEffect, useState } from "react";
-
-const notificationActions: { [key: string]: () => void } = {
- installUpdate: () => {
- getApi().installAppUpdate();
- },
- // Add other action functions here
-};
-
-export function useNotification() {
- const notificationPopoverMode = useAtomValue(atoms.notificationPopoverMode);
- const [notifications, setNotifications] = useAtom(atoms.notifications);
- const [hoveredId, setHoveredId] = useState(null);
-
- const removeNotification = useCallback(
- (id: string) => {
- setNotifications((prevNotifications) => prevNotifications.filter((n) => n.id !== id));
- },
- [setNotifications]
- );
-
- const hideNotification = useCallback(
- (id: string) => {
- setNotifications((prevNotifications) =>
- prevNotifications.map((n) => (n.id === id ? { ...n, hidden: true } : n))
- );
- },
- [setNotifications]
- );
-
- const hideAllNotifications = useCallback(() => {
- setNotifications((prevNotifications) => prevNotifications.map((n) => ({ ...n, hidden: true })));
- }, [setNotifications]);
-
- const removeAllNotifications = useCallback(() => {
- setNotifications((prevNotifications) => prevNotifications.filter((n) => n.persistent));
- }, [setNotifications]);
-
- const copyNotification = useCallback(
- (id: string) => {
- const notif = notifications.find((n) => n.id === id);
- if (!notif) return;
-
- let text = notif.title ?? "";
- if (notif.message) {
- text += text.length > 0 ? `\n${notif.message}` : notif.message;
- }
- navigator.clipboard
- .writeText(text)
- .then(() => {
- console.info("Text copied to clipboard");
- })
- .catch((err) => {
- console.error("Failed to copy text: ", err);
- });
- },
- [notifications]
- );
-
- const handleActionClick = useCallback(
- (e: React.MouseEvent, action: NotificationActionType, id: string) => {
- e.stopPropagation();
- const actionFn = notificationActions[action.actionKey];
- if (actionFn) {
- actionFn();
- removeNotification(id);
- } else {
- console.warn(`No action found for key: ${action.actionKey}`);
- }
- },
- [removeNotification]
- );
-
- useEffect(() => {
- if (notificationPopoverMode) {
- return;
- }
-
- const hasExpiringNotifications = notifications.some((notif) => notif.expiration);
- if (!hasExpiringNotifications) {
- return;
- }
-
- const intervalId = setInterval(() => {
- const now = Date.now();
-
- setNotifications((prevNotifications) =>
- prevNotifications.filter(
- (notif) => !notif.expiration || notif.expiration > now || notif.id === hoveredId
- )
- );
- }, 1000);
-
- return () => clearInterval(intervalId);
- }, [notificationPopoverMode, notifications, hoveredId, setNotifications]);
-
- const formatTimestamp = (timestamp: string): string => {
- const notificationTime = new Date(timestamp).getTime();
- const now = Date.now();
- const diffInSeconds = Math.floor((now - notificationTime) / 1000);
- const diffInMinutes = Math.floor(diffInSeconds / 60);
- const diffInHours = Math.floor(diffInMinutes / 60);
- const diffInDays = Math.floor(diffInHours / 24);
-
- if (diffInMinutes === 0) {
- return `Just now`;
- } else if (diffInMinutes < 60) {
- return `${diffInMinutes} mins ago`;
- } else if (diffInHours < 24) {
- return `${diffInHours} hrs ago`;
- } else if (diffInDays < 7) {
- return `${diffInDays} days ago`;
- } else {
- return new Date(timestamp).toLocaleString();
- }
- };
-
- return {
- notifications,
- hoveredId,
- setHoveredId,
- removeNotification,
- removeAllNotifications,
- hideNotification,
- hideAllNotifications,
- copyNotification,
- handleActionClick,
- formatTimestamp,
- };
-}
diff --git a/frontend/app/store/global-atoms.ts b/frontend/app/store/global-atoms.ts
index 1e1c4a84d0..d0e6950877 100644
--- a/frontend/app/store/global-atoms.ts
+++ b/frontend/app/store/global-atoms.ts
@@ -119,8 +119,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
const connStatuses = Array.from(connStatusMap.values()).map((atom) => get(atom));
return connStatuses;
});
- const notificationsAtom = atom([]);
- const notificationPopoverModeAtom = atom(false);
const reinitVersion = atom(0);
const rateLimitInfoAtom = atom(null) as PrimitiveAtom;
atoms = {
@@ -143,8 +141,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
documentHasFocus: documentHasFocusAtom,
modalOpen,
allConnStatus: allConnStatusAtom,
- notifications: notificationsAtom,
- notificationPopoverMode: notificationPopoverModeAtom,
reinitVersion,
waveAIRateLimitInfoAtom: rateLimitInfoAtom,
} as GlobalAtomsType;
diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts
index e46626448e..2cfb5945ae 100644
--- a/frontend/app/store/global.ts
+++ b/frontend/app/store/global.ts
@@ -736,35 +736,6 @@ function clearAllTabIndicators() {
}
}
-function addOrUpdateNotification(notif: NotificationType) {
- globalStore.set(atoms.notifications, (prevNotifications) => {
- // Remove any existing notification with the same ID
- const notificationsWithoutThisId = prevNotifications.filter((n) => n.id !== notif.id);
- // Add the new notification
- return [...notificationsWithoutThisId, notif];
- });
-}
-
-function pushNotification(notif: NotificationType) {
- if (!notif.id && notif.persistent) {
- return;
- }
- notif.id = notif.id ?? crypto.randomUUID();
- addOrUpdateNotification(notif);
-}
-
-function removeNotificationById(id: string) {
- globalStore.set(atoms.notifications, (prev) => {
- return prev.filter((notif) => notif.id !== id);
- });
-}
-
-function removeNotification(id: string) {
- globalStore.set(atoms.notifications, (prev) => {
- return prev.filter((notif) => notif.id !== id);
- });
-}
-
function createTab() {
getApi().createTab();
}
@@ -815,13 +786,10 @@ export {
loadConnStatus,
loadTabIndicators,
openLink,
- pushNotification,
readAtom,
recordTEvent,
refocusNode,
registerBlockComponentModel,
- removeNotification,
- removeNotificationById,
replaceBlock,
setActiveTab,
setNodeFocus,
diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts
index d37d6b4ce0..12e2827119 100644
--- a/frontend/types/custom.d.ts
+++ b/frontend/types/custom.d.ts
@@ -25,8 +25,6 @@ declare global {
updaterStatusAtom: jotai.PrimitiveAtom;
modalOpen: jotai.PrimitiveAtom;
allConnStatus: jotai.Atom;
- notifications: jotai.PrimitiveAtom;
- notificationPopoverMode: jotai.Atom;
reinitVersion: jotai.PrimitiveAtom;
waveAIRateLimitInfoAtom: jotai.PrimitiveAtom;
};
@@ -407,27 +405,6 @@ declare global {
baseDir: string;
};
- export type NotificationActionType = {
- label: string;
- actionKey: string;
- rightIcon?: string;
- color?: "green" | "grey";
- disabled?: boolean;
- };
-
- export type NotificationType = {
- id?: string;
- icon: string;
- title: string;
- message: string;
- timestamp: string;
- expiration?: number;
- hidden?: boolean;
- actions?: NotificationActionType[];
- persistent?: boolean;
- type?: "error" | "update" | "info" | "warning";
- };
-
interface AbstractWshClient {
recvRpcMessage(msg: RpcMessage): void;
}
diff --git a/frontend/wave.ts b/frontend/wave.ts
index 341b36f48e..c380163071 100644
--- a/frontend/wave.ts
+++ b/frontend/wave.ts
@@ -26,8 +26,6 @@ import {
initGlobalWaveEventSubs,
loadConnStatus,
loadTabIndicators,
- pushNotification,
- removeNotificationById,
subscribeToConnEvents,
} from "@/store/global";
import { activeTabIdAtom } from "@/store/tab-model";
@@ -49,8 +47,6 @@ let savedInitOpts: WaveInitOpts = null;
(window as any).countersPrint = countersPrint;
(window as any).countersClear = countersClear;
(window as any).getLayoutModelForStaticTab = getLayoutModelForStaticTab;
-(window as any).pushNotification = pushNotification;
-(window as any).removeNotificationById = removeNotificationById;
(window as any).modalsModel = modalsModel;
function updateZoomFactor(zoomFactor: number) {