Compare commits

..

6 Commits

Author SHA1 Message Date
lebaudantoine d59a352181 ♻️(frontend) inline MediaPipe WASM modules to avoid loading from remote
Copy the MediaPipe WASM modules into the frontend build output and
serve them locally, instead of loading them from the Google CDN at
runtime.

Mediapipe was a transitive dependency, add it explicitly.

Requested by some members of the community.
It closes #1168
2026-07-11 15:51:24 +02:00
lebaudantoine 09055684cb 🎨(frontend) format vite config file
Apply the project's formatter to the Vite config file to align its
style with the rest of the codebase.
2026-07-11 15:51:24 +02:00
lebaudantoine f5c7e0f6ec (frontend) install vite-plugin-static-copy for MediaPipe WASM assets
Add vite-plugin-static-copy as a dependency. It will be used to copy
and serve the WASM modules shipped by the MediaPipe JS dependency
from the frontend build output.
2026-07-11 15:51:24 +02:00
lebaudantoine 08288e1a46 ♻️(frontend) reorganize JS packages in package.json
Sort and regroup the entries in package.json that were
not in the expected order.
2026-07-11 15:51:23 +02:00
lebaudantoine a97d47096e ♻️(frontend) inline model weights to avoid loading them from remote
Inline the model weights instead of fetching them from a Google
remote location at runtime. The weights do not update frequently, so
inlining removes an external dependency.
2026-07-11 15:51:11 +02:00
lebaudantoine fbaade1a08 ♻️(frontend) refactor background processors to use the new API
Update the calls to the background processors imported from
livekit-track-processor so they use the new instantiation method
instead of the previous, now-deprecated one.
2026-07-11 15:45:35 +02:00
7 changed files with 112 additions and 185 deletions
-2
View File
@@ -23,13 +23,11 @@ and this project adheres to
- ♻️(frontend) refactor background processors to use the new API
- ♻️(frontend) inline model weights to avoid loading them from remote
- ♻️(frontend) inline MediaPipe WASM modules to avoid loading from remote
- ⬆️(frontend) upgrade posthog-js from 1.387.0 to 1.391.2
### Fixed
- 🩹(backend) identify externally provisioned users to PostHog
- 🐛(backend) fix info panel crash for unregistered rooms
- ♿️(frontend) focus side panel container on open #1452
## [1.23.0] - 2026-07-08
+14 -22
View File
@@ -30,7 +30,7 @@
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"posthog-js": "1.387.0",
"react": "18.3.1",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
@@ -9074,19 +9074,19 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.391.2",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.391.2.tgz",
"integrity": "sha512-q0DZN6ljchSnAFJIXf+sQFTPlsLjTlRa+TvrL+QRb6413BGtib/MNiQy1bnwLKt8KR+f6xJYvkqdLyty9s4Aww==",
"version": "1.387.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.387.0.tgz",
"integrity": "sha512-Pv1jUMySMN62zoAxdJBJPV8n62lkHdjuWhpeU7izczc5Dqbx3hhqO2hkrNTI8Yx1ezmWk2qUHZs03FuOBubdFQ==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "^1.35.3",
"@posthog/types": "^1.390.2",
"@posthog/core": "^1.33.0",
"@posthog/types": "^1.387.0",
"core-js": "^3.38.1",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
"preact": "^10.29.2",
"preact": "^10.28.2",
"query-selector-shadow-dom": "^1.0.1",
"web-vitals": "^5.3.0"
"web-vitals": "^5.1.0"
}
},
"node_modules/powershell-utils": {
@@ -9103,21 +9103,13 @@
}
},
"node_modules/preact": {
"version": "10.29.7",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz",
"integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==",
"version": "10.28.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.3.tgz",
"integrity": "sha512-tCmoRkPQLpBeWzpmbhryairGnhW9tKV6c6gr/w+RhoRoKEJwsjzipwp//1oCpGPOchvSLaAPlpcJi9MwMmoPyA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
},
"peerDependencies": {
"preact-render-to-string": ">=5"
},
"peerDependenciesMeta": {
"preact-render-to-string": {
"optional": true
}
}
},
"node_modules/prelude-ls": {
@@ -11593,9 +11585,9 @@
}
},
"node_modules/web-vitals": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz",
"integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.1.0.tgz",
"integrity": "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==",
"license": "Apache-2.0"
},
"node_modules/webrtc-adapter": {
+1 -1
View File
@@ -37,7 +37,7 @@
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"posthog-js": "1.387.0",
"react": "18.3.1",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
@@ -6,8 +6,8 @@ import { Button, Div } from '@/primitives'
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { ParticipantsList } from './controls/Participants/ParticipantsList'
import { PanelId, useSidePanel } from '../hooks/useSidePanel'
import React, { ReactNode, useCallback, useRef } from 'react'
import { useSidePanel } from '../hooks/useSidePanel'
import { ReactNode } from 'react'
import { Chat } from '../prefabs/Chat'
import { Effects } from './effects/Effects'
import { Admin } from './Admin'
@@ -15,7 +15,6 @@ import { Tools } from './Tools'
import { Info } from './Info'
import { HStack } from '@/styled-system/jsx'
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
import { useRestoreFocus } from '@/hooks/useRestoreFocus'
type StyledSidePanelProps = {
title: string
@@ -30,115 +29,103 @@ type StyledSidePanelProps = {
isReactionToolbarOpen?: boolean
}
const StyledSidePanel = React.forwardRef<HTMLElement, StyledSidePanelProps>(
(
{
title,
ariaLabel,
children,
onClose,
isClosed,
isReactionToolbarOpen,
closeButtonTooltip,
isSubmenu = false,
onBack,
backButtonLabel,
},
ref
) => (
<aside
ref={ref}
tabIndex={-1}
className={css({
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'box.border',
backgroundColor: 'box.bg',
color: 'box.text',
borderRadius: 8,
flex: 1,
position: 'absolute',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
margin: 'var(--sizes-room-side-panel-margin)',
marginLeft: 0,
marginBottom: 0,
padding: 0,
gap: 0,
right: 0,
top: 0,
width: 'var(--sizes-room-side-panel)',
transition: '.5s cubic-bezier(.4,0,.2,1) 5ms',
'&:focus': {
outline: 'none',
},
})}
style={{
transform: isClosed
? 'translateX(calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin)))'
: 'none',
bottom: isReactionToolbarOpen
? 'calc( var(--sizes-room-control-bar) + var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))'
: 'var(--sizes-room-control-bar)',
}}
aria-hidden={isClosed}
aria-label={ariaLabel}
>
<HStack alignItems="center">
{isSubmenu && (
<Button
variant="secondaryText"
size="sm"
square
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
aria-label={backButtonLabel}
onPress={onBack}
>
<RiArrowLeftLine size={20} aria-hidden="true" />
</Button>
)}
<Heading
slot="title"
level={1}
className={text({ variant: 'h2' })}
style={{
paddingLeft: isSubmenu ? 0 : '1.5rem',
paddingTop: '1rem',
display: isClosed ? 'none' : 'flex',
justifyContent: 'start',
alignItems: 'center',
}}
const StyledSidePanel = ({
title,
ariaLabel,
children,
onClose,
isClosed,
isReactionToolbarOpen,
closeButtonTooltip,
isSubmenu = false,
onBack,
backButtonLabel,
}: StyledSidePanelProps) => (
<aside
className={css({
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'box.border',
backgroundColor: 'box.bg',
color: 'box.text',
borderRadius: 8,
flex: 1,
position: 'absolute',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
margin: 'var(--sizes-room-side-panel-margin)',
marginLeft: 0,
marginBottom: 0,
padding: 0,
gap: 0,
right: 0,
top: 0,
width: 'var(--sizes-room-side-panel)',
transition: '.5s cubic-bezier(.4,0,.2,1) 5ms',
})}
style={{
transform: isClosed
? 'translateX(calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin)))'
: 'none',
bottom: isReactionToolbarOpen
? 'calc( var(--sizes-room-control-bar) + var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))'
: 'var(--sizes-room-control-bar)',
}}
aria-hidden={isClosed}
aria-label={ariaLabel}
>
<HStack alignItems="center">
{isSubmenu && (
<Button
variant="secondaryText"
size="sm"
square
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
aria-label={backButtonLabel}
onPress={onBack}
>
{title}
</Heading>
</HStack>
<Div
position="absolute"
top="5"
right="5"
<RiArrowLeftLine size={20} aria-hidden="true" />
</Button>
)}
<Heading
slot="title"
level={1}
className={text({ variant: 'h2' })}
style={{
display: isClosed ? 'none' : undefined,
paddingLeft: isSubmenu ? 0 : '1.5rem',
paddingTop: '1rem',
display: isClosed ? 'none' : 'flex',
justifyContent: 'start',
alignItems: 'center',
}}
>
<Button
invisible
variant="tertiaryText"
size="xs"
onPress={onClose}
aria-label={closeButtonTooltip}
tooltip={closeButtonTooltip}
>
<RiCloseLine />
</Button>
</Div>
{children}
</aside>
)
{title}
</Heading>
</HStack>
<Div
position="absolute"
top="5"
right="5"
style={{
display: isClosed ? 'none' : undefined,
}}
>
<Button
invisible
variant="tertiaryText"
size="xs"
onPress={onClose}
aria-label={closeButtonTooltip}
tooltip={closeButtonTooltip}
>
<RiCloseLine />
</Button>
</Div>
{children}
</aside>
)
StyledSidePanel.displayName = 'StyledSidePanel'
type PanelProps = {
isOpen: boolean
children: React.ReactNode
@@ -175,28 +162,8 @@ export const SidePanel = () => {
const { isOpen: isReactionToolbarOpen } = useReactionsToolbar()
const asideRef = useRef<HTMLElement>(null)
const focusAside = useCallback(() => {
requestAnimationFrame(() => {
asideRef.current?.focus({ preventScroll: true })
})
}, [])
const handlePanelOpened = useCallback(() => {
if (activePanelId === PanelId.CHAT) return
focusAside()
}, [activePanelId, focusAside])
useRestoreFocus(isSidePanelOpen, {
onOpened: handlePanelOpened,
preventScroll: true,
activeKey: activePanelId,
})
return (
<StyledSidePanel
ref={asideRef}
title={title}
ariaLabel={t('ariaLabel', { title })}
onClose={() => {
@@ -95,13 +95,8 @@ const ToolButton = ({
export const Tools = () => {
const { data } = useConfig()
const {
openTranscript,
openScreenRecording,
activeSubPanelId,
isToolsOpen,
isSidePanelOpen,
} = useSidePanel()
const { openTranscript, openScreenRecording, activeSubPanelId, isToolsOpen } =
useSidePanel()
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
// Restore focus to the element that opened the Tools panel
@@ -118,7 +113,6 @@ export const Tools = () => {
},
restoreFocusRaf: true,
preventScroll: true,
shouldRestoreOnClose: () => !isSidePanelOpen,
})
const isTranscriptEnabled = useIsRecordingModeEnabled(
@@ -37,7 +37,7 @@ export function Chat({ ...props }: ChatProps) {
const room = useRoomContext()
const { send, chatMessages, isSending } = useChat()
const { isChatOpen, isSidePanelOpen } = useSidePanel()
const { isChatOpen } = useSidePanel()
const chatSnap = useSnapshot(chatStore)
// Keep track of the element that opened the chat so we can restore focus
@@ -51,7 +51,6 @@ export function Chat({ ...props }: ChatProps) {
})
},
preventScroll: true,
shouldRestoreOnClose: () => !isSidePanelOpen,
})
// Use useParticipants hook to trigger a re-render when the participant list changes.
+1 -24
View File
@@ -6,10 +6,6 @@ export type RestoreFocusOptions = {
onClosed?: () => void
restoreFocusRaf?: boolean
preventScroll?: boolean
/** When the panel stays open but its content changes, update the restore target. */
activeKey?: string | null
/** Return false to skip restoring focus on close (e.g. when switching to another panel). */
shouldRestoreOnClose?: () => boolean
}
/**
@@ -26,12 +22,9 @@ export function useRestoreFocus(
onClosed,
restoreFocusRaf = false,
preventScroll = true,
activeKey,
shouldRestoreOnClose,
} = options
const prevIsOpenRef = useRef(false)
const prevActiveKeyRef = useRef(activeKey)
const triggerRef = useRef<HTMLElement | null>(null)
useEffect(() => {
@@ -44,41 +37,25 @@ export function useRestoreFocus(
onOpened?.()
}
// Panel switched while staying open
if (wasOpen && isOpen && activeKey !== prevActiveKeyRef.current) {
const activeEl = document.activeElement as HTMLElement | null
triggerRef.current = resolveTrigger ? resolveTrigger(activeEl) : activeEl
onOpened?.()
}
// Just closed
if (wasOpen && !isOpen) {
const trigger = triggerRef.current
const shouldRestore =
(shouldRestoreOnClose?.() ?? true) &&
trigger &&
document.contains(trigger)
if (shouldRestore) {
if (trigger && document.contains(trigger)) {
const focus = () => trigger.focus({ preventScroll })
if (restoreFocusRaf) requestAnimationFrame(focus)
else focus()
}
triggerRef.current = null
onClosed?.()
}
prevIsOpenRef.current = isOpen
prevActiveKeyRef.current = activeKey
}, [
isOpen,
activeKey,
onClosed,
onOpened,
preventScroll,
resolveTrigger,
restoreFocusRaf,
shouldRestoreOnClose,
])
}