Install tooling to visualize the Rollup bundle output. It helps
understand precisely what composes our bundle and what is included in
each chunk, which is a great help when trying to enhance code
splitting.
You can see its output running npm run build:debug.
Note: the library is not compatible with Node 20, which is the build
image we use in Docker to build the frontend, so it will raise a
warning in CI. This does not matter much, as it is a dev-only
dependency used locally without Docker.
Mark JS imports as type-only when applicable so they are stripped at
build time and ignored during chunk splitting, ensuring we take full
advantage of Rollup's code-splitting optimizations.
Following the ProConnect documentation leads to inlining almost 40ko of
SVG assets in the JS bundle. Moreover, each SVG asset (around 20ko) is
not optimized in size.
As the ProConnect button is loaded in the main index.js chunk at app
launch, these few Ko are critical.
Optimize index.js by 40ko and preload the hovered variant to avoid any
blink on hover.
Could be further optimized by handling the button background with CSS,
but leaving that as an improvement for later.
Inspired by commit eb23aef in suitenumerique/docs. The same base
route path is used everywhere, which helps when the backend sits
behind an ingress serving it with a regex like `api/*`.
Ensure the meet-backend createsuperuser and migrate jobs run only
once and do not rebuild the image by default. When the backend
updates, resources should focus on updating the backend pod itself.
Align enabled/disabled mature features across the dev environments.
Also reorganize backend env variables with a clearer scope and add
comments that help understand the responsibility of each setting and
why it exists.
Maintaining the two dev stacks is a nightmare. Start factorizing them
to make sure they stay updated easily, and to highlight differences
between the two at a glance.
Document the changes introduced in #128333, which allows passing
configuration to the external API when creating a room.
Warning: this documentation was generated in a rush using an LLM and
may contain minor errors.
Plan to factorize these YAML files into a common and shared
structure.
The securityContext and podSecurityContext were harming hot reloading
on the dev stack. The file permission system prevented source updates
because the user running the image was not root.
Allow passing configuration and access level when creating a room
through the external API.
Also add a few guardrails:
* control whether public rooms are accepted on the external API
* set the default access level when creating a new room
Ensure the new room configuration and access level are returned by
the serializer when listing rooms.
Surface connection state changes (reconnecting, disconnected,
etc.) directly in the PiP window so the user stays informed
without needing to switch back to the main window.
Co-authored-by: Cyril <c.gromoff@gmail.com>
Replace the hard-coded tooltip offset with a semantic CSS token,
making it possible to override the spacing per context (e.g.
tighter spacing in the PiP window where vertical room is scarce).
Co-authored-by: Cyril <c.gromoff@gmail.com>
react-aria computes overlay positions against the main window's
dimensions, which produces incorrect placement when the overlay
is rendered into a PiP document. Tooltips were the most visible
symptom, but the issue affects overlays in general.
Introduce a context that lets our primitives know when they're
rendering across documents. When set, the primitives use the
host document's window for positioning instead of falling back
to react-aria's default.
Original work by @ovgod. No cleaner approach seems feasible in
the short term, react-aria doesn't expose a clean way to
override the positioning target, so this works around it at our
primitive layer.
Co-authored-by: Cyril <c.gromoff@gmail.com>
The PiP window can be resized to a wide range of dimensions, so
the control bar needs to adapt. Introduce a collapsible mechanism
that hides or condenses controls as available width shrinks,
keeping the UX usable at small sizes.
Original works by @ovgdd
Co-authored-by: Cyril <c.gromoff@gmail.com>
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.
Ensure the participant requesting a mute action is still present
in the room before processing the request.
This mitigates scenarios where a previously issued token could be
reused after the meeting has ended.
Current token lifetime is intentionally long-lived and will be
refactored in the future to better align with LiveKit session
constraints. In the meantime, add this extra validation step to
reduce the attack surface.
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.
Update room serialization to include room configuration for all
users fetching the API response, not only room owners.
This behavior was inherited from the original upstream project.
At the moment, exposing this configuration does not appear to
introduce meaningful security concerns or provide attackers with
additional capabilities.
The decision will continue to be reviewed from a security
perspective, but sharing the configuration improves frontend
consistency and synchronization.
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 synchronization of room configuration changes across
active participants.
When a room configuration is updated through a PUT operation, the
backend now performs an additional LiveKit API call to notify room
participants through a room metadata update event.
This ensures admins and owners quickly see up-to-date settings in
their administration panel. It also prepares the frontend for
automatic updates of unprivileged participants room’s data without
refetching it from the API.
An event-driven design was chosen instead of storing the full room
configuration in LiveKit metadata. While embedding the state
directly in metadata would provide immediate synchronization, it
would also require initializing and maintaining configuration
state during room creation or webhook handling, increasing the
risk of operational failures and regressions.
Instead, the backend emits lightweight synchronization events and
active clients update their React Query cache, which remains the
single source of truth for room configuration data.
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.
Enable any participant to mute others when the room configuration
allows it. This is enabled by default for all meetings unless
explicitly disabled by an administrator.
Privileged users retain the ability to mute any participant
regardless of the room configuration.