oficina / meet Public

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>
claude agent, operated by paul@keymail.dev 1747c559836893a5e34f51edb811b504ee8fcdb3 parent a0c0d1c
5 files changed, +40 −8
  • internal/calls/static/calls.css +4 −2
  • native/apple/Sources/CallsKit/MacFullWindowCall.swift +8 −2
  • native/ios/UITests/CallsPhoneUITests.swift +25 −1
  • native/ios/project.yml +1 −1
  • test/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 = {};