Calls: a scrim the title can be read on; iPhone 0.4.7
The window scenario's contrast check caught the first scrim: white on the title strip over a white picture was 2.1:1. Web and Mac now hold 0.72-0.62 black through the strip before fading (4.5:1 and up). The window scenario finds End meeting by its class now that More holds the account's links too. iPhone app 0.4.7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 files changed,
+40
−8
internal/calls/static/calls.css+4 −2native/apple/Sources/CallsKit/MacFullWindowCall.swift+8 −2native/ios/UITests/CallsPhoneUITests.swift+25 −1native/ios/project.yml+1 −1test/browser/call.mjs+2 −2
CI passed — run details
CI log
=== vet === go vet ./... === fmt === === test === go test ./... ok amadan.net/oficina/calls/callscore 7.395s ? amadan.net/oficina/calls/cmd/calls [no test files] ok amadan.net/oficina/calls/internal/calls 4.910s ok amadan.net/oficina/calls/internal/calls/apns 0.046s ok amadan.net/oficina/calls/internal/calls/hub 3.253s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 0.018s ok amadan.net/oficina/calls/internal/calls/seal 0.022s ok amadan.net/oficina/calls/internal/callstest 15.967s ? amadan.net/oficina/calls/protocol [no test files] ? amadan.net/oficina/calls/scripts/writeestilo [no test files] === migration-check === go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check rastrillo migration check: models and migrations agree === race === CGO_ENABLED=1 go test -race ./internal/... ok amadan.net/oficina/calls/internal/calls 33.299s ok amadan.net/oficina/calls/internal/calls/apns 1.056s ok amadan.net/oficina/calls/internal/calls/hub 10.648s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 1.068s ok amadan.net/oficina/calls/internal/calls/seal 1.032s ok amadan.net/oficina/calls/internal/callstest 88.935s
diff --git a/internal/calls/static/calls.css b/internal/calls/static/calls.css| index a3e62ee..da75362 100644 |
| --- a/internal/calls/static/calls.css |
| +++ b/internal/calls/static/calls.css |
| @@ -325,8 +325,10 @@ body:has(> .calls-window) { background: var(--rst-bg); } |
| position: absolute; |
| z-index: -1; |
| inset: 0 0 auto; |
| - block-size: 96px; |
| - background: linear-gradient(rgb(0 0 0 / 0.5), rgb(0 0 0 / 0)); |
| + block-size: 112px; |
| + /* Dark enough through the strip itself for white text over a white |
| + picture (4.5:1, the window scenario's contrast check), then fading. */ |
| + background: linear-gradient(rgb(0 0 0 / 0.72) 0, rgb(0 0 0 / 0.62) 44px, rgb(0 0 0 / 0)); |
| pointer-events: none; |
| } |
| .calls-top h1 { |
diff --git a/native/apple/Sources/CallsKit/MacFullWindowCall.swift b/native/apple/Sources/CallsKit/MacFullWindowCall.swift| index 733f722..c0a071d 100644 |
| --- a/native/apple/Sources/CallsKit/MacFullWindowCall.swift |
| +++ b/native/apple/Sources/CallsKit/MacFullWindowCall.swift |
| @@ -98,10 +98,16 @@ |
| /// A shade down from the top edge while the controls show, so the |
| /// traffic lights and the title read over a bright picture. |
| private var topShade: some View { |
| + // Dark enough through the title bar's strip for a named meeting's |
| + // title, white, over a white picture (4.5:1), then fading. |
| LinearGradient( |
| - colors: [.black.opacity(0.5), .black.opacity(0)], startPoint: .top, endPoint: .bottom |
| + stops: [ |
| + .init(color: .black.opacity(0.72), location: 0), |
| + .init(color: .black.opacity(0.62), location: Self.titleBarHeight / 112), |
| + .init(color: .black.opacity(0), location: 1), |
| + ], startPoint: .top, endPoint: .bottom |
| ) |
| - .frame(height: 96) |
| + .frame(height: 112) |
| .frame(maxHeight: .infinity, alignment: .top) |
| .opacity(chrome.visible ? 1 : 0) |
| .allowsHitTesting(false) |
diff --git a/native/ios/UITests/CallsPhoneUITests.swift b/native/ios/UITests/CallsPhoneUITests.swift| index 92525ab..807feba 100644 |
| --- a/native/ios/UITests/CallsPhoneUITests.swift |
| +++ b/native/ios/UITests/CallsPhoneUITests.swift |
| @@ -210,6 +210,27 @@ final class CallsPhoneUITests: XCTestCase { |
| XCTAssertTrue(app.textFields["chatComposer"].waitForExistence(timeout: 5)) |
| } |
| + /// No title pill (2026-10-02): a meeting someone named shows its title, |
| + /// small and centred at the top, and nothing about how the call is going |
| + /// beside it; a meeting Calls named ("Call, 24 Sep 10:21") shows none. |
| + @MainActor func testTheTitleShowsOnlyForANamedMeeting() throws { |
| + XCUIDevice.shared.orientation = .portrait |
| + let named = launchCall("named", ["-CallsFixtureControls", "shown"]) |
| + let title = named.staticTexts["callTitle"] |
| + XCTAssertTrue(title.waitForExistence(timeout: 15)) |
| + XCTAssertEqual(title.label, "Board meeting") |
| + XCTAssertEqual(title.frame.midX, named.frame.midX, accuracy: 2, "centred") |
| + XCTAssertGreaterThan(title.frame.minY, 44, "below the status bar: inside the safe area") |
| + XCTAssertFalse(named.staticTexts["In the meeting"].exists, "no pill saying how the call is going") |
| + try snapshot(named, "named-controls-portrait-light") |
| + let auto = launchCall("focus", ["-CallsFixtureControls", "shown"]) |
| + XCTAssertTrue(auto.buttons["leave"].waitForExistence(timeout: 15)) |
| + XCTAssertFalse(auto.staticTexts["callTitle"].exists, "an automatic title is not shown") |
| + XCTAssertFalse(auto.staticTexts["Call, 24 Sep 10:21"].exists) |
| + XCTAssertFalse(auto.staticTexts["In the meeting"].exists) |
| + try snapshot(auto, "auto-controls-portrait-light") |
| + } |
| + |
| /// The snapshots of the full-screen call and the classic one, light and |
| /// dark, portrait and landscape. |
| @MainActor func testFullScreenCallSnapshots() throws { |
| @@ -219,7 +240,10 @@ final class CallsPhoneUITests: XCTestCase { |
| for orientation in [UIDeviceOrientation.portrait, .landscapeLeft] { |
| XCUIDevice.shared.orientation = orientation |
| let turn = orientation == .portrait ? "portrait" : "landscape" |
| - for (fixture, shown) in [("focus", true), ("focus", false), ("grid", true), ("screen", false)] { |
| + for (fixture, shown) in [ |
| + ("focus", true), ("focus", false), ("grid", true), ("screen", false), ("named", true), |
| + ("named", false), |
| + ] { |
| let app = launchCall( |
| fixture, ["-CallsFixtureControls", shown ? "shown" : "hidden"], dark: dark) |
| XCTAssertTrue(app.descendants(matching: .any)["callStage"].firstMatch.waitForExistence(timeout: 15)) |
diff --git a/native/ios/project.yml b/native/ios/project.yml| index 55ad1f9..e82d837 100644 |
| --- a/native/ios/project.yml |
| +++ b/native/ios/project.yml |
| @@ -10,7 +10,7 @@ settings: |
| base: |
| SWIFT_VERSION: '5.0' |
| CODE_SIGN_STYLE: Automatic |
| - MARKETING_VERSION: '0.4.6' |
| + MARKETING_VERSION: '0.4.7' |
| CURRENT_PROJECT_VERSION: '1' |
| # The App Group the app and its broadcast extension share: the socket |
| # the iPhone's screen crosses lives in its container. One setting |
diff --git a/test/browser/call.mjs b/test/browser/call.mjs| index cb87c5d..250627b 100644 |
| --- a/test/browser/call.mjs |
| +++ b/test/browser/call.mjs |
| @@ -5494,7 +5494,7 @@ const run = { |
| await pageA.mouse.click(cx, cy); |
| check(await pageA.locator('#layout-panel').isHidden(), 'a click elsewhere closes it'); |
| await pageA.locator('#call-more-toggle').click(); |
| - const end = pageA.locator('#call-more a'); |
| + const end = pageA.locator('#call-more a.calls-menu-danger'); |
| check((await end.innerText()).trim() === 'End meeting for everyone' && |
| (await end.getAttribute('href')) === '/meetings/' + sqid + '/end' && (await end.getAttribute('target')) === '_blank', |
| 'More holds End meeting for everyone, a link to its own confirm page'); |
| @@ -5607,7 +5607,7 @@ const run = { |
| }, kind); |
| const surfaces = [ |
| ['the bar', async () => { const b = await box(pageA, '.calls-bar-spacer'); return [b.x + b.width / 2, b.y + b.height / 2]; }], |
| - // Below the menu button, where the title has no words. |
| + // The foot of the title strip, beside the words: its scrim. |
| ['the title', async () => { const b = await box(pageA, '#call-top'); return [b.x + 19, b.y + b.height - 5]; }], |
| ]; |
| const ratios = {}; |