oficina / meet Public

iPhone bar leaves the tree while hidden; docs for the full-window call

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f71445d593002c5d4519ab1d033aea95d6b575f3 parent ff35ff5
3 files changed, +74 −7
  • native/DESIGN.md +55 −0
  • native/README.md +15 −3
  • native/ios/Sources/PhoneFullScreenCall.swift +4 −4
diff --git a/native/DESIGN.md b/native/DESIGN.md
index d6085b1..bcf520e 100644
--- a/native/DESIGN.md
+++ b/native/DESIGN.md
@@ -81,3 +81,58 @@ Safari member/guest sign-in and the real iPhone simulator native-media tests
passed after this change. The signed development app was built and installed on
Paul's paired iPhone. That installation does not substitute for a physical
camera/microphone and interruption acceptance call.
+
+## Full-window call · 2026-09-25 (0.3.4)
+
+The call fills the window (Mac) or the screen (iPhone), with the controls over
+it on demand. **Call layout: Full window (new) / Classic** — Calls → Settings…
+on Mac, the gear's sheet on iPhone — keeps the previous layout one choice away;
+Full window is the default. The mini window and tab are unchanged.
+
+- **When the controls show** is `ControlsVisibility` (CallChrome.swift), one
+ state machine for both apps with the time passed in, so tests drive it. Mac:
+ pointer movement shows them, 1.5 s still hides them, leaving the window hides
+ them at once, and the pointer hides with them over the picture
+ (`NSCursor.setHiddenUntilMouseMoves`). iPhone: a tap toggles, 2.5 s after the
+ last touch hides — no hover says the person is still there, and a thumb needs
+ the extra second to reach the bar. They stay up while the pointer is on the
+ bar, a menu, popover, sheet or panel is open (AppKit's menu-tracking and
+ window-sheet notifications cover the ones SwiftUI does not report), a bar
+ control has keyboard focus, VoiceOver is running, or an alert shows. Tab
+ reveals them. Space and ⌘⇧M work with them hidden. Fades are opacity only;
+ Reduce Motion shortens them to 0.1 s and panels fade instead of sliding.
+- **The stage** is edge to edge: square tiles for Focus and a screen, a grid
+ that fits every tile without scrolling until they would be under 200 pt
+ (`GridFit`). On Mac the title bar is transparent with the traffic lights kept.
+ Names are small capsules with the muted mark (shape and word) inside, lifted
+ clear of the bar while it shows, visible while it does not.
+- **The self view** floats in Focus, draggable, settling in the corner it was
+ flicked towards (`PiPLayout`), kept clear of the bar, an open chat and the
+ camera strip beside a shared screen, and remembered. It always says Muted
+ when you are. VoiceOver has "Move self view to …" actions. In Grid you are a
+ tile, as before.
+- **Title and notices.** Title and status go with the controls. Reconnecting,
+ waiting to be let in, full, ended (with Join again), problems (dismissible),
+ "You are sharing your screen" and each person waiting with **Let in** stay
+ until resolved (`CallNotices`); the relay-floor line fades.
+- **Mac bar**: a material bar along the bottom — microphone, camera, share,
+ devices, layout (Focus/Grid and who is on the stage, in a popover), share link
+ — with chat and Leave on the right. Devices is a floating panel over the
+ stage above the bar, not a column that shrinks it.
+- **Mac chat** is an overlay sidebar sliding over the stage's trailing edge,
+ with the existing pop-out to a separate window kept. Chosen over the window
+ alone because the window is window management mid-call on a laptop screen,
+ while the sidebar is one click and leaves the picture its full size underneath;
+ anyone who wants it beside the call still has the window.
+- **iPhone bar**: a kebab (More controls) on the left and End call on the right.
+ The kebab opens a sheet: mute, camera, flip camera, share screen (the system
+ broadcast picker, which is why it is a sheet and not a menu), devices, Mic
+ Mode, layout, share link, chat and Reduce background noise. The status bar and
+ home indicator hide with the controls. `-CallsMicBesideMenu YES` puts a
+ microphone button beside the kebab, off by default.
+
+Snapshots: `CallsInterop --call-snapshots <dir>` (also part of
+`--lobby-snapshots`) renders the Mac call, both layouts, light and dark. The
+iPhone's come from `CallsPhoneUITests.testFullScreenCallSnapshots` against the
+debug build's `-CallsCallFixture` calls (`CallFixture`), written to
+`CALLS_SNAPSHOT_DIR` or `/tmp/immersive-call`.
diff --git a/native/README.md b/native/README.md
index 4ced9a7..000b175 100644
--- a/native/README.md
+++ b/native/README.md
@@ -101,8 +101,11 @@ every rebuild, macOS may ask once per build whether Calls may read it.
Scheduling a meeting for later, ending one, and replacing invitations stay on
the web for this milestone.
-**Devices** in the call opens the camera, microphone and speaker panel, and
-the audio readout beneath them.
+**Devices** in the call opens the camera, microphone and speaker panel. The
+audio readout is behind its ⓘ (**Audio details**), with the screen-share line
+while sharing; the panel itself shows one warning line only when something is
+wrong (a stretched or broken-up signal, echo cancellation off, noise reduction
+bypassed, processing refused — `AudioWarning`).
The camera is a picker on both platforms. Switching rests on the capturer
feeding the same `RTCVideoSource`, so the `RTCVideoTrack` every peer
@@ -420,7 +423,16 @@ Microphone** (⌘⇧M) is the same toggle. The browser room does the same (never
an input, textarea, select, contenteditable, button, link or other control, and
announced through the polite live region), and on iPhone and iPad a hardware
keyboard's Space does it through a key command that yields to text editing and
-focus. Push-to-talk (holding Space while muted) is not built. It shows what the call window's stage would show by
+focus. Push-to-talk (holding Space while muted) is not built.
+
+**Call → Copy Invite Link** (⌘⇧I), beside Mute, puts the meeting's invite link
+on the clipboard and says "Invite link copied" briefly over the stage (and to
+VoiceOver), from the call window or the mini window, with the controls shown or
+not. It asks the server the way the Share button does, which returns the
+meeting's current sealed invitation and never replaces or revokes one. It is
+off outside a call; a guest pressing it is told only members can share. An
+iPad's hardware keyboard has the same shortcut. The browser room does not:
+⌘⇧I is Chrome's developer tools. It shows what the call window's stage would show by
default: the newest shared screen, otherwise the first other participant with
their muted and camera-off state (there is no active-speaker tracking yet), or
your own camera while you are alone. It is not
diff --git a/native/ios/Sources/PhoneFullScreenCall.swift b/native/ios/Sources/PhoneFullScreenCall.swift
index 8f0f41c..f215e40 100644
--- a/native/ios/Sources/PhoneFullScreenCall.swift
+++ b/native/ios/Sources/PhoneFullScreenCall.swift
@@ -30,7 +30,9 @@ extension PhoneCall {
.accessibilityIdentifier("callStage")
if !grid { selfView(in: size, safe: safe) }
topOverlay(safe: safe)
- bar(safe: safe)
+ // Gone rather than transparent while hidden, so nothing invisible
+ // can be tapped or found; the fade is the transition.
+ if chrome.visible { bar(safe: safe).transition(.opacity) }
// Space on a hardware keyboard, as the classic bar's Mute has it;
// the full-screen Mute is in the menu, so this stands in for it.
Button("Mute or unmute microphone") { call.mute() }
@@ -242,6 +244,7 @@ extension PhoneCall {
.opacity(chrome.visible ? 1 : 0)
.allowsHitTesting(chrome.visible)
.accessibilityHidden(!chrome.visible)
+ .accessibilityIdentifier("callTitle")
ForEach(notices.filter { $0.important && !dismissedNotices.contains($0.id) }) { notice in
noticeView(notice)
@@ -326,9 +329,6 @@ extension PhoneCall {
.frame(height: Self.barHeight + safe.bottom + 40)
.allowsHitTesting(false)
}
- .opacity(chrome.visible ? 1 : 0)
- .allowsHitTesting(chrome.visible)
- .accessibilityHidden(!chrome.visible)
}
}