Split the sub-components currently declared inside ParticipantTile
into dedicated files.
This makes the ParticipantTile file easier to read and lets each
sub-component be imported and reasoned about on its own.
Align how the participant name is retrieved and rendered inside the
ParticipantTile, so the different code paths use a single consistent
approach instead of a mix of ad hoc logic.
Stop using the useSize hook to compute the Avatar size in JS. Rely
on CSS to size the Avatar responsively instead.
This removes a set of unnecessary re-renders triggered by the
useSize subscription every time the container resized.
Stop using the useSize hook to compute the Avatar size in JS. Rely
on CSS to size the Avatar responsively instead.
This removes a set of unnecessary re-renders triggered by the
useSize subscription every time the container resized.
Apply the same pattern as previous layouts: push state and useSize
subscriptions down into child components.
The CarouselLayout now only re-renders when maxVisibles or
orientation actually change, instead of on every size update.
Move the useSize subscription into a dedicated child component of
MoreOptions.
The options now only re-render when they actually need to collapse,
instead of on every size change of the container.
Move the useSize observer into a leaf component of the GridLayout
tree. That leaf then propagates size changes back into the
GridLayout state through a callback.
This prevents every size change from re-rendering the whole
GridLayout tree, keeping the re-render local to the leaf that
actually observes the size.
Cut down the number of unnecessary re-renders happening in the
participant panel by narrowing subscriptions and isolating state to
the components that actually depend on it.
The Avatar was rendered as an image, which made the initials
centering unreliable across sizes and browsers.
Render the Avatar as an SVG instead, which allows the initials to be
properly centered by construction.
Align LowerAllHandsButton with the MuteEveryoneButton pattern by
wrapping it with the AdminOrOwnerOnly component.
This prevents mounting its hooks and rendering its logic for users
who are not admin or owner, instead of relying on an internal check
that still ran on every re-render.
Extract the participant count out of the participant badge into a
dedicated, optimized component.
This isolates the subscription to the participant count so it no
longer triggers a re-render of the whole toggle when the count
changes.
Reorganize all participant list related code elements into a
dedicated feature folder, so related components, hooks and helpers
are grouped together and easier to locate.
- Dissociate the chat observer (which persists messages in a store)
from chat rendering.
- Do not keep the chat mounted in the DOM anymore.
- Recompute the minimal amount of data when a participant renames.
- Memoize most of the layout.
- Drop the manual textarea row-size computation: recent React Aria
already handles it.
Known regressions still to solve: focus behavior on the input,
scroll behavior on message updates, and edge cases around list
sizing.
Not sure yet whether there is a better way to memoize the
virtualized list; open to feedback.
Memoize the ActiveSpeaker component used in the push-to-talk
component so it does not re-render on unrelated parent updates when
its props have not changed.
Reorganize all chat-related code elements into a dedicated feature
folder, so chat components, hooks and helpers are grouped together
and easier to locate.
Subscribing to the whole local participant caused re-renders on
every local participant update, even when the consumer only cared
about a specific attribute.
Narrow the subscription so only the relevant attributes trigger a
re-render.
Move the video resolution subscription down into a lower component
so it no longer re-renders the whole Videoconference component on
every resolution change.
The overall approach still needs validation, but this already avoids
the top-level re-render and is a clear improvement over the current
behavior.i
canPublishTrack was mistakenly subscribing to the whole local
participant changes, so every component using this hook re-rendered
on any local participant update, not only on permission changes.
Fix it to subscribe only to the permission attributes.
Restrict the full-screen warning to the local participant tile.
Previously, mounting it on every participant tile meant that when
the local participant toggled their camera or microphone, the
warning re-evaluated and triggered a re-render on every tile.
Push state down into the participant metadata subtree so a raised
hand change no longer re-renders the whole metadata block.
Use the children-as-props pattern so only the item actually related
to the raised hand re-renders when its state changes.
This also better encapsulates the color-update logic tied to the
raised-hand state.
useRaisedHandPosition is called on every participant tile, so any
unnecessary subscription in it multiplies re-renders across the
whole conference.
Reduce the number of events subscribed to for remote participants
to the strict minimum, while keeping the subscription to the
relevant attributes of the local participant intact.
Encapsulate the participant metadata rendering in a clear boundary
component, especially by pushing the raised-hand logic down into a
dedicated child.
This way, when a participant raises their hand, only the child
component re-renders instead of the whole ParticipantTile.
Reduce the set of events the chat subscribes to, keeping only those
that actually affect its rendering.
This avoids frequent re-renders triggered by unrelated events on
the room or participants.
In the participant tile, there is no need to read the pinned track
through a Valtio snapshot, which would trigger a re-render every
time the pinned track changes, for every participant tile.
Switch to an imperative read of the store to avoid these unnecessary
re-renders.
Reorganize all components related to the participant tile into a
dedicated feature folder, so the code organization is clearer and
related components are grouped together.
The useSettingsDialogs hook only encapsulated Valtio store
manipulation that should be declared at the module level. Keeping it
as a hook triggered unnecessary re-renders in components that used
it, subscribing to the store.
Remove the hook and use the store actions directly at the module
level.
Move the keyboard shortcut registration down into the
SettingsDialogProvider leaf component, so shortcut-related
re-renders no longer bubble up and re-render the whole
Videoconference component.
Reduce the set of events the ParticipantToggle subscribes to, so it
does not re-render on every LocalParticipant event but only on the
ones that actually affect its rendering.
Refactor the connection observer, previously a hook consumed inside
the Videoconference component, into a dedicated leaf component in
the tree.
This avoids re-rendering the whole Videoconference component every
time the connection state changes; only the leaf reacts to it.
Reduce the set of events that trigger an update of the participant
list in the chat, keeping only those relevant to display: attribute
and name changes.
This avoids unnecessary re-renders when other, unrelated participant
events fire.
At this stage, the chat is kept mounted in the DOM at all times to
preserve state and keep receiving new messages. As a consequence,
every chat message ends up rendered in the DOM, which can bloat it
significantly in large meetings with hundreds of participants
exchanging messages.
Virtualize the chat message list so only the few messages actually
visible are rendered in the DOM.
The fact that the chat is always mounted in the DOM will be
addressed in a later commit.
The InviteDialog open state was managed very high in the React tree,
which triggered a re-render of the whole conference tree whenever
the dialog was opened or closed.
Push the state down to a narrower component to limit the scope of
re-renders.
Part of a broader effort to isolate as much as possible what should
re-render, so we can then focus on tackling the real performance
issues.
Extract the code responsible for announcing pinned track changes
(for accessibility) into a well-scoped leaf component.
Previously, calling the useScreenReaderAnnounce hook inside
StageLayout triggered a re-render of the whole layout subtree.
Encapsulating the announcement logic in a narrow component keeps
those re-renders local to that leaf.
Vendor the pinned track context and its associated hook from
LiveKit, and switch the underlying state management to a Valtio
store.
The LiveKit ContextProvider was injected high in the tree, so any
pinned track change triggered a re-render of a large portion of the
app. With a Valtio store, only the parts of the app that actually
subscribe to the pinned track or need to pin one re-render when
present in the DOM.
Move track manipulation into a leaf component to avoid re-rendering
the whole videoconference tree when a track update occurs.
The new StageLayout component is now responsible for rendering the
video tracks in the relevant layout, while the Videoconference
component keeps the responsibility of building the overall
conference view.
We save re-rendering the ControlBar for every track/layout changes
for example.
In previous version, webhook may not be called in case of failure,
because the task wouldn't be actually retried.
We know check the exception raised against the auto_retry for
config.
Analytics capture also happens in case of definitive failure.
Request may fail for other reasons than HTTPError (ConnectionError for
instance). This change switches to the more generalized error for the
retry logic on call webhook & transcribe audio