diff --git a/.gitignore b/.gitignore
index 4f5dbf29..608168fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ db.sqlite3
# IDEs
.idea/
.vscode/
+.cursor/
*.iml
.devcontainer
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72093376..f906ed65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to
## [Unreleased]
+### Added
+
+- ✨(frontend) share OneToOneFocusLayout between PiP and main room
+
## [1.29.0] - 2026-08-25
### Added
diff --git a/src/frontend/src/features/layout/components/OneToOneFocusLayout.tsx b/src/frontend/src/features/layout/components/OneToOneFocusLayout.tsx
index 0541bad4..7cb9ed96 100644
--- a/src/frontend/src/features/layout/components/OneToOneFocusLayout.tsx
+++ b/src/frontend/src/features/layout/components/OneToOneFocusLayout.tsx
@@ -2,7 +2,7 @@ import { memo } from 'react'
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
-import { ParticipantTile } from '@/features/rooms/livekit/components/ParticipantTile'
+import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
import { getTrackKey } from '@/features/layout/utils/trackSelection'
type OneToOneFocusLayoutProps = {
diff --git a/src/frontend/src/features/layout/components/StageLayout.tsx b/src/frontend/src/features/layout/components/StageLayout.tsx
index d4fc8e05..d859a5ff 100644
--- a/src/frontend/src/features/layout/components/StageLayout.tsx
+++ b/src/frontend/src/features/layout/components/StageLayout.tsx
@@ -12,7 +12,8 @@ import {
import { Track } from 'livekit-client'
import { useSnapshot } from 'valtio'
import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout'
-import { useEffect, useRef } from 'react'
+import { useEffect, useMemo, useRef } from 'react'
+import { OneToOneFocusLayout } from '@/features/layout/components/OneToOneFocusLayout'
export const StageLayout = () => {
const lastAutoFocusedScreenShareTrack =
@@ -36,6 +37,29 @@ export const StageLayout = () => {
(track) => !isEqualTrackRef(track, pinnedTrackRef)
)
+ const cameraTracks = useMemo(
+ () => tracks.filter((t) => t.source === Track.Source.Camera),
+ [tracks]
+ )
+
+ const isOneToOne =
+ !pinnedTrackRef &&
+ screenShareTracks.length === 0 &&
+ cameraTracks.length <= 2
+
+ const oneToOneMainTrack = useMemo(() => {
+ if (!isOneToOne) return undefined
+ const remote = cameraTracks.find((t) => !t.participant?.isLocal)
+ const local = cameraTracks.find((t) => t.participant?.isLocal)
+ return remote ?? local
+ }, [isOneToOne, cameraTracks])
+
+ const oneToOneThumbnailTrack = useMemo(() => {
+ if (!isOneToOne) return undefined
+ const local = cameraTracks.find((t) => t.participant?.isLocal)
+ return oneToOneMainTrack === local ? undefined : local
+ }, [isOneToOne, cameraTracks, oneToOneMainTrack])
+
/* eslint-disable react-hooks/exhaustive-deps */
// Code duplicated from LiveKit; this warning will be addressed in the refactoring.
useEffect(() => {
@@ -87,7 +111,12 @@ export const StageLayout = () => {
return (
<>
- {!pinnedTrackRef ? (
+ {isOneToOne ? (
+