Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/calendar-macos/Compass/ParityViewSnapshotExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ enum ParityViewSnapshotExporter {
context.cgContext.clear(CGRect(x: 0, y: 0, width: pixelWidth, height: pixelHeight))
enableLayerBackedDrawing(contentView)
contentView.cacheDisplay(in: captureRect, to: bitmap)
NSGraphicsContext.current = context
drawPopoverWindows(over: window)

let folder = URL(fileURLWithPath: directory, isDirectory: true)
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
Expand All @@ -73,6 +75,33 @@ enum ParityViewSnapshotExporter {
NativeAccessibilityProbes.publishParityExport(surface: surface, on: window)
}

/// Popovers (the form's date picker and time list) are separate windows,
/// so `cacheDisplay` of the content view leaves them out and the capture
/// showed a closed field. Draw each visible one on top at its offset from
/// the main window's content, as it appears on screen.
@MainActor
private static func drawPopoverWindows(over window: NSWindow) {
let content = window.contentRect(forFrameRect: window.frame)
let popovers = (window.childWindows ?? []) + NSApp.windows.filter {
String(describing: type(of: $0)).contains("Popover")
}
var drawn = Set<ObjectIdentifier>()
for popover in popovers where popover !== window && popover.isVisible {
guard drawn.insert(ObjectIdentifier(popover)).inserted,
popover.frame.intersects(window.frame),
let view = popover.contentView,
let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds)
else { continue }
view.cacheDisplay(in: view.bounds, to: rep)
let frame = popover.contentRect(forFrameRect: popover.frame)
rep.draw(in: NSRect(
x: frame.minX - content.minX,
y: frame.minY - content.minY,
width: frame.width,
height: frame.height))
}
}

/// Full window PNG including traffic lights for marketing title-bar crop (CI).
@MainActor
static func exportTitleBarReference(from window: NSWindow?) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum ParityCaptureSurface: String, CaseIterable, Sendable {
case commandPalette = "command-palette"
case settings = "settings"
case onboardingWelcome = "onboarding-welcome"
case onboardingWelcomeAccountChoice = "onboarding-welcome-account-choice"

public static func isValid(_ name: String) -> Bool {
ParityCaptureSurface(rawValue: name) != nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ final class DesktopDebugCommandTests: XCTestCase {
XCTAssertEqual(
DesktopDebugCommand.parse("compass://debug/parity-export-title-bar-reference"),
.parityExportTitleBarReference)
// Every name NativeParityCaptureTests exports must parse, or the export times out.
XCTAssertEqual(
DesktopDebugCommand.parse("compass://debug/parity-export?name=onboarding-welcome-account-choice"),
.parityExport(surface: "onboarding-welcome-account-choice"))
XCTAssertNil(DesktopDebugCommand.parse("compass://debug/parity-export?name=not-a-surface"))

XCTAssertNil(DesktopDebugCommand.parse("compass://debug/unknown"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ struct WelcomeModalView: View {
.shadow(color: .black.opacity(0.15), radius: 6, y: 3)
}
.buttonStyle(.plain)
// The keycap is a visual hint; the title alone names the button.
.accessibilityLabel(title)
}

private func secondaryButton(title: String, hint: String, action: @escaping () -> Void) -> some View {
Expand All @@ -195,6 +197,8 @@ struct WelcomeModalView: View {
.overlay(Capsule().stroke(theme.borderColor, lineWidth: 1))
}
.buttonStyle(.plain)
// The keycap is a visual hint; the title alone names the button.
.accessibilityLabel(title)
}

private func quietTextButton(title: String, hint: String, action: @escaping () -> Void) -> some View {
Expand All @@ -208,6 +212,8 @@ struct WelcomeModalView: View {
.frame(maxWidth: .infinity, minHeight: 32)
}
.buttonStyle(.plain)
// The keycap is a visual hint; the title alone names the button.
.accessibilityLabel(title)
}

private func pillButton(title: String, hint: String, action: @escaping () -> Void) -> some View {
Expand All @@ -225,5 +231,7 @@ struct WelcomeModalView: View {
.overlay(Capsule().stroke(theme.borderColor, lineWidth: 1))
}
.buttonStyle(.plain)
// The keycap is a visual hint; the title alone names the button.
.accessibilityLabel(title)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,17 @@ final class NativeParityCaptureTests: XCTestCase {
app.launch()
let window = app.windows["Compass"]
XCTAssertTrue(window.waitForExistence(timeout: 15))
XCTAssertTrue(
window.descendants(matching: .any)["compass-native-welcome-modal"].waitForExistence(timeout: 15))
let welcome = window.descendants(matching: .any)["compass-native-welcome-modal"]
XCTAssertTrue(welcome.waitForExistence(timeout: 15))
sleep(1)
NativeImageSnapshotSupport.captureWindow(window, named: "onboarding-welcome")
window.buttons["Get started for free"].click()
window.buttons["Next"].click()
XCTAssertTrue(
window.buttons["Start free trial with Google"].waitForExistence(timeout: 8))
// Scope to the panel: the grid's own "Next" (week) button is still in
// the window's accessibility tree behind the modal.
welcome.buttons["Get started for free"].click()
welcome.buttons["Next"].click()
// The provider buttons depend on the backend config; the local-only
// choice is always on the account step.
XCTAssertTrue(welcome.buttons["On this device only"].waitForExistence(timeout: 8))
sleep(1)
NativeImageSnapshotSupport.captureWindow(window, named: "onboarding-welcome-account-choice")
}
Expand Down
2 changes: 2 additions & 0 deletions docs/features/desktop-web-parity-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ Each PR should link a child issue, include before/after screenshots (web Chromiu
| 2026-10-21 | Hold-Mod sidebar chips: month picker and Up next chips render, calendar list chip moved onto the list (compare run 39634795849) |
| 2026-10-21 | Per-account page-jump targets (web `calendar-account:` ids) |
| 2026-10-21 | Event jump chips use web day-prefix labels (W1, SU2) and digits after a day pick |
| 2026-10-22 | Parity capture draws popover windows (form date picker, time list) over the window render, so those surfaces stop showing a closed field |
| 2026-10-22 | macOS capture: register `onboarding-welcome-account-choice` so the welcome account step exports instead of timing out |

---

Expand Down
Loading