Skip to content

feat(one): bridge in-app notifications to native OS notifications#25

Open
os-zhuang wants to merge 3 commits into
mainfrom
feat/native-notification-bridge
Open

feat(one): bridge in-app notifications to native OS notifications#25
os-zhuang wants to merge 3 commits into
mainfrom
feat/native-notification-bridge

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

ObjectOS One is a desktop client, but notifications were in-app only — the Console bell (sys_inbox_message, ADR-0030 L5) is invisible when the window is backgrounded, which is exactly when a notification matters most. This is a minimal approach-1 prototype: a webview bridge that mirrors new inbox messages to native OS notifications (macOS Notification Center / Windows toast / Linux libnotify).

How

  • assets/notification-bridge.js — injected into every page alongside update-banner.js (combined into one initialization script in windows.rs). No-ops outside the Console and outside Tauri.
  • On the Console it:
    • polls the canonical inbox object via the data API (GET /api/v1/data/sys_inbox_message?sort=-created_at) — the dedicated /api/v1/notifications route returns 404 in this runtime, and the bell reads sys_inbox_message anyway;
    • dedupes by id (localStorage, baselined on first load so the existing backlog isn't replayed as a burst);
    • only when the window is backgrounded (document.hidden / no focus), fires one native notification per new message;
    • badges the dock/taskbar with the count missed while away, cleared on focus;
    • focuses + deep-links on click (best-effort, via the plugin's action event; OS-default focus still applies otherwise);
    • requests notification permission once, up front in the foreground.

No new infra needed

tauri-plugin-notification is already a dep and registered (lib.rs), notification:default + window show/focus/unminimize permissions are already granted (capabilities/default.json), and withGlobalTauri exposes the JS API. So this is a JS file + one injection line.

Verification

  • Data contract verified end-to-end against a live @objectstack 9.7.0 runtime: inserting a real sys_inbox_message row, the bridge's exact fetch+parse extracts title / body (body_md) / actionUrl (action_url) / id / createdAt correctly. JS syntax-checked.
  • Not exercisable here: the native firing path (sendNotification / dock badge / focus) needs an actual Tauri build (no cargo in this environment; the Tauri build is tag-triggered in CI). The plumbing is all in place; this needs a real desktop build to see toasts.

Follow-ups (out of scope for this prototype)

  • A desktop/native channel in sys_notification_preference so it's user-configurable like email/push (approach 2 — needs a framework touch).
  • True unread badge via the receipt join (this prototype badges "missed while away").
  • Firmer click→deep-link wiring (depends on the notification plugin's action API surface in the built app).

🤖 Generated with Claude Code

os-zhuang and others added 3 commits June 16, 2026 18:27
ObjectOS One is a desktop client, but notifications were in-app only — the
Console bell (`sys_inbox_message`, ADR-0030 L5) is invisible when the window is
backgrounded, which is exactly when notifications matter. This adds a webview
bridge that mirrors new inbox messages to native OS notifications (macOS
Notification Center / Windows toast / Linux libnotify).

`assets/notification-bridge.js` is injected into every page alongside
update-banner.js (combined into one initialization script in windows.rs). It
no-ops outside the Console and outside Tauri. On the Console it polls the
canonical inbox object via the data API (`GET /api/v1/data/sys_inbox_message`
— the dedicated `/api/v1/notifications` route isn't mounted in this runtime),
dedupes by id (localStorage, baselined on first load so the backlog isn't
replayed), and when the window is backgrounded fires a native notification per
new message, badges the dock with the count missed while away (cleared on
focus), and focuses + deep-links on click (best-effort via the plugin's action
event).

No new Rust deps or capabilities needed: tauri-plugin-notification is already
registered (lib.rs) and `notification:default` + window show/focus perms are
already granted; withGlobalTauri exposes the JS API.

Verified the data contract end-to-end against a live 9.7.0 runtime: inserting a
real sys_inbox_message row, the bridge's fetch+parse extracts
title/body(body_md)/actionUrl(action_url)/id/createdAt correctly. The native
firing path (sendNotification/badge/focus) needs an actual Tauri build to
exercise — out of reach here (no cargo); the plumbing is in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first cut assumed Tauri plugin/window JS APIs are on `window.__TAURI__`
(`.notification.sendNotification`, `.window…setBadgeCount`, `.app`). They are
not: this app's `withGlobalTauri` exposes only `core`/`event`, and every native
op here goes through a Rust `#[tauri::command]` called via `core.invoke` (see
prefs.html / update-banner.js). As written the bridge would have fired nothing —
the permission gate (`T.notification`) would stay false.

Rework to match the codebase:
- add Rust commands `notify_native`, `set_badge`, `notif_request_permission`
  (commands.rs) and register them (lib.rs); they call the already-registered
  notification plugin + the main window's badge from Rust, bypassing the JS ACL
  (no capability changes needed);
- the bridge now calls them via `core.invoke` and drops all `T.notification`/
  `.window`/`.app` usage; the tested poll/parse path is unchanged.

Compiler-verified: `cargo check` passes (toolchain installed locally), so the
Tauri APIs (`notification().builder().show()`, `set_badge_count`,
`request_permission`) are correct. Still needs a real desktop build to see a
toast actually render (and macOS authorization typically wants a signed bundle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lightweight observability for the notification bridge (content-free — no
title/body logged). Verified the app builds and launches via `tauri dev`
(toolchain installed locally); the sidecar loads Auth+Audit so sys_notification
exists and the inbox path is live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant