Skip to content

feat(calls): re-enable Document PiP as an env-gated, always-on pop-out#2391

Merged
rozagerardo merged 8 commits into
mainfrom
rozagerardo/feat/GH-2383-call-pip-reenable
Jul 17, 2026
Merged

feat(calls): re-enable Document PiP as an env-gated, always-on pop-out#2391
rozagerardo merged 8 commits into
mainfrom
rozagerardo/feat/GH-2383-call-pip-reenable

Conversation

@rozagerardo

@rozagerardo rozagerardo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Re-enables Document Picture-in-Picture for calls (feat(matrix): video call improvements — PiP, screenshare layout, sound isolation, recording, cross-context awareness #2383 item a), disabled since Call world-class UX #2297 while share UX stabilized.
  • CALL_DOCUMENT_PIP_ENABLED moves from a hardcoded const into packages/feature-flags (getEnableCallDocumentPip(), NEXT_PUBLIC_ENABLE_CALL_DOCUMENT_PIP, default true) for an instant rollback without a code change.
  • PiP now auto-opens on any active call when supported, not just during screenshare, and no longer force-closes when screenshare stops.
  • Adds an explicit pop-out / un-pop toolbar button (required regardless — browsers mandate a user gesture to open Document PiP).
  • Unsupported/disabled browsers keep the existing embedded floating dock, no button shown — covers the Safari/Firefox fallback.

Static code trace confirms resolveCallAudioPortalTarget() unconditionally portals remote audio to the main document body whenever PiP is open, which looks like the historical WCUX-PIP audio-continuity fix already landed independently of the disable commit — not yet confirmed live, pending preview testing.

Test plan

  • tsc --noEmit clean
  • eslint clean (no new warnings)
  • Full @hypha-platform/epics test suite: 256/256 pass (1 pre-existing unrelated failure needing a DB env var, confirmed failing on main too)
  • Preview: join a real call, confirm PiP pops out automatically
  • Preview: confirm remote and local audio survive PiP open/close
  • Preview: pop-out button opens PiP; un-pop button (inside the PiP window) returns to the embedded dock; re-pop via the button works
  • Preview: theme/space-accent styling copies correctly into the PiP window
  • Preview: screenshare + PiP together — no regression to the existing filmstrip layout
  • Preview: unsupported browser (Safari/Firefox) shows no pop-out button, embedded dock still works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Enable Call Document Picture-in-Picture via a new feature flag (defaults ON, supports instant rollback).
    • Document PiP auto-opens during active calls when eligible, with dedicated open/close controls.
  • Bug Fixes
    • Improved resilience when browsers block auto-open requests, keeping behavior stable and user-tolerant.
  • Improvements
    • Better PiP loading experience and refined PiP toolbar/menu layout and spacing for smoother access.
  • Tests
    • Updated PiP regression coverage for the feature-flag gate, auto-open behavior, and key UI constraints.

Re-enable Document Picture-in-Picture for calls (GH-2383 item a),
disabled since PR #2297. The historical audio-continuity regression
appears already fixed independently: resolveCallAudioPortalTarget()
unconditionally pins remote audio to the main document body whenever
PiP is open.

- Move CALL_DOCUMENT_PIP_ENABLED off a hardcoded const into
  packages/feature-flags as getEnableCallDocumentPip()
  (NEXT_PUBLIC_ENABLE_CALL_DOCUMENT_PIP, default true) for an instant
  rollback without a code change.
- Auto-open Document PiP on any active call when supported, not just
  during screenshare; no longer force-closes when screenshare stops.
- Add an explicit pop-out / un-pop toolbar button (required regardless,
  since browsers mandate a user gesture to open Document PiP).
- Unsupported/disabled browsers keep the existing embedded floating
  dock with no button shown, covering the Safari/Firefox fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rozagerardo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb6db68f-cc2b-40e1-9186-cd2127595fa7

📥 Commits

Reviewing files that changed from the base of the PR and between 1be75c8 and ad6a877.

📒 Files selected for processing (4)
  • packages/epics/src/common/global-call-dock-overlay.tsx
  • packages/epics/src/common/human-chat-panel/human-chat-panel-call-react-popover.tsx
  • packages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsx
  • packages/epics/src/common/use-call-dock-document-pip.ts

Walkthrough

Document PiP is controlled by a client feature flag that defaults on, supports active-call auto-open and explicit open/close controls, tracks dismissal per call, waits for PiP styles before rendering, and updates PiP controls, menus, capture confirmation, and regression gates.

Changes

Call Document Picture-in-Picture

Layer / File(s) Summary
Document PiP feature flag contract
packages/feature-flags/src/client.ts, packages/feature-flags/src/index.ts, packages/epics/package.json, apps/web/.env.template
Adds the client-safe getter, discovery definition, workspace dependency, and environment variable for Document PiP.
Call dock PiP lifecycle
packages/epics/src/common/global-call-dock-overlay.tsx, packages/epics/src/common/human-chat-panel/call-document-pip-window-geometry.ts
Updates PiP gating, active-call auto-open behavior, per-call dismissal tracking, toggle handling, and floating-window controls.
PiP style loading synchronization
packages/epics/src/common/use-call-dock-document-pip.ts, packages/epics/src/common/global-call-dock-overlay.tsx
Tracks stylesheet loading and renders a loading view until PiP styles are ready.
PiP controls and responsive layout
packages/epics/src/common/human-chat-panel/*, packages/epics/src/common/global-call-dock-overlay.tsx, packages/ui-utils/src/global.css
Adapts PiP toolbar sizing, reaction and emoji menus, capture confirmation, popover positioning, and consent-banner layout.
Document PiP regression coverage
packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts
Verifies feature-flag control, auto-open behavior, explicit toggles, menu sizing, confirmation visibility, and toolbar layout.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GlobalCallDockOverlay
  participant useCallDockDocumentPip
  participant BrowserDocumentPiP
  User->>GlobalCallDockOverlay: enter active call
  GlobalCallDockOverlay->>useCallDockDocumentPip: openPip()
  useCallDockDocumentPip->>BrowserDocumentPiP: create PiP window and copy styles
  BrowserDocumentPiP-->>useCallDockDocumentPip: stylesheet loading completes
  useCallDockDocumentPip-->>GlobalCallDockOverlay: stylesReady
  GlobalCallDockOverlay-->>User: render PiP dock
  User->>GlobalCallDockOverlay: toggle floating window
  GlobalCallDockOverlay->>useCallDockDocumentPip: closePip() or openPip()
Loading

Possibly related PRs

Suggested reviewers: plitzenberger, alexprate

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title uses conventional commits, but the scope "calls" does not reference an affected package or app as required. Change the scope to a package or app name such as feat(web): ... or feat(epics): ... while keeping the PiP summary.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rozagerardo/feat/GH-2383-call-pip-reenable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/epics/src/common/global-call-dock-overlay.tsx`:
- Around line 1002-1012: Update onToggleDocumentPip to handle rejected promises
from openPip by attaching a catch handler, matching the existing auto-open
effect’s rejection handling while preserving the current toggle behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c5b70a0d-5b7a-49cd-8776-b729850cc164

📥 Commits

Reviewing files that changed from the base of the PR and between 4667973 and 217808c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/web/.env.template
  • packages/epics/package.json
  • packages/epics/src/common/global-call-dock-overlay.tsx
  • packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts
  • packages/epics/src/common/human-chat-panel/call-document-pip-window-geometry.ts
  • packages/feature-flags/src/client.ts
  • packages/feature-flags/src/index.ts
💤 Files with no reviewable changes (1)
  • packages/epics/src/common/human-chat-panel/call-document-pip-window-geometry.ts

Comment thread packages/epics/src/common/global-call-dock-overlay.tsx
@github-actions

Copy link
Copy Markdown

🔗 Custom preview URL: https://pr-2391.preview-app.hypha.earth

CodeRabbit review on #2391: void openPip() doesn't suppress a
rejected requestWindow() call, which can surface as an unhandled
promise rejection. Add the same .catch() already used by the
auto-open effect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rozagerardo and others added 4 commits July 16, 2026 13:37
…om un-popping

- The PiP-mode toolbar footer set overflow-hidden at a fixed h-8
  height, clipping the audio-settings, capture, and reaction popovers
  (all position:absolute; bottom-full, same as the embedded dock,
  which uses overflow-visible for the same container and works fine).
- "Open Space" unconditionally closed Document PiP before navigating.
  That's two independent actions bundled together — PiP staying open
  while browsing to the call's own space is exactly the use case PiP
  is for. Drop the forced closePip(), keep the focus + navigation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Menus anchored to their trigger's right edge with a fixed min-width,
which in narrow Document PiP windows pushed content past the left
edge where the PiP body's overflow:hidden silently clipped it (worst
on the emoji picker). Center on the trigger and cap width to the
viewport for pip density so the panel always fits inside the window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nd stop-recording confirm

Root cause of most of these: dockControlsDensity was hardcoded to
'default', so every density === 'pip' branch downstream (popover
sizing/clamping, compact buttons, inline stop-recording confirm) never
actually ran in the real Document PiP window. Now correctly flips to
'pip' when inDocumentPip.

- Capture-consent recording banner now floats above the PiP toolbar
  instead of pushing it (and its click targets) out of the fixed-height,
  overflow-hidden PiP window.
- react/audio/capture dropdown menus clamp to the PiP window's actual
  width/height instead of overflowing it; same treatment extended to the
  embedded (non-PiP) floating dock via a --hypha-call-dock-popover-max-h
  CSS var driven by the dock's own measured height.
- Stop-recording confirmation renders inline in the capture dropdown in
  PiP instead of a page-level AlertDialog, which portals to the main
  window's document and is invisible while viewing another tab.
- Emoji picker now passes dynamicWidth to emoji-mart's Picker — it
  defaults to a fixed internal pixel width and ignores container CSS
  entirely otherwise.
- PiP toolbar buttons/icons now match the embedded dock's sizing and
  grouping (same grid layout, same 32px buttons) instead of a smaller,
  inconsistent PiP-only tier; removed the [data-call-pip-toolbar]
  global CSS override this exposed as redundant/harmful (forced every
  button, including ones inside dropdown menus, to a fixed square).
- PiP window shows a lightweight inline-styled loading spinner while its
  cloned stylesheets are still loading, instead of a flash of unstyled
  content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/epics/src/common/global-call-dock-overlay.tsx (1)

622-623: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset PiP dismissal when the active call changes.

This resets only when showFloatingDock becomes false. If activeRoomId changes while the dock remains visible, dismissing PiP in one call suppresses auto-open for the next call.

Proposed fix
+  const dismissedCallIdRef = React.useRef<string | null>(null);
+
   React.useEffect(() => {
-    if (!showFloatingDock) {
+    if (!showFloatingDock || dismissedCallIdRef.current !== activeRoomId) {
       pipDismissedRef.current = false;
+      dismissedCallIdRef.current = activeRoomId;
     }
-  }, [showFloatingDock]);
+  }, [activeRoomId, showFloatingDock]);

Also applies to: 881-886

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/epics/src/common/global-call-dock-overlay.tsx` around lines 622 -
623, Reset pipDismissedRef when activeRoomId changes, not only when
showFloatingDock becomes false, so PiP dismissal state does not carry between
calls. Update the effect or call-transition logic associated with
pipDismissedRef and preserve its existing reset behavior when the floating dock
closes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/epics/src/common/global-call-dock-overlay.tsx`:
- Around line 96-124: Add an accessible, translated loading label to
CallDocumentPipLoadingView by retrieving the appropriate translation and
exposing it via aria-label or visually hidden text within the role="status"
element. Use the existing translation mechanism and translation key conventions
for all user-facing text.

In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsx`:
- Around line 558-561: Update the PiP menu positioning in the conditional class
blocks around the audio and capture controls so the menu is positioned relative
to the PiP dock/viewport and clamped within its horizontal bounds, rather than
centered on the trigger wrapper via left-1/2 and -translate-x-1/2. Apply the
same correction to both affected menus while preserving the non-PiP positioning.
- Around line 682-710: Update the PiP stop-confirmation block in the human chat
panel controls to use alert-dialog semantics with an accessible label and
description, and move keyboard focus to the cancel or confirm action when the
confirmation appears. Preserve the existing recording/transcript text and
handlers while ensuring focus remains within the confirmation until the user
completes or cancels it.

In `@packages/epics/src/common/use-call-dock-document-pip.ts`:
- Around line 221-224: Update the PiP window initialization flow around
copyStylesIntoWindow so its completion callback only sets stylesReady(true) when
it still belongs to the current PiP window or request generation. Invalidate the
previous request when opening a new window or closing PiP, while preserving the
existing style-loading behavior for the active window.

---

Outside diff comments:
In `@packages/epics/src/common/global-call-dock-overlay.tsx`:
- Around line 622-623: Reset pipDismissedRef when activeRoomId changes, not only
when showFloatingDock becomes false, so PiP dismissal state does not carry
between calls. Update the effect or call-transition logic associated with
pipDismissedRef and preserve its existing reset behavior when the floating dock
closes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ae96097-813b-4480-9264-0272cf725550

📥 Commits

Reviewing files that changed from the base of the PR and between 217808c and 1be75c8.

📒 Files selected for processing (7)
  • packages/epics/src/common/global-call-dock-overlay.tsx
  • packages/epics/src/common/human-chat-panel/__tests__/call-regression-manual-gates.test.ts
  • packages/epics/src/common/human-chat-panel/human-chat-panel-call-react-popover.tsx
  • packages/epics/src/common/human-chat-panel/human-chat-panel-emoji-mart-surface.tsx
  • packages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsx
  • packages/epics/src/common/use-call-dock-document-pip.ts
  • packages/ui-utils/src/global.css
💤 Files with no reviewable changes (1)
  • packages/ui-utils/src/global.css

Comment thread packages/epics/src/common/global-call-dock-overlay.tsx
Comment thread packages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsx Outdated
Comment thread packages/epics/src/common/use-call-dock-document-pip.ts Outdated
rozagerardo and others added 2 commits July 17, 2026 10:36
- react/audio/capture menus now use fixed positioning anchored to the
  PiP window's own viewport instead of absolute positioning centered on
  their trigger — the triggers sit off-center in the toolbar, so
  trigger-relative centering could still clip a menu against the
  opposite window edge on narrower windows.
- Guard against a stale-window race: if PiP is closed and reopened
  within the stylesheet-load window, the first window's load can no
  longer mark the second window ready.
- Loading spinner in the PiP window now exposes an accessible/
  translated label instead of an empty role="status".
- Inline stop-recording confirmation in PiP gets alertdialog semantics
  (aria-labelledby/describedby) and moves focus to Cancel when it
  appears.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rozagerardo
rozagerardo merged commit eb0855d into main Jul 17, 2026
10 checks passed
@rozagerardo
rozagerardo deleted the rozagerardo/feat/GH-2383-call-pip-reenable branch July 17, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant