feat(native): "Mark as read" button on rich toasts (#9)
Build Lotus Chat Desktop / prepare (push) Successful in 7s
Build Lotus Chat Desktop / build-windows (push) Canceled after 32m48s
Build Lotus Chat Desktop / build-linux (push) Canceled after 32m25s
Build Lotus Chat Desktop / build-arch (push) Canceled after 0s
Build Lotus Chat Desktop / update-manifest (push) Canceled after 0s

Toasts with a room id get a Mark as read action next to Send; it emits
`lotus-notification-mark-read {roomId}` without raising the window, and
cinny marks the room read through its existing markAsRead path. Invite
toasts (no room id) get neither button.

Bump cinny to 23649f12.

Closes #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-23 19:49:54 -04:00
co-authored by Claude Opus 5.5
parent 4648c8ccc7
commit 5a9bfa8fe4
2 changed files with 24 additions and 5 deletions
+23 -4
View File
@@ -24,6 +24,10 @@
//! `thread_id`), never the tag. Toasts without a room id (invites) get no reply
//! box.
//!
//! Mark as read (cinny-desktop #9): toasts with a room id also carry a
//! **Mark as read** button (`arguments="mark_read"`); activating it forwards the
//! room id so the web client sends the read receipt, without raising the window.
//!
//! If ANY WinRT step fails (most importantly: no registered AppUserModelID — see
//! the runtime note below), we fall back to the plain `tauri-plugin-notification`
//! notification so notifications always work.
@@ -161,13 +165,17 @@ fn show_windows_toast(
_ => String::new(),
};
// An inline reply input and a foreground Send action, only when there is a
// room to reply to. `hint-inputId="reply"` binds the Send button to the text
// box so the reply text arrives in `UserInput()` keyed "reply".
// An inline reply input, a Send action and Mark as read, only when there is
// a room to act on. `hint-inputId="reply"` binds the Send button to the text
// box so the reply text arrives in `UserInput()` keyed "reply". Both are
// "foreground" because the in-process Activated event (not COM background
// activation) is what an unpackaged app receives; `arguments` tells them
// apart.
let actions = if room_id.is_some() {
r#"<actions>
<input id="reply" type="text" placeHolder="Reply..."/>
<action content="Send" arguments="reply" activationType="foreground" hint-inputId="reply"/>
<action content="Mark as read" arguments="mark_read" activationType="foreground"/>
</actions>"#
} else {
""
@@ -225,8 +233,19 @@ fn show_windows_toast(
// Extract the reply text (if the Send action / input was used).
let reply = read_reply(&activated_args).unwrap_or_default();
let action = activated_args
.Arguments()
.map(|a| a.to_string())
.unwrap_or_default();
if !reply.is_empty() {
if action == "mark_read" {
// Mark as read: no window raise, like the quick reply.
let payload = serde_json::json!({
"roomId": room_id_owned.as_deref(),
})
.to_string();
super::emit_to_web(&app_activated, "lotus-notification-mark-read", &payload);
} else if !reply.is_empty() {
// Quick reply: forward the room id + text to the web client.
let payload = serde_json::json!({
"roomId": room_id_owned.as_deref(),