Skip to content

Ref(html5): Re-introduce raise hand audio notification and full user options for raised hand users list#24214

Merged
ramonlsouza merged 3 commits intobigbluebutton:v3.0.x-releasefrom
Tainan404:i-24175
Nov 11, 2025
Merged

Ref(html5): Re-introduce raise hand audio notification and full user options for raised hand users list#24214
ramonlsouza merged 3 commits intobigbluebutton:v3.0.x-releasefrom
Tainan404:i-24175

Conversation

@Tainan404
Copy link
Member

@Tainan404 Tainan404 commented Nov 10, 2025

What does this PR do?

  • Reintroduces a lightweight raise-hand notifier that restores the audible alert when hands go up, even if the user list is closed.
  • Adds a simple toast (“{users} raised their hand”) that respects the notification toggles, filters out presenter self-raises, and shares the existing notification infrastructure.
  • Consolidates typing for the raised-hand subscription so all consumers rely on the same response shape.

Closes Issue(s)

Closes #24175

How to test

  1. Join a BBB session as a moderator/presenter.
  2. In Settings → Notifications, toggle “audible notification” and “popup notification” for raised hands; verify sounds/toasts only fire when their respective toggle is ON.
  3. Have a participant raise their hand while the user list is closed; ensure you still hear/see the notifications.
  4. Have the presenter raise their own hand; confirm no “Presenter raised hand” toast appears.
  5. Repeat with popup disabled to confirm only the sound plays, and vice versa.

More

  • Raised hand user list dropdown
image

Simple notification
image

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

This change adds a raise-hand notification system. A new RaiseHandNotifier presentational component detects newly raised hands and can play audio and send push notifications. A RaiseHandNotifierContainer subscribes to RAISED_HAND_USERS, maps current user context and settings, and supplies props to the notifier. App now imports and conditionally renders the notifier when isRaiseHandEnabled is true. Shared types for RaisedHandUser and the subscription response were added. The audio manager now falls back to 'default' sinkId when no output device is set.

Sequence Diagram

sequenceDiagram
    participant App
    participant RaiseHandNotifierContainer
    participant GraphQL as GraphQL Subscription
    participant RaiseHandNotifier
    participant AudioManager
    participant NotificationService

    App->>RaiseHandNotifierContainer: render (if isRaiseHandEnabled)
    RaiseHandNotifierContainer->>GraphQL: subscribe to RAISED_HAND_USERS
    GraphQL-->>RaiseHandNotifierContainer: RaisedHandUsers[]
    RaiseHandNotifierContainer->>RaiseHandNotifier: props (users, settings, user context)

    RaiseHandNotifier->>RaiseHandNotifier: diff current vs previous users

    alt New raised-hand(s) detected (exclude current user) and user has presenter/moderator role
        opt audio alerts enabled
            RaiseHandNotifier->>AudioManager: playAlertSound(outputDeviceId or 'default')
            AudioManager-->>RaiseHandNotifier: played
        end
        opt push alerts enabled
            RaiseHandNotifier->>NotificationService: notify(text)
            NotificationService-->>RaiseHandNotifier: sent
        end
    end

    RaiseHandNotifier->>RaiseHandNotifier: update previous users ref
    RaiseHandNotifier-->>App: (no visible UI returned / null)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Files to check closely:

    • imports/ui/components/raisehand-notifier/component.tsx — diffing logic, effect dependencies, intl message usage, audio/notification side effects
    • imports/ui/components/raisehand-notifier/container.tsx — GraphQL subscription handling, error path that sets connection status
    • imports/ui/components/app/component.jsx — prop addition, import, and conditional rendering of notifier
    • imports/ui/core/graphql/queries/users.ts — new types RaisedHandUser and RaisedHandUsersSubscriptionResponse and their shape
    • imports/ui/components/user-list/.../raised-hands/component.tsx — subscription typing change and removal of local type
    • imports/ui/services/audio-manager/index.js — sinkId fallback to 'default'
    • public/locales/en.json — new translation key and updated translation id usage in user-actions
  • Pay attention to:

    • Correct filtering to exclude current user's own raises and role checks (presenter/moderator)
    • Side-effect cleanup and stable dependency arrays in useEffect
    • Consistency of new GraphQL types with subscription payloads
    • That audio fallback doesn't break devices expecting undefined sinkId behavior
    • i18n key correctness for updated translation id usage

Possibly related issues

  • #24175 — reintroduces audio notifications and wires notification settings for moderators (matches intent of added notifier and settings usage)

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: re-introducing raise-hand audio notifications and adding user options for the raised-hand list.
Linked Issues check ✅ Passed The PR addresses all three objectives from issue #24175: restore audible notifications (RaiseHandNotifier component), add toast messages with toggle support, and align user action options by reusing the same UserActions component.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: RaiseHandNotifier implementation, subscription type consolidation, notification settings wiring, and UI consistency for user actions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly explains what the changes do: reintroduces a raise-hand notifier with audio alerts and toast notifications, consolidates typing, and directly addresses the linked issue #24175.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/container.tsx (1)

43-43: Redundant fallback for currentUserId.

The fallback ?? '' on line 43 is redundant because line 34 already guards against !currentUser. Since currentUser is guaranteed to exist at this point, the nullish coalescing can be removed for clarity.

Apply this diff to simplify:

-      currentUserId={currentUser.userId ?? ''}
+      currentUserId={currentUser.userId}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b452f7c and c4adb99.

📒 Files selected for processing (6)
  • bigbluebutton-html5/imports/ui/components/app/component.jsx (4 hunks)
  • bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (1 hunks)
  • bigbluebutton-html5/imports/ui/components/raisehand-notifier/container.tsx (1 hunks)
  • bigbluebutton-html5/imports/ui/components/user-list/user-list-content/raised-hands/component.tsx (2 hunks)
  • bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (2 hunks)
  • bigbluebutton-html5/imports/ui/services/audio-manager/index.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (1)
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (1)
  • RaisedHandUser (99-110)
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (1)
bigbluebutton-html5/imports/ui/Types/user.ts (1)
  • User (61-111)
bigbluebutton-html5/imports/ui/components/user-list/user-list-content/raised-hands/component.tsx (1)
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (2)
  • RaisedHandUsersSubscriptionResponse (112-114)
  • RAISED_HAND_USERS (116-134)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/container.tsx (1)
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (2)
  • RaisedHandUsersSubscriptionResponse (112-114)
  • RAISED_HAND_USERS (116-134)
bigbluebutton-html5/imports/ui/services/audio-manager/index.js (1)
bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx (1)
  • outputDeviceId (82-82)
bigbluebutton-html5/imports/ui/components/app/component.jsx (2)
bigbluebutton-html5/imports/ui/components/app/container.jsx (1)
  • isRaiseHandEnabled (97-97)
bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx (1)
  • isRaiseHandEnabled (95-95)
🔇 Additional comments (2)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/container.tsx (2)

25-31: Verify error handling behavior: should rendering continue after subscription failure?

The error handling correctly logs the issue and sets the connection status, but the component continues to render with an empty raiseHandUsers array. Please confirm whether this is the intended behavior or if the component should return null when the subscription fails.

If rendering should stop on error, apply this diff:

   if (usersError) {
     connectionStatus.setSubscriptionFailed(true);
     logger.error({
       logCode: 'raisehand_notifier_container_subscription_error',
       extraInfo: { usersError },
     }, 'Error on requesting raise hand data');
+    return null;
   }

1-10: LGTM: Clean component structure and proper hook usage.

The imports, subscription setup, and render logic are well-structured. The use of useCurrentUser with a selector function and useDeduplicatedSubscription with proper typing follows best practices. The boolean coercion with !! ensures type safety when passing props to the presentational component.

Also applies to: 13-23, 36-42

Comment on lines +64 to +87
const newRaisedHandUsers = raiseHandUsers.filter(
(user) => !previousUserIdsSet.has(user.userId),
);
const hasNewRaisedHand = newRaisedHandUsers.length > 0;
const isAllowed = isModerator || isPresenter;

if (hasNewRaisedHand && isAllowed) {
if (raiseHandAudioAlert) {
AudioService.playAlertSound(getRaiseHandSoundUrl());
}

if (raiseHandPushAlert) {
const nonCurrentUser = newRaisedHandUsers
.filter((user) => user.userId !== currentUserId);
const names = nonCurrentUser
.map((user) => user.name)
.filter((name): name is string => !!name && name.length > 0);
const message = buildRaisedHandMessage(names);

if (message) {
notify(message, 'info', 'hand');
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid audible alert for presenter self-raise

We're still playing the alert sound when the only new raised hand belongs to the current presenter. The PR objective calls out filtering presenter self-raises, so the audible notification should be skipped as well; otherwise the presenter hears the chime whenever they raise their own hand. Please filter the current user out before deciding to play the sound and reuse that filtered list for the toast names. Based on PR objectives.

-    const hasNewRaisedHand = newRaisedHandUsers.length > 0;
+    const newRaisedHandUsersExcludingSelf = newRaisedHandUsers.filter(
+      (user) => user.userId !== currentUserId,
+    );
+    const hasNewRaisedHand = newRaisedHandUsersExcludingSelf.length > 0;
@@
-      if (raiseHandAudioAlert) {
+      if (raiseHandAudioAlert && newRaisedHandUsersExcludingSelf.length > 0) {
         AudioService.playAlertSound(getRaiseHandSoundUrl());
       }
 
       if (raiseHandPushAlert) {
-        const nonCurrentUser = newRaisedHandUsers
-          .filter((user) => user.userId !== currentUserId);
-        const names = nonCurrentUser
+        const names = newRaisedHandUsersExcludingSelf
           .map((user) => user.name)
🤖 Prompt for AI Agents
In bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx
around lines 64 to 87, filter out the current user before deciding there are new
raised hands and reuse that filtered list for both the audible alert and the
push/toast names: compute newRaisedHandUsers as you do, then derive
nonCurrentNewRaisedHandUsers = newRaisedHandUsers.filter(u => u.userId !==
currentUserId); use nonCurrentNewRaisedHandUsers.length > 0 (together with
isAllowed) to decide whether to play the alert sound and to build the names for
the push notification so the presenter’s own raise is ignored for both sound and
toast.

Comment on lines +32 to +33
// @ts-ignore
const { raiseHandAudioAlerts, raiseHandPushAlerts } = useSettings(SETTINGS.APPLICATION);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove @ts-ignore and add proper typing.

The @ts-ignore directive suppresses TypeScript errors without explanation, which makes the code fragile and hides potential type issues. Please resolve the underlying type mismatch by either:

  • Adding proper type definitions to useSettings for the APPLICATION settings shape, or
  • Using a typed assertion with explicit interface definition

Apply this approach to properly type the settings:

-  // @ts-ignore
-  const { raiseHandAudioAlerts, raiseHandPushAlerts } = useSettings(SETTINGS.APPLICATION);
+  const settings = useSettings(SETTINGS.APPLICATION) as { 
+    raiseHandAudioAlerts?: boolean; 
+    raiseHandPushAlerts?: boolean; 
+  };
+  const { raiseHandAudioAlerts, raiseHandPushAlerts } = settings;

Or better yet, define a proper interface for APPLICATION settings and use it consistently across the codebase.

📝 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
// @ts-ignore
const { raiseHandAudioAlerts, raiseHandPushAlerts } = useSettings(SETTINGS.APPLICATION);
const settings = useSettings(SETTINGS.APPLICATION) as {
raiseHandAudioAlerts?: boolean;
raiseHandPushAlerts?: boolean;
};
const { raiseHandAudioAlerts, raiseHandPushAlerts } = settings;
🤖 Prompt for AI Agents
In bigbluebutton-html5/imports/ui/components/raisehand-notifier/container.tsx
around lines 32-33, remove the `// @ts-ignore` and fix the typing by declaring
an interface for the APPLICATION settings (including raiseHandAudioAlerts and
raiseHandPushAlerts) and use that interface with useSettings (or pass it as a
generic) so the returned object is correctly typed; update any related type
exports so the APPLICATION shape is reused across the codebase and replace the
ignored line with a typed call or a typed assertion to eliminate the ts-ignore.

@Tainan404
Copy link
Member Author

@codex

@antobinary antobinary added this to the Release 3.0 milestone Nov 10, 2025
@gustavotrott
Copy link
Collaborator

IMO it's not necessary to include the user's name here!

image

@ramonlsouza ramonlsouza self-requested a review November 11, 2025 12:03
Copy link
Member

@ramonlsouza ramonlsouza left a comment

Choose a reason for hiding this comment

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

We're still playing the alert sound when the only new raised hand belongs to the current presenter. The PR objective calls out filtering presenter self-raises, so the audible notification should be skipped as well

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (1)

76-77: Remove redundant filtering after fixing line 66.

After fixing the filter at line 66 to exclude the current user, these lines become unnecessary since newRaisedHandUsers already excludes currentUserId.

Apply this diff to remove the redundant filter:

 if (raiseHandPushAlert) {
-  const nonCurrentUser = newRaisedHandUsers
-    .filter((user) => user.userId !== currentUserId);
-  const names = nonCurrentUser
+  const names = newRaisedHandUsers
     .map((user) => user.name)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4adb99 and c34d970.

📒 Files selected for processing (3)
  • bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (1 hunks)
  • bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx (1 hunks)
  • bigbluebutton-html5/public/locales/en.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-15T17:52:37.432Z
Learnt from: guiiBecker
Repo: bigbluebutton/bigbluebutton PR: 24084
File: bigbluebutton-html5/public/locales/pt.json:323-323
Timestamp: 2025-10-15T17:52:37.432Z
Learning: Translation issues in locale files (bigbluebutton-html5/public/locales/*.json) should be updated in Transifex, the translation management platform used by the BigBlueButton project, rather than being fixed directly in pull requests.

Applied to files:

  • bigbluebutton-html5/public/locales/en.json
🧬 Code graph analysis (1)
bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (1)
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts (1)
  • RaisedHandUser (99-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-package (bbb-html5)
🔇 Additional comments (6)
bigbluebutton-html5/public/locales/en.json (1)

717-717: LGTM! Proper placement and naming.

The new translation key app.actionsBar.reactions.lowUserHand is well-placed within the reactions namespace and follows the existing convention established by app.actionsBar.reactions.lowHand on the previous line.

bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx (1)

160-160: Verify translation key synchronization across locale files.

The old translation key app.statusNotifier.lowerHandDescOneUser is not referenced in code and has been successfully replaced. However, the old key remains in all 30+ locale files, while the new key app.actionsBar.reactions.lowUserHand is currently only defined in en.json. Per the project's guidelines, translation management should be coordinated through Transifex rather than updated directly in pull requests. Ensure this translation change is properly synchronized across all locale files through the Transifex platform.

bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx (4)

1-14: LGTM!

Imports and type definitions are well-structured. The shared RaisedHandUser type promotes consistency across the codebase.


16-25: LGTM!

Internationalization setup correctly handles singular and plural cases.


27-53: LGTM!

Helper functions are well-implemented. The message builder correctly handles empty, singular, and plural cases.


99-102: LGTM!

Returning null is appropriate for a notification-only component with side effects.

Comment on lines +64 to +66
const newRaisedHandUsers = raiseHandUsers.filter(
(user) => !previousUserIdsSet.has(user.userId),
).filter((user) => isPresenter && user.userId !== currentUserId);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Broken filter logic prevents notifications for non-presenters.

Line 66 uses isPresenter && user.userId !== currentUserId which is incorrect. The isPresenter boolean refers to whether the current user is a presenter, not the user being filtered. When the current user is not a presenter (e.g., a moderator-only), the condition evaluates to false for all users, removing everyone from newRaisedHandUsers and completely disabling notifications.

Apply this diff to filter out only the current user's own raise:

 const newRaisedHandUsers = raiseHandUsers.filter(
   (user) => !previousUserIdsSet.has(user.userId),
-).filter((user) => isPresenter && user.userId !== currentUserId);
+).filter((user) => user.userId !== currentUserId);

This relates to the past review comment which suggested filtering presenter self-raises, but the implementation introduced a logic error.

📝 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
const newRaisedHandUsers = raiseHandUsers.filter(
(user) => !previousUserIdsSet.has(user.userId),
).filter((user) => isPresenter && user.userId !== currentUserId);
const newRaisedHandUsers = raiseHandUsers.filter(
(user) => !previousUserIdsSet.has(user.userId),
).filter((user) => user.userId !== currentUserId);
🤖 Prompt for AI Agents
bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx lines
64-66: the filter currently uses "isPresenter && user.userId !== currentUserId"
which wrongly ties per-user filtering to whether the current user is a
presenter, causing all users to be filtered out when the current user is not a
presenter; replace that filter so it only excludes the current user's own raise
(i.e., drop the isPresenter check and filter by user.userId !== currentUserId)
so notifications for other users work regardless of the current user's presenter
status.

Comment on lines +90 to +97
}, [
intl,
isModerator,
isPresenter,
raiseHandAudioAlert,
raiseHandPushAlert,
raiseHandUsers,
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add missing dependency to useEffect.

The currentUserId is used within the effect (lines 66, 77) but is not included in the dependency array. This could cause stale closure issues if currentUserId changes.

Apply this diff to add the missing dependency:

 }, [
+  currentUserId,
   intl,
   isModerator,
📝 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
}, [
intl,
isModerator,
isPresenter,
raiseHandAudioAlert,
raiseHandPushAlert,
raiseHandUsers,
]);
}, [
currentUserId,
intl,
isModerator,
isPresenter,
raiseHandAudioAlert,
raiseHandPushAlert,
raiseHandUsers,
]);
🤖 Prompt for AI Agents
In bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.tsx
around lines 90 to 97, the useEffect depends on currentUserId (used at lines ~66
and ~77) but currentUserId is missing from the dependency array; update the
dependency array to include currentUserId so the effect re-runs when it changes,
ensuring closures see the latest value.

@github-actions
Copy link

Automated tests Summary

All the CI tests have passed!

@ramonlsouza ramonlsouza merged commit 475da5f into bigbluebutton:v3.0.x-release Nov 11, 2025
34 checks passed
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.

A few tweaks to Raise Hand's new look

4 participants