Skip to content

VIDSOL-619: Mirror Self View Toggle#390

Closed
czoli1976 wants to merge 8 commits intoVonage:developfrom
czoli1976:czoli1976/VIDSOL-619-mirror-self-view-toggle
Closed

VIDSOL-619: Mirror Self View Toggle#390
czoli1976 wants to merge 8 commits intoVonage:developfrom
czoli1976:czoli1976/VIDSOL-619-mirror-self-view-toggle

Conversation

@czoli1976
Copy link
Copy Markdown
Contributor

@czoli1976 czoli1976 commented Mar 1, 2026

Hi team! 👋

Resolves VIDSOL-619

Checklist

  • Branch is based on develop (not main).
  • Resolves a Known Issue.
  • If yes, did you remove the item from the docs/KNOWN_ISSUES.md?
  • Resolves an item reported in Issues.

What is this PR doing?

Adds a Mirror Self View toggle so users can choose whether their preview is mirrored. Default is ON (mirrored) and
persisted in localStorage.

Where it appears:

  • Waiting room: flip icon button on the self-view tile
  • Meeting room: toggle item in the camera device dropdown

Mirror behavior:

  • mirrorSelfView = true: use the SDK’s .OT_mirrored CSS when present; if the SDK isn’t mirroring, fall back to
    scaleX(-1) inline.
  • mirrorSelfView = false: force scaleX(1) to cancel any mirroring (SDK or inline).

Styling refactor:

  • Mirror controls and device menu now use Tailwind with Vera tokens (no MUI sx).

Manual test notes remain the same; ensure toggles update immediately and persist on refresh across browsers.

Mirror logic:

mirrorSelfView Transform applied
true (default) none — the SDK's .OT_mirrored class handles the flip
false scaleX(-1) — cancels the SDK mirror

Changes:

  • storage.ts — Added MIRROR_SELF_VIEW key
  • user.tsx — Added mirrorSelfView: boolean to UserContext / UserProvider, loaded from localStorage
  • MirrorSelfViewButton.tsx (new) — Waiting room icon button
  • MirrorSelfViewToggle.tsx (new) — Conference room toggle menu item
  • VideoContainer.tsx — Replaced static Tailwind child:-scale-x-100 with reactive useEffect; added MirrorSelfViewButton
  • Publisher.tsx — Dynamic transform via useEffect; objectFit: cover
  • BackgroundVideoContainer.tsx — Dynamic transform + objectFit: cover
  • en.json, es.json, es-MX.json, it.json — Added devices.video.mirrorSelfView i18n key
  • user.spec.tsx, BackgroundVideoContainer.spec.tsx, DeviceSettingsMenu.spec.tsx — Updated tests

How should this be manually tested?

Waiting room:

  1. Open the app and enter the waiting room
  2. Locate the flip icon button in the upper-right corner of the self-view video tile
  3. Click it — your self-view should switch from mirrored to un-mirrored (and back)
  4. Refresh the page and verify the last setting is remembered

Conference room:

  1. Join a call and click the camera device dropdown (chevron next to the camera button)
  2. Verify "Mirror Self View" toggle is visible at the bottom of the dropdown
  3. Click it — your publisher video should update immediately
  4. Verify the toggle is visible in Safari and Firefox (not gated by video effects support)

Tested on macOS Tahoe 26.3 (arm64):

Browser Waiting room toggle Conference room toggle Persistence
Chrome 145
Safari 26.3
Firefox 148.0 ✅ (camera list empty — pre-existing bug, see comments)

Instructions for Testing

Setup:

  • Use Node
    22.
  • Backend .env set (provider + keys) so sessions start.

Waiting room:

  1. Load waiting room.
  2. Click mirror button (top-right of self-view).
    - ON → video mirrored. OFF → unmirrored.
  3. Refresh: last setting persists.
  4. Ensure button hover/focus uses Vera tokens (no default MUI colors).

Meeting room:

  1. Join a call, open camera device dropdown.
  2. Toggle “Mirror Self View”.
    - ON → mirrored; OFF → unmirrored.
  3. Switch the toggle multiple times; video updates immediately.
  4. Verify dropdown styling matches Tailwind/Vera tokens.

Fallback:

  • If SDK doesn’t apply .OT_mirrored, ON still mirrors via scaleX(-1); OFF forces scaleX(1).

Cross-browser:

  • Chrome, Safari, Firefox: confirm toggle visibility and behavior; Safari/Firefox should show the dropdown item.

Persistence:

  • Toggle in meeting room, rejoin or reload; setting remains.

Regression:

  • Background effects preview respects mirror toggle.
  • Publisher/preview still object-fit cover (no letterboxing).

@czoli1976 czoli1976 force-pushed the czoli1976/VIDSOL-619-mirror-self-view-toggle branch from 02fd3f4 to fe0648f Compare March 2, 2026 18:37
@czoli1976 czoli1976 force-pushed the czoli1976/VIDSOL-619-mirror-self-view-toggle branch from fe0648f to e1d35ce Compare March 8, 2026 10:04
Copilot AI review requested due to automatic review settings March 8, 2026 10:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a persisted Mirror Self View user preference and exposes it in both the waiting room (icon button) and the meeting room device settings menu, updating publisher/preview video styling accordingly.

Changes:

  • Added a new localStorage key + UserContext state for mirrorSelfView (defaulting to mirrored).
  • Introduced UI controls to toggle the setting in the waiting room and in the camera device dropdown.
  • Updated publisher/preview video containers to apply mirroring based on the new setting, plus i18n and test updates.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/src/utils/storage.ts Adds MIRROR_SELF_VIEW storage key.
frontend/src/Context/user.tsx Loads/persists mirrorSelfView into UserContext default settings.
frontend/src/components/WaitingRoom/VideoContainer/VideoContainer.tsx Applies dynamic mirroring/objectFit and adds waiting room toggle button.
frontend/src/components/WaitingRoom/MirrorSelfViewButton/index.tsx Barrel export for the new waiting room toggle button.
frontend/src/components/WaitingRoom/MirrorSelfViewButton/MirrorSelfViewButton.tsx Implements waiting room mirror toggle + persistence.
frontend/src/components/Publisher/Publisher.tsx Applies dynamic mirroring to in-call publisher rendering.
frontend/src/components/BackgroundEffects/BackgroundVideoContainer/BackgroundVideoContainer.tsx Applies dynamic mirroring to background preview video element.
frontend/src/components/MeetingRoom/MirrorSelfViewToggle/MirrorSelfViewToggle.tsx Implements meeting room dropdown toggle + persistence.
frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.tsx Renders the new mirror toggle in the video settings dropdown.
frontend/src/locales/en.json Adds i18n label for “Mirror Self View”.
frontend/src/locales/es.json Adds i18n label for “Mirror Self View”.
frontend/src/locales/es-MX.json Adds i18n label for “Mirror Self View”.
frontend/src/locales/it.json Adds i18n label for “Mirror Self View”.
frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.spec.tsx Updates tests to account for mirror toggle (and new provider setup).
frontend/src/components/BackgroundEffects/BackgroundVideoContainer/BackgroundVideoContainer.spec.tsx Wraps tests with UserProvider due to new useUserContext dependency.
frontend/src/Context/tests/user.spec.tsx Updates default settings expectations with mirrorSelfView.
frontend/src/Context/PublisherProvider/usePublisherQuality/usePublisherQuality.spec.tsx Updates mocked UserContext default settings with mirrorSelfView.

You can also share your feedback on Copilot code review. Take the survey.

@czoli1976
Copy link
Copy Markdown
Contributor Author

Hi @johnny-quesada-developer @OscarFava 👋

This PR is ready for your first review. All Copilot review comments have been addressed (fixed removeProperty vs 'none' for mirror transform, cleaned up unused isSMViewport dep, fixed test provider setup). CI is green ✅

Would love your eyes on it when you have a chance! 🙏

Copy link
Copy Markdown
Contributor

@OscarFava OscarFava left a comment

Choose a reason for hiding this comment

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

Does it have sense to have a button to mirror? I think it should be mirrored by default and we can allow users to change it with the environment variables.

</>
)}
<DropdownSeparator />
<MenuList sx={{ display: 'flex', flexDirection: 'column', mt: 1 }}>
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.

Could you use tailwind? Please, ensure that it is visually correct, sometimes AI does not translate MUI to tailwind correctly.

};

return (
<Box
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.

Same here, tailwind and remember to use our tokens, not default colors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this should be addressed

if (!publisherVideoElement) return;

// eslint-disable-next-line react-hooks/immutability
publisherVideoElement.style.objectFit = 'cover';
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.

Why that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We set object-fit: cover because the SDK injects the preview video with object-fit: contain, which leaves letterboxing
inside our 16:9 tile. Cover fills the tile consistently; the inline set is necessary because the SDK owns the element.

Can remove that if you do not think it is appropriate

* Follows the same pattern as the Advanced Noise Suppression toggle in the audio menu.
* @returns {ReactElement} The MirrorSelfViewToggle component.
*/
const MirrorSelfViewToggle = (): ReactElement => {
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.

Does it have sense to have a button to mirror? I think it should be mirrored by default and we can allow users to change it with the environment variables.

@czoli1976
Copy link
Copy Markdown
Contributor Author

Hi @OscarFava, thanks for the question! 🙏

The feature actually has a user-first design rationale:

  • Mirror is ON by default — users see themselves mirrored (the natural "looking in a mirror" experience) right away, with no setup needed
  • The toggle lets users consciously turn it off — some users need to read text on their screen or present documents where the mirrored view is confusing; in those cases they need a real-time UI control, not an env var restart

This is consistent with how mainstream video apps work (Teams, Zoom, Meet) — all expose a per-session mirror toggle.

An env var would only work for operators deploying the app, not for end-users who want to flip it mid-call. It would also require a page reload to take effect, which is disruptive.

Happy to discuss further — but I think the toggle is the right call here. What do you think?

@VZaphod
Copy link
Copy Markdown
Contributor

VZaphod commented Mar 9, 2026

Hi @czoli1976, I see that this changes the UI. We should first align with the when a PR touches the UI in a non trivial way.

@czoli1976
Copy link
Copy Markdown
Contributor Author

Hi @VZaphod, thanks for flagging this! 🙏

Totally agree — happy to pause and align before proceeding. Could you point me to the right process or channel for UI alignment? I want to make sure the right people have visibility before this moves forward.

@czoli1976
Copy link
Copy Markdown
Contributor Author

@johnny-quesada-developer Friendly ping 👋 — this PR has been open for 16 days and is waiting for your initial review. CI is green, all Copilot bot feedback addressed. Happy to walk through the implementation if helpful! 🙏

czoli1976 and others added 6 commits March 17, 2026 16:09
Adds a user-toggleable mirror (horizontal flip) for the self-view video.
Previously, the self-view was always mirrored via scaleX(-1). Now users
can toggle mirroring on/off, and the preference persists in localStorage.

The toggle is available in two places:
- Waiting Room: a badge-style Flip icon button in the upper-right corner
  of the video tile (aligned with the Background Effects badge)
- Conference Room: a toggle menu item in the camera device dropdown,
  always visible on all browsers (not gated by media processor support),
  following the same pattern as Advanced Noise Suppression in the audio menu

Key design decisions:
- Default: mirrored (true) — preserves existing behaviour
- Inverted transform logic: SDK mirrors via .OT_mirrored; when mirror is
  ON we set transform:none, when OFF we apply scaleX(-1) to cancel it
- objectFit changed from contain to cover across all three video components
- Preference stored as localStorage key mirrorSelfView
- i18n keys added for en, es, es-MX, it locales
- Fixed pre-existing no-floating-promises lint error in useSuspenseUntilAppConfigReady

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add mirrorSelfView: true to user.spec.tsx expected defaultSettings
- Add UserProvider wrapper to BackgroundVideoContainer.spec.tsx (component now uses useUserContext)
- Update DeviceSettingsMenu.spec.tsx: add providers.user and userContext to RenderOptions, use queryAllByTestId for dropdown-separator (two separators now rendered)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove providers.appConfig (does not exist in current codebase)
- Use providers.user only for MirrorSelfViewToggle context
- Restore env.partialUpdate for ALLOW_BACKGROUND_EFFECTS test
- Remove appConfigContext from RenderOptions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ainer

- Replace transform:'none' with removeProperty/'' so SDK's .OT_mirrored
  CSS (scale(-1,1) on .OT_video-element) is not inadvertently overridden
  when mirror is ON
- Use scaleX(1) when mirror is OFF to correctly cancel SDK's scale(-1,1)
- Remove unused isSMViewport hook and dep from BackgroundVideoContainer effect

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…line

The react-hooks/immutability rule fires on direct property assignments
(`element.style.transform = 'scaleX(1)'`) but not on method calls
(`removeProperty`) or ternary assignments. The disable comments were
placed on the wrong lines — moved to sit immediately above the
assignment that actually triggers the rule, and removed the now-unused
disable in BackgroundVideoContainer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@czoli1976 czoli1976 force-pushed the czoli1976/VIDSOL-619-mirror-self-view-toggle branch from 1dcb6e3 to 1ec7de8 Compare March 17, 2026 16:10
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@czoli1976 czoli1976 force-pushed the czoli1976/VIDSOL-619-mirror-self-view-toggle branch from 3927b7b to b6d6b23 Compare March 17, 2026 16:32
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@OscarFava
Copy link
Copy Markdown
Contributor

OscarFava commented Mar 18, 2026

Hi @czoli1976, I see that this changes the UI. We should first align with the when a PR touches the UI in a non trivial way.

Hello, @czoli1976 We are still pending UI review as I DO NOT approve this PR design (Does not seem to match with UI previous figma). So, please, do not ping the team to review before solving the design. Thanks.

@VZaphod
Copy link
Copy Markdown
Contributor

VZaphod commented Mar 18, 2026

vcr:deploy

@github-actions
Copy link
Copy Markdown
Contributor

❌ Deployment to VCR Failed

Commit: 635d622c33dd849542ff87aadfbe5df0c32c999d

View build logs

@czoli1976
Copy link
Copy Markdown
Contributor Author

vcr:deploy

@github-actions
Copy link
Copy Markdown
Contributor

❌ Deployment to VCR Failed

Commit: 635d622c33dd849542ff87aadfbe5df0c32c999d

View build logs

@czoli1976
Copy link
Copy Markdown
Contributor Author

vcr:deploy

@github-actions
Copy link
Copy Markdown
Contributor

❌ Deployment to VCR Failed

Commit: 635d622c33dd849542ff87aadfbe5df0c32c999d

View build logs

@czoli1976
Copy link
Copy Markdown
Contributor Author

Closing in favor of #414 (same branch pushed to upstream to enable VCR deploy)

@czoli1976 czoli1976 closed this Mar 18, 2026
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.

4 participants