Skip to content

Add group chat snooze menu back from history#339

Open
nacklequack wants to merge 1 commit into
AlchemyViewer:developfrom
nacklequack:feature/group-chat-snooze
Open

Add group chat snooze menu back from history#339
nacklequack wants to merge 1 commit into
AlchemyViewer:developfrom
nacklequack:feature/group-chat-snooze

Conversation

@nacklequack

Copy link
Copy Markdown
Contributor

Description

Adds the group chat snooze flyout/menu back to group IM sessions on develop.

This restores the small group chat toolbar menu that allows a group conversation to be snoozed for:

  • Next message
  • 5 minutes
  • 15 minutes
  • 30 minutes
  • 45 minutes
  • 1 hour
  • Next relog

The implementation ports the group-chat floater/action/session pieces from the older main history into the current develop IM/session code. It tracks snoozed group sessions locally, suppresses reopening while the snooze is active, and restores the group session when a message arrives after the snooze expires.

image

Historical attribution/context:

  • Based primarily on old main commit be67f0cd6c06 by @RyeMutt: “Port Catznip's snooze group feature”.
  • That commit itself credits Catznip as the original source of the group snooze feature.
  • This branch builds on group muting work already present on develop, including 2c15baf8b5 by @cinderblocks/@RyeMutt , originally from Katherine Berry, and follow-up ced1e6ed9b by Rye for muted group chat session behavior.
  • I intentionally did not restore the older group profile/options behavior around snoozing on close, because later old-main commits f804d2e7f80d / 821bc5b960fe by @RyeMutt partially backed out/adjusted that area. This PR is scoped to the explicit group chat toolbar snooze menu.

Checklist

Please ensure the following before requesting review:

  • I have provided a clear title and detailed description for this pull request.
  • If useful, I have included media such as screenshots and video to show off my changes.
  • I have tested the changes locally and verified they work as intended.
  • All new and existing tests pass.
  • Code follows the project's style guidelines.
  • Documentation has been updated if needed.
  • Any dependent changes have been merged and published in downstream modules
  • I have reviewed the contributing guidelines.

Additional Notes

Local verification completed:

.\.venv\Scripts\Activate.ps1
cmake --build build-Windows-vs2026-os --config Release

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a group chat snooze option with preset durations, including “Next message” and “Next relog.”
    • Group conversations can now be temporarily paused and later restored automatically.
  • Bug Fixes

    • Improved handling of incoming group chat sessions so snoozed conversations can resume correctly instead of being dropped.
    • Leaving a group chat now respects the selected close action, including snooze behavior.

Walkthrough

This PR adds a group IM "snooze" feature: session state gains a close-action and snooze duration, LLGroupActions exposes leaveIM/snoozeIM replacing endIM, LLIMMgr tracks and restores snoozed sessions, incoming messages trigger restoration, and a new toolbar flyout UI lets users snooze conversations for preset durations.

Changes

Group IM Snooze

Layer / File(s) Summary
Session close-action and snooze data model
indra/newview/llimview.h
Adds SCloseAction enum (CLOSE_DEFAULT, CLOSE_LEAVE, CLOSE_SNOOZE), mCloseAction/mSnoozeDuration fields on LLIMSession, and mSnoozedSessions map plus method declarations on LLIMMgr.
Group action entry points
indra/newview/llgroupactions.cpp, indra/newview/llgroupactions.h
Adds close_group_im helper and public leaveIM/snoozeIM methods that replace the prior endIM implementation.
leaveSession snooze handling and restoration
indra/newview/llimview.cpp
leaveSession now snoozes group sessions with CLOSE_SNOOZE instead of leaving; adds checkSnoozeExpiration, isSnoozedSession, restoreSnoozedSession; removes an unrelated LLHTTPRegistration block.
Incoming message snooze restoration
indra/newview/llimprocessing.cpp
IM_SESSION_SEND handling now attempts to restore a snoozed session for in-group agents instead of always discarding the message when no session exists.
UI handler and toolbar snooze flyout
indra/newview/llfloaterimsession.cpp, indra/newview/llfloaterimsession.h, indra/newview/skins/default/xui/en/floater_im_session.xml
Adds onSnoozeGroupClicked handler wired to a new snooze_groupt_btn flyout offering preset snooze durations and a leave option.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LLFloaterIMSession
  participant LLGroupActions
  participant LLIMMgr
  participant LLIMProcessing

  User->>LLFloaterIMSession: click snooze_groupt_btn (duration)
  LLFloaterIMSession->>LLGroupActions: snoozeIM(group_id, duration) / leaveIM(group_id)
  LLGroupActions->>LLIMMgr: leaveSession(session_id) with mCloseAction set
  LLIMMgr->>LLIMMgr: store expiration in mSnoozedSessions

  Note over LLIMProcessing: New group message arrives
  LLIMProcessing->>LLIMMgr: checkSnoozeExpiration(session_id)
  LLIMMgr-->>LLIMProcessing: not expired
  LLIMProcessing->>LLIMMgr: restoreSnoozedSession(session_id)
  LLIMMgr->>LLIMMgr: remove from mSnoozedSessions, addSession, sendStartSession
  LLIMMgr-->>LLIMProcessing: session restored
Loading

Poem

A hop, a pause, a session set aside,
No need to leave, just let it hide.
Five minutes, an hour, or 'til next relog,
I snooze my chats like a nap in the fog. 🐇💤
Then back I bounce when new words arrive —
Thump thump, my burrow of code's alive!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: restoring the group chat snooze menu.
Description check ✅ Passed The description is detailed and covers context, behavior, screenshots, and verification, but it omits the related issue link.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
indra/newview/llfloaterimsession.cpp (1)

288-300: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: naming typo and cast robustness.

  • boost::lexical_cast<S32>(value) will throw bad_lexical_cast if getValue() ever returns a non-numeric string; since values are hardcoded in the XML this is low risk today but fragile against future edits (e.g., adding a non-numeric flyout item value).
  • The control name snooze_groupt_btn (typo for "group") is used both here (implicitly via caller) and in the XML; worth fixing for consistency, though it's an internal identifier only.

Consider using LLStringUtil::convertToS32 or wrapping the cast in a try/catch for defensive robustness against malformed values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/llfloaterimsession.cpp` around lines 288 - 300, The snooze-time
handling in llfloaterimsession.cpp is too fragile because llfloaterIMSession’s
use of boost::lexical_cast<S32>(value) can throw if the control value ever
becomes non-numeric. Update the logic in the value-handling branch to use a
safer conversion path such as LLStringUtil::convertToS32 or a guarded try/catch,
and keep the LLGroupActions::leaveIM/snoozeIM behavior unchanged for the
existing "-1", empty, and numeric cases. Also rename the internal control
identifier snooze_groupt_btn to the consistent group spelling in both the UI XML
and any code that references it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@indra/newview/llfloaterimsession.cpp`:
- Around line 281-302: The LLFloaterIMSession::onSnoozeGroupClicked handler
treats the “Next relog” option as leaveIM and only stores snooze state in
mSnoozedSessions, so update the snooze flow to persist a true “mute until next
login” state instead of using a transient in-memory flag. Add a restore path for
that persistent state when sessions are recreated, and keep the existing snooze
durations for the other value cases while ensuring the value="-1" branch maps to
the new persistent snooze behavior rather than leaving the IM session.

In `@indra/newview/llimprocessing.cpp`:
- Around line 1346-1356: The message handling in the block using hasSession,
checkSnoozeExpiration, and restoreSnoozedSession drops the incoming message
after a snoozed session is successfully restored because control flow exits the
hasSession branch before the later offline/is_do_not_disturb handling. Update
this logic so a successful restore continues into the normal message-processing
path instead of returning or skipping the rest of the function, and make the
same change in the duplicated code path referenced by the other affected
section.

In `@indra/newview/llimview.cpp`:
- Around line 3561-3562: The snooze handling in llimview.cpp is collapsing the
“next relog” sentinel because llmax(0, im_session->mSnoozeDuration) turns
mSnoozeDuration == -1 into an immediately expired timer. Update the snooze
calculation in the code paths that assign mSnoozedSessions for both the session
state in llimview.cpp so that the sentinel value is preserved and treated as
“snoozed until logout,” while only finite snooze durations are converted into a
timestamp-based expiry. Use the existing im_session and mSnoozedSessions logic
as the anchor points when applying the fix.
- Around line 3779-3785: The snooze entry is being removed before restoration is
confirmed, so a failed getGroupData() call leaves the session permanently
unsnoozed. Update the snoozed-session restoration flow in the logic that uses
mSnoozedSessions and gAgent.getGroupData() so the erase only happens after group
data has been successfully retrieved and the session can actually be restored.
Keep the existing false return path, but preserve the snooze state when
restoration cannot proceed.

---

Nitpick comments:
In `@indra/newview/llfloaterimsession.cpp`:
- Around line 288-300: The snooze-time handling in llfloaterimsession.cpp is too
fragile because llfloaterIMSession’s use of boost::lexical_cast<S32>(value) can
throw if the control value ever becomes non-numeric. Update the logic in the
value-handling branch to use a safer conversion path such as
LLStringUtil::convertToS32 or a guarded try/catch, and keep the
LLGroupActions::leaveIM/snoozeIM behavior unchanged for the existing "-1",
empty, and numeric cases. Also rename the internal control identifier
snooze_groupt_btn to the consistent group spelling in both the UI XML and any
code that references it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b2a332db-80d2-46e3-aba4-5a95a34fa016

📥 Commits

Reviewing files that changed from the base of the PR and between 43e3ae0 and 2f447de.

📒 Files selected for processing (8)
  • indra/newview/llfloaterimsession.cpp
  • indra/newview/llfloaterimsession.h
  • indra/newview/llgroupactions.cpp
  • indra/newview/llgroupactions.h
  • indra/newview/llimprocessing.cpp
  • indra/newview/llimview.cpp
  • indra/newview/llimview.h
  • indra/newview/skins/default/xui/en/floater_im_session.xml

Comment on lines +281 to +302
void LLFloaterIMSession::onSnoozeGroupClicked(const LLUICtrl* pCtrl)
{
if (!pCtrl)
{
return;
}

const std::string value = pCtrl->getValue().asString();
if (value == "-1")
{
LLGroupActions::leaveIM(mSessionID);
}
else if (value.empty())
{
LLGroupActions::snoozeIM(mSessionID, 0);
}
else
{
LLGroupActions::snoozeIM(mSessionID, boost::lexical_cast<S32>(value) * 60);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and symbols.
git ls-files | rg 'indra/newview/(llfloaterimsession.cpp|llgroupactions.*|.*im_session.xml)$'

printf '\n--- llfloaterimsession.cpp (relevant section) ---\n'
sed -n '260,330p' indra/newview/llfloaterimsession.cpp

printf '\n--- LLGroupActions definitions/usages ---\n'
rg -n "leaveIM\\(|snoozeIM\\(|close_group_im\\(" indra/newview -g '!*.o'

printf '\n--- floater_im_session.xml (relevant section) ---\n'
sed -n '320,350p' indra/newview/skins/default/xui/en/floater_im_session.xml

printf '\n--- snooze/leave behavior references ---\n'
rg -n "CLOSE_SNOOZE|CLOSE_LEAVE|snooze_duration|Next relog|snooze" indra/newview

Repository: AlchemyViewer/Alchemy

Length of output: 8096


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- llgroupactions.cpp close_group_im/leaveIM/snoozeIM ---\n'
sed -n '600,665p' indra/newview/llgroupactions.cpp

printf '\n--- llimview.cpp snooze/reopen path ---\n'
sed -n '3540,3795p' indra/newview/llimview.cpp

printf '\n--- llimview.h close action / snoozed sessions declarations ---\n'
sed -n '70,95p' indra/newview/llimview.h
sed -n '535,555p' indra/newview/llimview.h

Repository: AlchemyViewer/Alchemy

Length of output: 12670


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- restoreSnoozedSession references ---\n'
rg -n "restoreSnoozedSession\\(" indra/newview

printf '\n--- mSnoozedSessions references ---\n'
rg -n "mSnoozedSessions|checkSnoozeExpiration|isSnoozedSession" indra/newview

printf '\n--- login/session-init hooks that might restore snoozed sessions ---\n'
rg -n "login|logged in|login.*session|session.*login|restore.*snooze|Snooze" indra/newview/ll*.cpp indra/newview/ll*.h

Repository: AlchemyViewer/Alchemy

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1328,1362p' indra/newview/llimprocessing.cpp

printf '\n--- any snooze persistence/serialization hooks ---\n'
rg -n "mSnoozedSessions|snoozed_sessions|restoreSnoozedSession|checkSnoozeExpiration|isSnoozedSession|save.*snooze|load.*snooze" indra/newview/llim*.cpp indra/newview/llim*.h

Repository: AlchemyViewer/Alchemy

Length of output: 3096


Next relog needs persistent snooze handling. value="-1" still calls leaveIM, while snooze state is only kept in mSnoozedSessions in memory. If this option is meant to mute the group until the next login, it needs a persistent restore path; otherwise the label is misleading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/llfloaterimsession.cpp` around lines 281 - 302, The
LLFloaterIMSession::onSnoozeGroupClicked handler treats the “Next relog” option
as leaveIM and only stores snooze state in mSnoozedSessions, so update the
snooze flow to persist a true “mute until next login” state instead of using a
transient in-memory flag. Add a restore path for that persistent state when
sessions are recreated, and keep the existing snooze durations for the other
value cases while ensuring the value="-1" branch maps to the new persistent
snooze behavior rather than leaving the IM session.

Comment on lines 1346 to 1356
if (!gIMMgr->hasSession(session_id))
{
return;
if (!gAgent.isInGroup(session_id) ||
!gIMMgr->checkSnoozeExpiration(session_id) ||
!gIMMgr->restoreSnoozedSession(session_id))
{
return;
}
}

else if (offline == IM_ONLINE && is_do_not_disturb)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Process the message after restoring the snoozed session.

When restoreSnoozedSession() succeeds, execution leaves the if (!hasSession) block and skips the attached else if / else, so the message that woke the session is dropped.

🐛 Proposed fix
             if (!gIMMgr->hasSession(session_id))
             {
                 if (!gAgent.isInGroup(session_id) ||
                     !gIMMgr->checkSnoozeExpiration(session_id) ||
                     !gIMMgr->restoreSnoozedSession(session_id))
                 {
                     return;
                 }
             }
 
-            else if (offline == IM_ONLINE && is_do_not_disturb)
+            if (offline == IM_ONLINE && is_do_not_disturb)
             {

Also applies to: 1389-1415

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/llimprocessing.cpp` around lines 1346 - 1356, The message
handling in the block using hasSession, checkSnoozeExpiration, and
restoreSnoozedSession drops the incoming message after a snoozed session is
successfully restored because control flow exits the hasSession branch before
the later offline/is_do_not_disturb handling. Update this logic so a successful
restore continues into the normal message-processing path instead of returning
or skipping the rest of the function, and make the same change in the duplicated
code path referenced by the other affected section.

Comment on lines +3561 to +3562
const F64 duration = llmax(0, im_session->mSnoozeDuration);
mSnoozedSessions[session_id] = LLTimer::getTotalSeconds() + duration;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the “next relog” sentinel instead of expiring it immediately.

mSnoozeDuration == -1 is the only visible sentinel for “next relog”, but llmax(0, ...) stores it as an immediately-expired snooze, so the next group message restores the chat instead of keeping it snoozed until logout.

🐛 Proposed fix
-        const F64 duration = llmax(0, im_session->mSnoozeDuration);
-        mSnoozedSessions[session_id] = LLTimer::getTotalSeconds() + duration;
+        const S32 duration = im_session->mSnoozeDuration;
+        mSnoozedSessions[session_id] = duration < 0
+            ? -1.0 // in-memory only: snoozed until next relog
+            : LLTimer::getTotalSeconds() + duration;
-    return it != mSnoozedSessions.end() && it->second <= LLTimer::getTotalSeconds();
+    return it != mSnoozedSessions.end()
+        && it->second >= 0.0
+        && it->second <= LLTimer::getTotalSeconds();

Also applies to: 3760-3763

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/llimview.cpp` around lines 3561 - 3562, The snooze handling in
llimview.cpp is collapsing the “next relog” sentinel because llmax(0,
im_session->mSnoozeDuration) turns mSnoozeDuration == -1 into an immediately
expired timer. Update the snooze calculation in the code paths that assign
mSnoozedSessions for both the session state in llimview.cpp so that the sentinel
value is preserved and treated as “snoozed until logout,” while only finite
snooze durations are converted into a timestamp-based expiry. Use the existing
im_session and mSnoozedSessions logic as the anchor points when applying the
fix.

Comment on lines +3779 to +3785
mSnoozedSessions.erase(it);

LLGroupData group_data;
if (!gAgent.getGroupData(session_id, group_data))
{
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t consume snooze state before restoration can succeed.

If getGroupData() fails, this returns false after erasing the snooze entry, so future messages can no longer restore the snoozed session.

🐛 Proposed fix
-    mSnoozedSessions.erase(it);
-
     LLGroupData group_data;
     if (!gAgent.getGroupData(session_id, group_data))
     {
         return false;
     }
+
+    mSnoozedSessions.erase(it);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mSnoozedSessions.erase(it);
LLGroupData group_data;
if (!gAgent.getGroupData(session_id, group_data))
{
return false;
}
LLGroupData group_data;
if (!gAgent.getGroupData(session_id, group_data))
{
return false;
}
mSnoozedSessions.erase(it);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/llimview.cpp` around lines 3779 - 3785, The snooze entry is
being removed before restoration is confirmed, so a failed getGroupData() call
leaves the session permanently unsnoozed. Update the snoozed-session restoration
flow in the logic that uses mSnoozedSessions and gAgent.getGroupData() so the
erase only happens after group data has been successfully retrieved and the
session can actually be restored. Keep the existing false return path, but
preserve the snooze state when restoration cannot proceed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant