The toolbar's onFocus handler called focusManager.focusFirst() whenever
focus arrived from outside the toolbar, which included clicks on
reaction buttons. This caused focus (and the scroll viewport) to snap
back to the first button on every click.
Use :focus-visible to distinguish keyboard focus from pointer focus.
Only redirect to the first button when focus arrives via keyboard,
leaving click-induced focus untouched.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Render the notification region from the main window into the PiP
document via a portal, rather than duplicating it. This keeps a
single listener, a single store, and a single component instance —
so notifications stay consistent across both windows without extra
synchronization.
Simplest approach I could find. Good enough as a first pass;
worth revisiting if the requirements grow.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The core component of the PiP layout. Adapted from a simplified version
of @ovgodd's work in #890 — see that PR for context on the original design.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Adding a menu turned out to be tricky. Our usual menu primitive
is built on react-aria, which positions overlays based on the
`window` object. Since the JS runs in the main window, the
computed position refers to the main window's coordinates, and
the menu renders in the wrong place inside the PiP document.
After investigating, we couldn't find a clean way to make
react-aria target the PiP window's `window`/`document` without
significant rework. Agreed with @ovgodd to duplicate the menu in
the PiP window as a pragmatic workaround until a better approach
emerges.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP control bar shares state with the main window's control
bar — most importantly, the reaction toolbar and the mic/camera
toggles, which are fully stateful.
Sharing state works naturally here because the PiP content is
rendered through a portal into the same React tree, so the
controls in both windows read from and write to the same stores.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The main window computes a JS offset to align the reaction toolbar
with the (off-center) reaction toggle. PiP and mobile don't need
this — centering the toolbar is enough. Add `adjustedCentering` to
opt out.
Not a great seam: the component shouldn't know about its host.
A cleaner fix would move the alignment concern to the parent.
Leaving for now to unblock PiP.
Co-authored-by: Cyril <c.gromoff@gmail.com>
When the PiP window is active, replace the corresponding content
in the main window with a lightweight placeholder. This avoids
rendering the camera feed (and other expensive media) twice when
the user is only watching the PiP window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Add a hook that manages the logic needed to open a document
picture-in-picture window, stores a ref to the window in a global
store, and—once ready—mounts a portal to duplicate content into
the PiP document's container.
Mounting the PiP content via a portal lets us share the same React
tree as the main app, and therefore share application state. This
comes with some trade-offs, particularly around components that
rely on the DOM hierarchy: react-aria popovers and overlays may
not behave correctly across documents.
The logic is kept to a minimum here. This first commit does nothing
more than open a window containing a loading spinner. The topic is
new to us, so plenty is likely to be refined in follow-ups.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Refine keyboard behavior so left and right arrow keys no longer
control toolbar navigation, allowing users to directly tab into
individual reaction buttons for faster and more intuitive access.
Remove the reaction toolbar from accessibility tree to avoid
unnecessary focus and announcements, keeping assistive technology
focused on the actual reaction controls.
This ensures the UX centers on reaction actions rather than
auxiliary navigation elements.
Also, not related to this topic, I've reworked the scroll viewport styles.
Implement the missing disabled state styling for the
primaryDarkText button variant to ensure consistent UI feedback
when the button is not interactive.
Allow reaction interactions on mobile.
Adding the toolbar toggle currently impacts the responsive layout
of the control bar on very small screens. This will be improved in
a future PR with an auto-collapsing mobile control bar.
Replace the previous hardcoded offset approach with a dedicated
hook that dynamically centers the reaction toolbar relative to the
reaction toggle.
This improves layout accuracy and removes reliance on fixed values.
Note: ResizeObserver is used for positioning updates and may not
be supported in older browsers. Additional testing will be
performed before production release.
Replace direct DOM queries using hardcoded IDs with proper constants.
Add horizontal navigation support using left/right arrow controls
to scroll through available reactions on smaller screens.
This work is inspired by Cyril's implementation.
Rename strip-related components into a more generic container
abstraction.
Extract the buttons container into a dedicated component file and
update related naming to improve readability and maintainability.
Fix unreachable code when notifying participants that they were
muted.
Prevent unnecessary function re-creations when props remain
unchanged.
Also guard against missing tokens by logging an error and
returning early when the token is undefined.
Extend the existing live synchronization mechanism beyond room
configuration to also include lobby access level changes.
This ensures that all owners and admins sharing a room maintain a
consistent and up-to-date view of room state in the frontend,
including configuration and access control updates.
Listen to room metadata change events and synchronize the React
Query cache with the latest room data fetched from the API.
This ensures clients react to live configuration updates, such as
showing or hiding mute controls when `everyone_can_mute` changes.
Simplify source serialization and validation logic while improving
type safety around room configuration handling.
Introduce a dedicated TypeScript type matching the backend
Pydantic model more precisely.
Also harmonize track source casing between frontend and backend to
remove redundant conversion logic and resolve#1282.
Introduce a new room setting controlling whether all participants,
including non-privileged users, can mute others.
Update API validation accordingly and add the frontend controls
allowing administrators to toggle the option and persist the
configuration through the API.
Allow non-privileged users to mute others when the
everyone_can_mute configuration is unset or true.
This setting is not yet customizable by room owners and will be
introduced in a future update.
Pass the LiveKit token when calling the mute-participant endpoint
to authenticate the request.
This enables non-authenticated participants to mute others through
the API while preserving proper authorization checks.
Fix compatibility issues with the DINUM frontend image, which
overrides the default `font-sans` value.
Simplify the implementation by having the JavaScript layer only
toggle well-scoped CSS classes responsible for accessibility font
overrides. This makes the behavior more predictable and restoring
default styles straightforward.
Also clarify the intent of the hook by making its accessibility
purpose explicit and moving its usage to the App component, where
it better fits the application lifecycle.
Room identifiers are created with `Math.random()`, which is predictable
and not suitable for security-sensitive identifiers. Predictable
room IDs increase the risk of room enumeration and unauthorized
access attempts, especially when IDs are part of join URLs.
Affected files: generateRoomId.ts
Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
Fixes#1126 - Inconsistent role terminology in localization files.
Standardize on 'host' as the primary role term across en, de, and nl
locales, replacing mixed usage of 'administrator', 'organizer', 'admin',
'Organisator:in', 'Organisierende', and 'organisator'.
Updating connectionObserverSnapshot triggered page re-renders,
causing participants to reconnect due to a race condition.
Read directly from the underlying store instead of using the
snapshot to avoid unnecessary re-renders.
Ensure screen recording is properly disabled when the feature
flag is set to false.
Previously, no feature flag was enforced, allowing unintended
access.
Also update the blocked-access message to be more generic and not
specific to public sector users.
Provide the minimal components required to support an Outlook
add-in: user authentication, JWT retrieval, and API calls to
generate meeting links.
This implementation is an early alpha: developer experience is
limited, documentation is incomplete, and the solution is not
white-labeled.
It's too early to consider these parts ready to ship into
production.
As a result, it is currently only available within the DINUM
frontend image.
A 1–7 scale is not commonly used in software products. Feedback
from both users and the support team suggests reducing the number
of options to simplify usage and analysis.
Adopting a 1–5 scale improves usability and makes responses
easier to interpret and process.
The scale has to be odd.
Allow controlling when the metadata collector agent is started,
enabling users to try the feature and disable it if needed.
Introduce a user-level feature flag to toggle the agent for the
initial release.
Capture selected ICE candidates for both subscriber and publisher
peer connections.
This enables correlation between survey feedback and connectivity
setup, helping identify problematic network configurations.
Bump the base image to Alpine 3.23 to resolve most vulnerabilities
reported by Cyberwatch and Trivy.
Remaining issues require manual updates:
- musl / musl-utils: upgrade to 1.2.5-r11 (CVE-2026-40200)
- zlib: upgrade to 1.3.2-r0 (CVE-2026-22184)
Replace costly PostHog surveys with basic surveys, which better
fit our headless usage and avoid short data retention limits.
Enhance emitted events with additional metadata, including a
unique session ID and room ID, to correlate survey responses with
specific sessions.
This lays the groundwork for further enrichment with participant
connection data.
Replace passing the LiveKit JWT in the request body with the
Authorization header, following standard authentication practices.
Extend the LiveKit authentication backend usage across additional
endpoints.
This also raises questions about how clients should securely
retrieve LiveKit tokens, to be addressed later.
Introduce toggle-hand and rename endpoints in RoomViewSet,
secured with LiveKit token authentication.
Remove direct permission for clients to update their own metadata
via LiveKit tokens to prevent spoofing (e.g. faking admin status).
Proxy participant metadata updates through the backend to enforce
proper validation and authorization.
Signed-off-by: lebaudantoine <lebaud.antoine131@gmail.com>