cmux-next: Picture in Picture for browser tabs, and a media card like macOS's

Merged
TE
16d ago
+1720-172

Leo asked for Picture in Picture for browser tabs, with the media card brought up to the macOS reference.

What it does

  • One action, every entrypoint. tab.pictureInPicture ("Picture in Picture") backs the media card's new button, the tab menu (Options), the card's right-click menu, the palette, cmux media picture-in-picture and the MCP tool app_media_picture_in_picture. With a named tab it acts on that tab. With no tab named, it picks the focused tab if it shows a video, then the tab whose video plays (the one the card shows, in any workspace), then the focused tab. The card's right-click menu passes nowPlayingCard, so its row always acts on the tab the card shows and reads Exit Picture in Picture while that video floats. A CLI or MCP run gets "No video" or "can't play in Picture in Picture" back as the run's answer; a palette or menu run shows it as a notice.
  • Both engines. A page-world script runs as a user gesture in both engines (CEF Runtime.evaluate with userGesture, WebKit evaluateJavaScript). It calls requestPictureInPicture() on the playing video of the page and its same-origin frames, and falls back to WebKit's webkitSetPresentationMode. If a video already floats, it brings it back. The page's disablePictureInPicture is respected. Chromium (Chrome runtime style) and WebKit each draw their own always-on-top PiP window, and closing it returns the video to the tab. A site's own PiP button already worked in both engines (scripts/cmux-next/now-playing-live.py, step 3); this PR leaves that path as it is.
  • Keeps playing. The media report gains pip. Hibernation's probe counts a floating page as playing (the page script and tab.state.media.isPictureInPicture), so a paused or muted floating video is never frozen or discarded after its tab hides or another workspace shows.
  • Media card like macOS's. The site sits over the title and artist. The artwork fills the top right and fades into the card's glass (CardSurfaceView, unchanged and never opaque). Previous, Play or Pause and Next start at the leading edge, then a divider and Picture in Picture for a video. The play disc and the scrubber use the theme accent (Palette.accentFill / onAccent; with no accent chosen that is the text color, as before). The level bars move to the end of the controls row.
  • The toolbar's media hub rows get the same PiP button for a video.
  • Strings in all 21 locales. A What's New highlight is included.
  • No default key yet. The defaults guard needs a call owner other than the author to approve a new default key, so ⌥⇧⌘P comes in its own small PR. Until then the action can be bound by id in Settings › Keyboard Shortcuts or cmux.json.

Changelog

  • Added: Picture in Picture for browser tab videos from the media card, the tab menu and the command palette.
  • Changed: The media card shows the site, larger artwork and a Picture in Picture button, in the theme's accent.

Known limits

  • A video inside a cross-origin frame (an embedded YouTube player) shows the row, but the toggle cannot reach it and answers "No video". The page script only sees the page and its same-origin frames.
  • The toggle answers "entered" once it asked for PiP; a page that then rejects the request (no video track yet) fails silently.

Observed / Not run

  • Observed: prepush-gate.py reports no new violations.
  • Observed: the fleet export (scripts/measure/export-action-surfaces.sh) at ec2f307 (the latest feat-cmux-next merged in) passed every ActionSurfaceParityTests check; its exports are committed in 4679944 (the shortcuts change is base's ⌘W alias for a multi-row sidebar selection, which base's own exports don't have yet; this PR adds no default key).
  • Observed (fleet, package-test-lane.sh at 038941b, the head): PictureInPictureTests 8/8 at cd91441 (incl. the tab menu's and the card menu's hide and Exit title hooks), NowPlayingCardDismissTests 7/7, NowPlayingLevelsTests 4/4, NewTabNowPlayingTests 7/7, ActionSurfaceParityTests 11/11 pass; NowPlayingCardTests 8/9.
  • Not mine: NowPlayingCardTests.aTabsArtworkThatDidNotLoadComesInLater fails the same way at the base, 511423d1 (fleet run, NowPlayingCardTests.swift:199 there, :240 here after this PR's added tests, "the card never shows the tab's artwork after its first fetch failed"). This PR does not touch the artwork fetch or its retry.
  • Review: an independent subagent review found no blockers; its should-fix items (CLI/MCP refusals, the card menu's tab, narrow-card overlap, rank tie, docs, a menu-hook test) are in 93b9b07. A second review of 93b9b07 passed; the card-menu test it asked for (theCardsMenuFloatsTheTabTheCardShows) is in cd91441.
  • Not run: the live PiP window in a running app, which needs a GUI Mac.

🤖 Generated with Claude Code
Agent session: cosmic-gray