diff --git a/src/docs/src/UI.md b/src/docs/src/UI.md
index 70405e8c1..7b6e4b104 100644
--- a/src/docs/src/UI.md
+++ b/src/docs/src/UI.md
@@ -12,6 +12,7 @@ The UI API provides a comprehensive set of tools for creating rich user interfac
### Dialogs and Alerts
- **[`puter.ui.alert()`](/UI/alert/)** - Show alert dialogs
+- **[`puter.ui.notify()`](/UI/notify/)** - Show desktop notifications
- **[`puter.ui.prompt()`](/UI/prompt/)** - Show input prompts
### Window Management
@@ -47,4 +48,4 @@ The UI API provides a comprehensive set of tools for creating rich user interfac
- **[`puter.ui.showColorPicker()`](/UI/showColorPicker/)** - Show color picker
- **[`puter.ui.showFontPicker()`](/UI/showFontPicker/)** - Show font picker
- **[`puter.ui.showSpinner()`](/UI/showSpinner/)** - Show spinner
-- **[`puter.ui.socialShare()`](/UI/socialShare/)** - Share content socially
\ No newline at end of file
+- **[`puter.ui.socialShare()`](/UI/socialShare/)** - Share content socially
diff --git a/src/docs/src/UI/notify.md b/src/docs/src/UI/notify.md
new file mode 100644
index 000000000..d5adef88c
--- /dev/null
+++ b/src/docs/src/UI/notify.md
@@ -0,0 +1,41 @@
+---
+title: puter.ui.notify()
+description: Displays a desktop notification in Puter.
+platforms: [apps]
+---
+
+Displays a desktop notification in Puter. Use this to surface app events without interrupting the user.
+
+## Syntax
+```js
+puter.ui.notify(options)
+```
+
+## Parameters
+
+#### `options` (optional)
+An object that configures the notification.
+
+- `title` (string): Title shown in the notification.
+- `text` (string): Body text shown under the title.
+- `icon` (string): Icon URL or Puter icon name (for example `bell.svg`).
+- `round_icon` (boolean): If `true`, renders the icon as a circle. `roundIcon` is accepted as an alias.
+- `uid` (string): Optional ID to associate with the notification.
+- `value` (any): Optional value stored on the notification element.
+
+## Return value
+A `Promise` that resolves to the notification UID.
+
+## Examples
+```html
+
+
+```
diff --git a/src/docs/src/sidebar.js b/src/docs/src/sidebar.js
index 687758bdb..45b4ef3f8 100755
--- a/src/docs/src/sidebar.js
+++ b/src/docs/src/sidebar.js
@@ -678,6 +678,14 @@ let sidebar = [
source: '/UI/alert.md',
path: '/UI/alert',
},
+ {
+ title: 'notify()',
+ page_title: 'puter.ui.notify()',
+ title_tag: 'puter.ui.notify()',
+ icon: '/assets/img/function.svg',
+ source: '/UI/notify.md',
+ path: '/UI/notify',
+ },
{
title: 'contextMenu()',
page_title: 'puter.ui.contextMenu()',
diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js
index 6e08d3b35..d7dcba448 100644
--- a/src/gui/src/IPC.js
+++ b/src/gui/src/IPC.js
@@ -34,6 +34,7 @@ import UIWindowFontPicker from './UI/UIWindowFontPicker.js';
import UIWindowRequestPermission from './UI/UIWindowRequestPermission.js';
import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js';
import UIWindowSignup from './UI/UIWindowSignup.js';
+import UINotification from './UI/UINotification.js';
import { PROCESS_IPC_ATTACHED } from './definitions.js';
import TeePromise from './util/TeePromise.js';
@@ -253,6 +254,38 @@ const ipc_listener = async (event, handled) => {
}, '*');
}
//--------------------------------------------------------
+ // showNotification
+ //--------------------------------------------------------
+ else if ( event.data.msg === 'showNotification' ) {
+ const options = event.data.options ?? {};
+ const notification_uid = options.uid ?? `app-${app_uuid}-${msg_id}`;
+ let icon = window.icons['bell.svg'];
+ let round_icon = false;
+
+ if ( typeof options.icon === 'string' && options.icon.length > 0 ) {
+ icon = window.icons[options.icon] ?? options.icon;
+ }
+
+ if ( options.round_icon ) {
+ round_icon = true;
+ }
+
+ UINotification({
+ title: options.title ?? app_name ?? 'Notification',
+ text: options.text ?? '',
+ icon,
+ round_icon,
+ uid: notification_uid,
+ value: options.value,
+ });
+
+ target_iframe.contentWindow.postMessage({
+ original_msg_id: msg_id,
+ msg: 'notificationShown',
+ uid: notification_uid,
+ }, '*');
+ }
+ //--------------------------------------------------------
// getLanguage
//--------------------------------------------------------
else if ( event.data.msg === 'getLanguage' ) {
@@ -1488,12 +1521,14 @@ const ipc_listener = async (event, handled) => {
target_path, el_filedialog_window,
file_to_upload, overwrite,
}) => {
- const res = await puter.fs.write(target_path,
- file_to_upload,
- {
- dedupeName: false,
- overwrite: overwrite,
- });
+ const res = await puter.fs.write(
+ target_path,
+ file_to_upload,
+ {
+ dedupeName: false,
+ overwrite: overwrite,
+ },
+ );
await tell_caller_and_update_views({ res, el_filedialog_window, target_path });
};
diff --git a/src/puter-js/src/modules/UI.js b/src/puter-js/src/modules/UI.js
index f299ecc27..e0320618f 100644
--- a/src/puter-js/src/modules/UI.js
+++ b/src/puter-js/src/modules/UI.js
@@ -499,6 +499,9 @@ class UI extends EventListener {
// execute callback
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
}
+ else if ( e.data.msg === 'notificationShown' ) {
+ this.#callbackFunctions[e.data.original_msg_id](e.data.uid);
+ }
else if ( e.data.msg === 'languageReceived' ) {
// execute callback
this.#callbackFunctions[e.data.original_msg_id](e.data.language);
@@ -722,6 +725,16 @@ class UI extends EventListener {
});
};
+ notify (options) {
+ return new Promise((resolve) => {
+ const normalized = { ...(options ?? {}) };
+ if ( normalized.roundIcon !== undefined && normalized.round_icon === undefined ) {
+ normalized.round_icon = normalized.roundIcon;
+ }
+ this.#postMessageWithCallback('showNotification', resolve, { options: normalized });
+ });
+ };
+
showDirectoryPicker (options, callback) {
return new Promise((resolve, reject) => {
if ( ! globalThis.open ) {
diff --git a/src/puter-js/types/modules/ui.d.ts b/src/puter-js/types/modules/ui.d.ts
index 60530d685..511449b94 100644
--- a/src/puter-js/types/modules/ui.d.ts
+++ b/src/puter-js/types/modules/ui.d.ts
@@ -74,6 +74,16 @@ export interface DirectoryPickerOptions {
multiple?: boolean;
}
+export interface NotificationOptions {
+ title?: string;
+ text?: string;
+ icon?: string;
+ round_icon?: boolean;
+ roundIcon?: boolean;
+ uid?: string;
+ value?: unknown;
+}
+
export interface AppConnectionCloseEvent {
appInstanceID: string;
statusCode?: number;
@@ -113,6 +123,7 @@ export class AppConnection {
export class UI {
alert (message?: string, buttons?: AlertButton[]): Promise;
prompt (message?: string, placeholder?: string): Promise;
+ notify (options?: NotificationOptions): Promise;
authenticateWithPuter (): Promise;
contextMenu (options: ContextMenuOptions): void;
createWindow (options?: WindowOptions): void;