From b6f96c126192bb43cf277abf30cec99060c7babd Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:23:17 +0900 Subject: [PATCH 01/11] Read Mac calendars through EventKit and add a sample-day onboarding Until can now use the events the Calendar app already syncs (iCloud, Exchange, Google, CalDAV) without signing in. Mac calendars are one more source next to Google accounts: they are listed in Settings grouped by account, default off when the same account is connected to Google, and events that duplicate a Google event are merged away. Google-only actions (adding a Meet link, creating notes) stay on Google events. The first-run popover now offers Mac calendars first, Google sign-in second, and a sample day that shows the timeline before connecting anything. Events also carry up to three non-meeting links from their description or attachments for the upcoming card. --- Sources/Until/AppModel.swift | 191 +++++++- Sources/Until/CalendarClient.swift | 14 +- Sources/Until/MacCalendarSource.swift | 418 ++++++++++++++++++ Sources/Until/Models.swift | 205 +++++++-- .../Resources/en.lproj/Localizable.strings | 24 + .../Resources/ja.lproj/Localizable.strings | 24 + Sources/Until/Views.swift | 298 +++++++++---- Tests/UntilTests/MacCalendarSourceTests.swift | 204 +++++++++ scripts/entitlements/app.entitlements | 2 + scripts/entitlements/mas.entitlements | 2 + scripts/package-app.sh | 4 + 11 files changed, 1233 insertions(+), 153 deletions(-) create mode 100644 Sources/Until/MacCalendarSource.swift create mode 100644 Tests/UntilTests/MacCalendarSourceTests.swift diff --git a/Sources/Until/AppModel.swift b/Sources/Until/AppModel.swift index c39a27b..076e59f 100644 --- a/Sources/Until/AppModel.swift +++ b/Sources/Until/AppModel.swift @@ -1,5 +1,6 @@ import AppKit import Combine +import EventKit import Foundation import OSLog import ServiceManagement @@ -27,6 +28,12 @@ final class AppModel: ObservableObject { @Published var externalSharePrompt: ExternalSharePrompt? @Published private(set) var launchAtLoginEnabled = false @Published private(set) var launchAtLoginError: String? + @Published private(set) var macCalendarAccess: MacCalendarAccess = MacCalendarSource.access() + @Published private(set) var macCalendarError: String? + /// A sample day shown before any calendar is connected, so the timeline can + /// be tried without signing in. Nothing is persisted, notified, or shared + /// with the widget while it is on. + @Published private(set) var isPreviewingSample = false /// Launch-at-login via `SMAppService` only works from a real .app bundle. In /// bare `swift run` dev mode the row is shown disabled — same bundle check @@ -48,6 +55,8 @@ final class AppModel: ObservableObject { private let runtimeOptions: AppRuntimeOptions private let store = ConfigStore() private let notifier = EventNotifier() + private let macCalendars = MacCalendarSource() + private var macCalendarObserver: NSObjectProtocol? private var accounts: [GoogleAuth] = [] private var rawEvents: [CalendarEvent] = [] private var refreshTimer: Timer? @@ -86,6 +95,7 @@ final class AppModel: ObservableObject { return } accounts = KeychainStore.loadTokens().map { GoogleAuth(config: config, token: $0) } + observeMacCalendarChanges() updateAuthState() startTimers() Task { @@ -98,6 +108,96 @@ final class AppModel: ObservableObject { if let wakeObserver { NSWorkspace.shared.notificationCenter.removeObserver(wakeObserver) } + if let macCalendarObserver { + NotificationCenter.default.removeObserver(macCalendarObserver) + } + } + + /// Demo runs and the sample preview both show synthetic data and must not + /// reach Google, the widget, or Notification Center. + private var isDemo: Bool { + runtimeOptions.demoMode || isPreviewingSample + } + + func startSamplePreview() { + guard !runtimeOptions.demoMode, !state.auth.authenticated else { return } + isPreviewingSample = true + loadDemoData(now: Date()) + } + + func endSamplePreview() { + guard isPreviewingSample else { return } + isPreviewingSample = false + rawEvents = [] + calendars = [] + state.lastSync = nil + invalidateCalendarCoverage() + updateAuthState() + reapplyFilter() + Task { + await refreshCalendars() + await refresh() + } + } + + /// Mac calendars are in use once the user turned them on and macOS still + /// grants full read access. + var usesMacCalendars: Bool { + config.macCalendarsEnabled && macCalendarAccess == .authorized + } + + /// Turns on Mac calendars, asking macOS for access first when needed. + func enableMacCalendars() { + guard !runtimeOptions.demoMode else { return } + endSamplePreview() + macCalendarError = nil + Task { + let granted = await macCalendars.requestAccess() + macCalendarAccess = MacCalendarSource.access() + guard granted else { + macCalendarError = loc( + "Until can't read your calendars. Allow access in System Settings → Privacy & Security → Calendars." + ) + return + } + var next = config + next.macCalendarsEnabled = true + saveConfig(next) + updateAuthState() + } + } + + func disableMacCalendars() { + var next = config + next.macCalendarsEnabled = false + saveConfig(next) + updateAuthState() + } + + func openCalendarPrivacySettings() { + let urls = [ + "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Calendars", + "x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars" + ] + for value in urls { + guard let url = URL(string: value), NSWorkspace.shared.open(url) else { continue } + return + } + } + + /// Calendar app edits (and syncs from iCloud/Exchange) land here right away + /// instead of waiting for the next poll. + private func observeMacCalendarChanges() { + macCalendarObserver = NotificationCenter.default.addObserver( + forName: .EKEventStoreChanged, + object: nil, + queue: .main + ) { [weak self] _ in + Task { @MainActor in + guard let self, self.usesMacCalendars else { return } + await self.refresh() + } + } } /// Invalidates the calendar snapshot before a shaping input changes or a @@ -112,7 +212,10 @@ final class AppModel: ObservableObject { guard RuleValidator.validate(next.filterRules) == nil else { return } invalidateCalendarCoverage() config = normalized(next) - if runtimeOptions.demoMode { + if isDemo { + if isPreviewingSample { + persistConfig() + } loadDemoData(now: Date()) startTimers() return @@ -137,7 +240,7 @@ final class AppModel: ObservableObject { var next = config next.skippedMenubarEvents[event.actionKey] = event.endDate config = AppModel.purgingExpiredSkips(next) - if !runtimeOptions.demoMode { + if !isDemo { persistConfig() } reapplyFilter() @@ -148,7 +251,7 @@ final class AppModel: ObservableObject { var next = config next.skippedMenubarEvents.removeValue(forKey: event.actionKey) config = AppModel.purgingExpiredSkips(next) - if !runtimeOptions.demoMode { + if !isDemo { persistConfig() } reapplyFilter() @@ -170,6 +273,7 @@ final class AppModel: ObservableObject { /// in-flight sign-in first so only one OAuth loopback server runs at a /// time. func startLogin() { + endSamplePreview() signInTask?.cancel() signInTask = Task { [weak self] in await self?.login() @@ -252,7 +356,7 @@ final class AppModel: ObservableObject { } func logout(email: String? = nil) async { - guard !runtimeOptions.demoMode else { + guard !isDemo else { loadDemoData(now: Date()) return } @@ -279,19 +383,25 @@ final class AppModel: ObservableObject { state.lastError = nil reapplyFilter() updateAuthState() + if usesMacCalendars { + await refreshCalendars() + await refresh() + } } catch { state.lastError = error.localizedDescription } } func refresh() async { - if runtimeOptions.demoMode { + if isDemo { isRefreshing = true loadDemoData(now: Date()) isRefreshing = false return } - guard !accounts.isEmpty else { + macCalendarAccess = MacCalendarSource.access() + updateAuthState() + guard !accounts.isEmpty || usesMacCalendars else { reapplyFilter() return } @@ -302,11 +412,14 @@ final class AppModel: ObservableObject { let lookaheadHours = config.lookaheadHours calendarRefreshGeneration += 1 let refreshGeneration = calendarRefreshGeneration - let results = await fetchAllAccounts( + var results = await fetchAllAccounts( selectedIds: config.selectedCalendarIds, lookaheadHours: lookaheadHours, now: fetchStart ) + if usesMacCalendars { + results.append(fetchMacCalendars(lookaheadHours: lookaheadHours, now: fetchStart)) + } applyFetchResults( results, fetchStart: fetchStart, @@ -360,6 +473,21 @@ final class AppModel: ObservableObject { } } + /// Mac calendars ride along as one more "account" so a failure there is + /// reported the same way and never discards Google events. + private func fetchMacCalendars(lookaheadHours: Int, now: Date) -> AccountFetchResult { + let calendars = macCalendars.calendars( + selections: config.macCalendarSelections, + googleAccountEmails: accounts.map(\.email) + ) + let events = macCalendars.events( + calendars: calendars.filter(\.selected), + lookaheadHours: lookaheadHours, + now: now + ) + return AccountFetchResult(email: loc("Calendars on this Mac"), calendars: calendars, events: events, error: nil) + } + /// Aggregates per-account results, publishing the merged events/calendars /// only when at least one account succeeded — so a transient outage on /// every account leaves previously cached events (`rawEvents`) untouched @@ -389,7 +517,7 @@ final class AppModel: ObservableObject { } if anySucceeded { - rawEvents = fetchedEvents.sorted { $0.startDate < $1.startDate } + rawEvents = mergingCalendarSources(fetchedEvents.sorted { $0.startDate < $1.startDate }) calendars = fetchedCalendars.sorted { $0.name < $1.name } state.lastSync = fetchStart } @@ -404,13 +532,21 @@ final class AppModel: ObservableObject { } func refreshCalendars() async { - if runtimeOptions.demoMode { + if isDemo { calendars = DemoCalendarData.calendars(selectedIds: config.selectedCalendarIds) return } + let macCalendarList = usesMacCalendars + ? macCalendars.calendars( + selections: config.macCalendarSelections, + googleAccountEmails: accounts.map(\.email) + ) + : [] guard !accounts.isEmpty else { - calendars = [] - invalidateCalendarCoverage() + calendars = macCalendarList.sorted { $0.name < $1.name } + if macCalendarList.isEmpty { + invalidateCalendarCoverage() + } return } let selectedIds = config.selectedCalendarIds @@ -431,7 +567,7 @@ final class AppModel: ObservableObject { } return collected } - calendars = next.sorted { $0.name < $1.name } + calendars = (next + macCalendarList).sorted { $0.name < $1.name } } catch { invalidateCalendarCoverage() state.lastError = error.localizedDescription @@ -439,7 +575,15 @@ final class AppModel: ObservableObject { } func setCalendar(_ id: String, selected: Bool) { + if id.hasPrefix(macCalendarIdPrefix) { + var next = config + next.macCalendarSelections[id] = selected + saveConfig(next) + return + } var ids = Set(config.selectedCalendarIds) + // Keep "all selected" (an empty list) about Google calendars only. + let calendars = self.calendars.filter { $0.source == .google } if ids.isEmpty { ids = Set(calendars.map(\.id)) } @@ -607,8 +751,9 @@ final class AppModel: ObservableObject { } func addConference(for event: CalendarEvent) { + guard event.source == .google else { return } let key = event.actionKey - if runtimeOptions.demoMode { + if isDemo { rawEvents = rawEvents.map { current in guard current.actionKey == key else { return current } var next = current @@ -663,7 +808,7 @@ final class AppModel: ObservableObject { /// Creates an app-managed template Google Doc for the account, stores its id, /// and opens it in the browser for editing. func createTemplateDoc(for accountEmail: String) { - guard !runtimeOptions.demoMode else { + guard !isDemo else { var next = config next.meetingNotesTemplateDocsByAccount[accountEmail] = "demo-template-\(accountEmail)" saveConfig(next) @@ -713,6 +858,9 @@ final class AppModel: ObservableObject { openNote(url: notesUrl, accountEmail: event.account.email) return } + // Notes docs are attached to the Google event; Mac calendar events can + // only open a notes link they already carry. + guard event.source == .google else { return } let external = externalAttendees(for: event) if !external.isEmpty { @@ -804,7 +952,7 @@ final class AppModel: ObservableObject { let notificationEvents = config.notifyVideoOnly ? activeEvents.filter { !$0.conferenceUrl.isEmpty } : activeEvents - guard runtimeOptions.allowsNotifications else { return } + guard runtimeOptions.allowsNotifications, !isPreviewingSample else { return } Task { await notifier.sync( events: notificationEvents, @@ -816,9 +964,9 @@ final class AppModel: ObservableObject { private func publishWidgetSnapshot() { // Demo runs must not replace the real calendar shown by an installed widget. - guard !runtimeOptions.demoMode, let url = WidgetAgendaStore.sharedURL() else { return } + guard !isDemo, let url = WidgetAgendaStore.sharedURL() else { return } let snapshot = WidgetAgendaSnapshot( - authenticated: !accounts.isEmpty, + authenticated: state.auth.authenticated, lastSync: state.lastSync, coverageEnd: state.calendarCoverageEnd, events: (state.allDayEvents + state.events).map { event in @@ -967,10 +1115,11 @@ final class AppModel: ObservableObject { accounts: demoFixture.accountEmails().map { AccountState(email: $0) } ) } else { - calendars = DemoCalendarData.calendars(selectedIds: config.selectedCalendarIds) + let selectedIds = isPreviewingSample ? [] : config.selectedCalendarIds + calendars = DemoCalendarData.calendars(selectedIds: selectedIds) rawEvents = DemoCalendarData.events( now: now, - selectedIds: config.selectedCalendarIds, + selectedIds: selectedIds, scenario: runtimeOptions.demoScenario ) state.auth = DemoCalendarData.accountState() @@ -990,7 +1139,7 @@ final class AppModel: ObservableObject { .map { AccountState(email: $0.email) } .sorted { $0.email < $1.email } state.auth = AuthState( - authenticated: !accountStates.isEmpty, + authenticated: !accountStates.isEmpty || usesMacCalendars, accounts: accountStates ) } @@ -1003,7 +1152,7 @@ final class AppModel: ObservableObject { defer { creatingNoteKey = nil } do { - if runtimeOptions.demoMode { + if isDemo { noteResults[key] = DemoCalendarData.noteResult(for: event) return } diff --git a/Sources/Until/CalendarClient.swift b/Sources/Until/CalendarClient.swift index 90667df..31c633b 100644 --- a/Sources/Until/CalendarClient.swift +++ b/Sources/Until/CalendarClient.swift @@ -116,6 +116,11 @@ final class CalendarClient { let attendees = normalizeAttendees(raw.attendees ?? []) let selfAttendee = attendees.first { $0.selfUser } let noteURL = raw.existingNoteURL + let conference = conferenceURL(for: raw) + let attachmentLinks = (raw.attachments ?? []).compactMap { attachment -> EventLink? in + guard let url = attachment.fileUrl, !url.isEmpty else { return nil } + return EventLink(title: attachment.title?.nilIfEmpty ?? linkTitle(for: url), url: url) + } return CalendarEvent( id: raw.id, @@ -134,11 +139,16 @@ final class CalendarClient { organizer: raw.organizer?.email ?? "", selfResponse: selfAttendee?.responseStatus ?? "none", isRecurring: raw.recurringEventId != nil, - conferenceUrl: conferenceURL(for: raw), + conferenceUrl: conference, notesUrl: noteURL ?? "", colorId: raw.colorId ?? "", transparency: raw.transparency == "transparent" ? "free" : "busy", - htmlLink: raw.htmlLink ?? "" + htmlLink: raw.htmlLink ?? "", + links: eventLinks( + description: raw.description, + attachments: attachmentLinks, + excluding: [conference, noteURL ?? ""] + ) ) } diff --git a/Sources/Until/MacCalendarSource.swift b/Sources/Until/MacCalendarSource.swift new file mode 100644 index 0000000..e004a53 --- /dev/null +++ b/Sources/Until/MacCalendarSource.swift @@ -0,0 +1,418 @@ +import AppKit +import EventKit +import Foundation + +/// Prefix for Mac calendar ids, keeping them apart from Google's +/// `email::calendarId` keys in selections and filter rules. +let macCalendarIdPrefix = "eventkit::" + +func macCalendarKey(_ calendarIdentifier: String) -> String { + macCalendarIdPrefix + calendarIdentifier +} + +enum MacCalendarAccess: Equatable { + case notDetermined + case denied + case restricted + /// macOS 14+ can grant add-only access, which cannot read events. + case writeOnly + case authorized + + var label: String { + switch self { + case .notDetermined: return loc("Not Determined") + case .denied: return loc("Denied") + case .restricted: return loc("Restricted") + case .writeOnly: return loc("Add-only") + case .authorized: return loc("Authorized") + } + } +} + +/// Reads the Calendar app's events through EventKit. Everything stays on the +/// Mac: no sign-in, no network, and read-only. +@MainActor +final class MacCalendarSource { + private let store = EKEventStore() + + static func access() -> MacCalendarAccess { + let status = EKEventStore.authorizationStatus(for: .event) + switch status { + case .notDetermined: return .notDetermined + case .restricted: return .restricted + case .denied: return .denied + default: return grantedAccess(status) + } + } + + /// macOS 14 split the old "authorized" into full and write-only access; + /// only full access can read events. + private static func grantedAccess(_ status: EKAuthorizationStatus) -> MacCalendarAccess { + if #available(macOS 14.0, *) { + return status == .fullAccess ? .authorized : .writeOnly + } + return .authorized + } + + /// Shows the system prompt when access hasn't been decided yet. Returns + /// whether events can be read afterwards. + func requestAccess() async -> Bool { + do { + if #available(macOS 14.0, *) { + _ = try await store.requestFullAccessToEvents() + } else { + _ = try await store.requestAccess(to: .event) + } + } catch { + return false + } + store.reset() + return Self.access() == .authorized + } + + func calendars( + selections: [String: Bool], + googleAccountEmails: [String] + ) -> [CalendarSummary] { + guard Self.access() == .authorized else { return [] } + let googleEmails = Set(googleAccountEmails.map { $0.lowercased() }) + return store.calendars(for: .event).map { calendar in + let key = macCalendarKey(calendar.calendarIdentifier) + let sourceTitle = calendar.source?.title ?? "" + let duplicate = googleEmails.contains(sourceTitle.lowercased()) + return CalendarSummary( + id: key, + googleId: calendar.calendarIdentifier, + name: calendar.title, + primary: false, + backgroundColor: calendar.color.map(hexString(for:)) ?? "#888888", + selected: isMacCalendarSelected(key, duplicatesGoogleAccount: duplicate, selections: selections), + accountEmail: sourceTitle.contains("@") ? sourceTitle : "", + source: .eventKit, + sourceTitle: sourceTitle, + duplicatesGoogleAccount: duplicate + ) + } + } + + func events( + calendars summaries: [CalendarSummary], + lookaheadHours: Int, + now: Date + ) -> [CalendarEvent] { + guard Self.access() == .authorized else { return [] } + let wanted = Set(summaries.map(\.googleId)) + let ekCalendars = store.calendars(for: .event).filter { wanted.contains($0.calendarIdentifier) } + guard !ekCalendars.isEmpty else { return [] } + let byId = Dictionary(summaries.map { ($0.googleId, $0) }, uniquingKeysWith: { first, _ in first }) + // Same window as the Google fetch: from local midnight so today's finished + // events can sit above the now-line. + let start = Calendar.current.startOfDay(for: now) + let end = now.addingTimeInterval(TimeInterval(lookaheadHours) * 3600) + let predicate = store.predicateForEvents(withStart: start, end: end, calendars: ekCalendars) + return store.events(matching: predicate).compactMap { event in + guard let calendar = event.calendar, let summary = byId[calendar.calendarIdentifier] else { return nil } + return MacCalendarEventInput(event).makeEvent(calendar: summary, now: now) + } + .sorted { $0.startDate < $1.startDate } + } +} + +/// Calendars without an explicit choice are on, except those whose account is +/// already connected directly to Google (they would show every event twice). +func isMacCalendarSelected( + _ key: String, + duplicatesGoogleAccount: Bool, + selections: [String: Bool] +) -> Bool { + selections[key] ?? !duplicatesGoogleAccount +} + +/// The EventKit fields Until uses, copied into a plain value so the mapping to +/// `CalendarEvent` is testable without a calendar database. +struct MacCalendarEventInput { + struct Participant { + var email: String + var name: String + var status: String + var isCurrentUser: Bool + var isResource: Bool + } + + var itemIdentifier: String + var eventIdentifier: String? + var title: String? + var notes: String? + var location: String? + var url: String? + var startDate: Date + var endDate: Date + var isAllDay: Bool + var status: String + var isFree: Bool + var isRecurring: Bool + var organizerEmail: String + var participants: [Participant] + + init( + itemIdentifier: String, + eventIdentifier: String? = nil, + title: String?, + notes: String? = nil, + location: String? = nil, + url: String? = nil, + startDate: Date, + endDate: Date, + isAllDay: Bool = false, + status: String = "confirmed", + isFree: Bool = false, + isRecurring: Bool = false, + organizerEmail: String = "", + participants: [Participant] = [] + ) { + self.itemIdentifier = itemIdentifier + self.eventIdentifier = eventIdentifier + self.title = title + self.notes = notes + self.location = location + self.url = url + self.startDate = startDate + self.endDate = endDate + self.isAllDay = isAllDay + self.status = status + self.isFree = isFree + self.isRecurring = isRecurring + self.organizerEmail = organizerEmail + self.participants = participants + } + + init(_ event: EKEvent) { + let status: String + switch event.status { + case .canceled: status = "cancelled" + case .tentative: status = "tentative" + default: status = "confirmed" + } + self.init( + itemIdentifier: event.calendarItemIdentifier, + eventIdentifier: event.eventIdentifier, + title: event.title, + notes: event.notes, + location: event.location, + url: event.url?.absoluteString, + startDate: event.startDate, + endDate: event.endDate, + isAllDay: event.isAllDay, + status: status, + isFree: event.availability == .free, + isRecurring: event.hasRecurrenceRules, + organizerEmail: event.organizer.map { emailAddress(from: $0.url) } ?? "", + participants: (event.attendees ?? []).map { participant in + let email = emailAddress(from: participant.url) + return Participant( + email: email, + name: participant.name ?? email, + status: participantStatus(participant.participantStatus), + isCurrentUser: participant.isCurrentUser, + isResource: participant.participantType == .room || participant.participantType == .resource + ) + } + ) + } + + /// EventKit ends an all-day event either at the next midnight or at + /// 23:59:59 of its last day; Google (and the rest of the app) use an + /// exclusive next-midnight end. + private var normalizedBounds: (start: Date, end: Date) { + guard isAllDay else { return (startDate, endDate) } + let calendar = Calendar.current + let lastDay = calendar.startOfDay(for: max(startDate, endDate.addingTimeInterval(-1))) + return (calendar.startOfDay(for: startDate), calendar.date(byAdding: .day, value: 1, to: lastDay) ?? lastDay) + } + + private var attendees: [Attendee] { + participants.map { + Attendee( + email: $0.email, + name: $0.name, + responseStatus: $0.status, + selfUser: $0.isCurrentUser, + resource: $0.isResource + ) + } + } + + func makeEvent(calendar: CalendarSummary, now: Date) -> CalendarEvent? { + let (start, end) = normalizedBounds + guard end >= start else { return nil } + let attendees = self.attendees + let conference = [url, location, notes] + .lazy + .compactMap { extractMeetingURL($0) } + .first ?? "" + let notesURL = GoogleDocLinks.documentURL(from: notes) ?? "" + let links = eventLinks(description: notes, extra: [url], excluding: [conference, notesURL]) + return CalendarEvent( + id: "\(itemIdentifier)::\(Int(start.timeIntervalSince1970))", + title: (title?.isEmpty == false ? title : nil) ?? "(no title)", + description: notes ?? "", + location: location ?? "", + startISO: ISO8601DateFormatter.fallback.string(from: start), + endISO: ISO8601DateFormatter.fallback.string(from: end), + allDay: isAllDay, + status: status, + startMinutesFromNow: Int((start.timeIntervalSince(now) / 60).rounded()), + durationMinutes: max(0, Int((end.timeIntervalSince(start) / 60).rounded())), + calendar: CalendarRef( + id: calendar.id, + googleId: calendar.googleId, + primary: false, + backgroundColor: calendar.backgroundColor, + source: .eventKit + ), + account: AccountRef(email: calendar.accountEmail), + attendees: attendees, + organizer: organizerEmail, + selfResponse: attendees.first(where: \.selfUser)?.responseStatus ?? "none", + isRecurring: isRecurring, + conferenceUrl: conference, + notesUrl: notesURL, + colorId: "", + transparency: isFree ? "free" : "busy", + htmlLink: calendarAppURL(eventIdentifier: eventIdentifier) ?? "", + links: links + ) + } +} + +/// Opens the event in the Calendar app (the scheme Calendar registers for +/// its own events). +private func calendarAppURL(eventIdentifier: String?) -> String? { + guard let eventIdentifier, !eventIdentifier.isEmpty else { return nil } + // Identifiers look like "UUID:UUID"; Calendar expects the colon as is. + let allowed = CharacterSet.urlPathAllowed.union(CharacterSet(charactersIn: ":")) + let encoded = eventIdentifier.addingPercentEncoding(withAllowedCharacters: allowed) ?? eventIdentifier + return "ical://ekevent/\(encoded)?method=show&options=more" +} + +private func emailAddress(from url: URL) -> String { + let raw = url.absoluteString + guard raw.lowercased().hasPrefix("mailto:") else { return "" } + let address = String(raw.dropFirst("mailto:".count)) + return (address.removingPercentEncoding ?? address).lowercased() +} + +private func participantStatus(_ status: EKParticipantStatus) -> String { + switch status { + case .accepted, .delegated, .completed, .inProcess: return "accepted" + case .declined: return "declined" + case .tentative: return "tentative" + default: return "needsAction" + } +} + +private func hexString(for color: NSColor) -> String { + guard let rgb = color.usingColorSpace(.sRGB) else { return "#888888" } + let red = Int((rgb.redComponent * 255).rounded()) + let green = Int((rgb.greenComponent * 255).rounded()) + let blue = Int((rgb.blueComponent * 255).rounded()) + return String(format: "#%02X%02X%02X", red, green, blue) +} + +/// Mac calendars mirror accounts the user may also connect to Google directly, +/// and the same invitation can land in two Mac calendars. Google copies win +/// (they carry notes and Meet actions); otherwise the first copy wins. +func mergingCalendarSources(_ events: [CalendarEvent]) -> [CalendarEvent] { + func key(_ event: CalendarEvent) -> String { + let title = event.title.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return "\(title)|\(event.startDate.timeIntervalSince1970)|\(event.endDate.timeIntervalSince1970)|\(event.allDay)" + } + let googleKeys = Set(events.filter { $0.source == .google }.map(key)) + var seenMacKeys: Set = [] + return events.filter { event in + guard event.source == .eventKit else { return true } + let eventKey = key(event) + guard !googleKeys.contains(eventKey) else { return false } + return seenMacKeys.insert(eventKey).inserted + } +} + +/// Up to three links from an event's description and attachments that are not +/// the meeting link itself — the doc, design, or PR the meeting is about. +func eventLinks( + description: String?, + attachments: [EventLink] = [], + extra: [String?] = [], + excluding excluded: [String] +) -> [EventLink] { + var seen = Set(excluded) + var links: [EventLink] = [] + func append(_ link: EventLink) { + guard links.count < 3, !link.url.isEmpty, seen.insert(link.url).inserted else { return } + guard EventLinks.meetingProvider(for: link.url) == nil else { return } + links.append(link) + } + attachments.forEach(append) + let candidates = extra.compactMap { $0 } + allURLs(in: description) + for url in candidates { + append(EventLink(title: linkTitle(for: url), url: url)) + } + return links +} + +private func allURLs(in text: String?) -> [String] { + guard let text, !text.isEmpty else { return [] } + let decoded = decodeHtmlEntities(text) + let pattern = #"\bhttps?://[^\s<>"')\]}]+"# + guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] } + let range = NSRange(decoded.startIndex.. String { + guard let url = URL(string: rawURL), let host = url.host?.lowercased() else { return rawURL } + if host == "docs.google.com", let kind = googleDocKinds.first(where: { url.path.hasPrefix($0.path) }) { + return kind.name + } + if host == "github.com" { + return gitHubLinkTitle(path: url.path) + } + if let product = linkProductNames.first(where: { host == $0.domain || host.hasSuffix("." + $0.domain) }) { + return product.name + } + return host.hasPrefix("www.") ? String(host.dropFirst(4)) : host +} + +private func gitHubLinkTitle(path: String) -> String { + let parts = path.split(separator: "/") + guard parts.count >= 4 else { return "GitHub" } + switch parts[2] { + case "pull": return "PR #\(parts[3])" + case "issues": return "Issue #\(parts[3])" + default: return "GitHub" + } +} diff --git a/Sources/Until/Models.swift b/Sources/Until/Models.swift index a5c28c5..ff1614e 100644 --- a/Sources/Until/Models.swift +++ b/Sources/Until/Models.swift @@ -8,11 +8,27 @@ struct Attendee: Hashable { var resource: Bool } +/// Where an event or calendar comes from. Google events carry the richer +/// Google-only actions (Meet links, notes docs); Mac calendars are read +/// through EventKit and never leave the machine. +enum EventSource: String, Hashable, Codable { + case google + case eventKit +} + struct CalendarRef: Hashable { var id: String var googleId: String var primary: Bool var backgroundColor: String + var source: EventSource = .google +} + +/// A non-meeting link found on an event (a spec doc, a design file, a pull +/// request) so the card can surface what the meeting is about. +struct EventLink: Hashable { + var title: String + var url: String } struct AccountRef: Hashable { @@ -40,6 +56,7 @@ struct CalendarEvent: Identifiable, Hashable { var colorId: String var transparency: String var htmlLink: String + var links: [EventLink] // Parsed once at construction from the source ISO strings. These were previously // computed properties that reparsed the ISO strings on every access; they are @@ -50,6 +67,8 @@ struct CalendarEvent: Identifiable, Hashable { var actionKey: String { "\(account.email)::\(calendar.googleId)::\(id)" } + var source: EventSource { calendar.source } + /// Designated init taking the ISO strings; dates are parsed exactly once here. /// Returns nil if either bound fails to parse, so callers can drop the event /// instead of substituting a bogus date (which would render as "happening now"). @@ -74,7 +93,8 @@ struct CalendarEvent: Identifiable, Hashable { notesUrl: String, colorId: String, transparency: String, - htmlLink: String + htmlLink: String, + links: [EventLink] = [] ) { guard let start = ISO8601DateFormatter.shared.date(fromAnyInternetDate: startISO), let end = ISO8601DateFormatter.shared.date(fromAnyInternetDate: endISO) else { @@ -100,6 +120,7 @@ struct CalendarEvent: Identifiable, Hashable { self.colorId = colorId self.transparency = transparency self.htmlLink = htmlLink + self.links = links self.startDate = start self.endDate = end } @@ -138,12 +159,16 @@ struct EventActionSet: Equatable { attached.append(.openMeetingNotes) } + // Adding a Meet link or a notes doc writes to the Google event, so Mac + // calendar events only get the attached and common actions. var addable: [Item] = [] - if !hasConference && !event.allDay { - addable.append(.addGoogleMeet) - } - if !hasNotes { - addable.append(.createNotes) + if event.source == .google { + if !hasConference && !event.allDay { + addable.append(.addGoogleMeet) + } + if !hasNotes { + addable.append(.createNotes) + } } return EventActionSet( @@ -264,6 +289,12 @@ struct CalendarSummary: Identifiable, Hashable { var backgroundColor: String var selected: Bool var accountEmail: String + var source: EventSource = .google + /// The macOS account a Mac calendar belongs to ("iCloud", an email, …). + var sourceTitle: String = "" + /// A Mac calendar whose account is also connected directly to Google, so + /// its events would otherwise appear twice. Off by default. + var duplicatesGoogleAccount: Bool = false } struct AccountState: Identifiable, Hashable { @@ -515,6 +546,25 @@ struct AppConfig: Codable, Hashable { /// value is the event's `endDate`, kept only so expired entries can be /// purged automatically instead of accumulating forever. var skippedMenubarEvents: [String: Date] + /// Read events from the Calendar app through EventKit (iCloud, Exchange, + /// Google, CalDAV — whatever macOS already syncs). No sign-in needed. + var macCalendarsEnabled: Bool + /// Explicit on/off choices for Mac calendars, keyed by `CalendarSummary.id`. + /// Calendars without a choice follow the default (on, unless the account is + /// also connected directly through Google). + var macCalendarSelections: [String: Bool] + /// A second global shortcut that joins the current or next meeting. + var joinHotkeyEnabled: Bool + var joinHotkeyPreset: String + /// Open Zoom and Teams links in their desktop apps when installed. + var openMeetingsInApps: Bool + /// "off", "floating", or "fullScreen": an on-screen alert as a meeting starts. + var startAlertStyle: String + var startAlertLeadMinutes: Int + var startAlertVideoOnly: Bool + /// Show the event title next to the countdown. Off keeps only the time, + /// which is handy while presenting. + var menubarShowsTitle: Bool // Google OAuth client credentials, injected at build/package time rather than // committed to source. Packaged builds read them from Info.plist (written by @@ -536,6 +586,21 @@ struct AppConfig: Codable, Hashable { .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" } + /// Build-time credentials win over persisted values so rotating .env takes + /// effect without clearing config. + private static func decodedCredentials( + from container: KeyedDecodingContainer, + defaults: AppConfig + ) throws -> (clientId: String, clientSecret: String) { + let oauth = try container.nestedContainer(keyedBy: OAuthKeys.self, forKey: .oauth) + let storedClientId = try oauth.decodeIfPresent(String.self, forKey: .clientId) + let storedClientSecret = try oauth.decodeIfPresent(String.self, forKey: .clientSecret) + return ( + resolvedCredential(buildTime: defaults.oauthClientId, stored: storedClientId), + resolvedCredential(buildTime: defaults.oauthClientSecret, stored: storedClientSecret) + ) + } + private static func resolvedCredential(buildTime: String, stored: String?) -> String { if !buildTime.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return buildTime } let stored = stored?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" @@ -585,7 +650,16 @@ struct AppConfig: Codable, Hashable { meetingNotesTitleTemplatesByAccount: [:], meetingNotesTemplateDocsByAccount: [:], didApplyDefaultLaunchAtLogin: false, - skippedMenubarEvents: [:] + skippedMenubarEvents: [:], + macCalendarsEnabled: false, + macCalendarSelections: [:], + joinHotkeyEnabled: false, + joinHotkeyPreset: "ctrl-opt-j", + openMeetingsInApps: false, + startAlertStyle: StartAlertStyle.off.rawValue, + startAlertLeadMinutes: 1, + startAlertVideoOnly: true, + menubarShowsTitle: true ) enum CodingKeys: String, CodingKey { @@ -601,6 +675,11 @@ struct AppConfig: Codable, Hashable { case meetingNotesTemplateDocsByAccount case didApplyDefaultLaunchAtLogin case skippedMenubarEvents + case macCalendarsEnabled, macCalendarSelections + case joinHotkeyEnabled, joinHotkeyPreset + case openMeetingsInApps + case startAlertStyle, startAlertLeadMinutes, startAlertVideoOnly + case menubarShowsTitle } enum OAuthKeys: String, CodingKey { @@ -628,7 +707,16 @@ struct AppConfig: Codable, Hashable { meetingNotesTitleTemplatesByAccount: [String: String], meetingNotesTemplateDocsByAccount: [String: String], didApplyDefaultLaunchAtLogin: Bool = false, - skippedMenubarEvents: [String: Date] = [:] + skippedMenubarEvents: [String: Date] = [:], + macCalendarsEnabled: Bool = false, + macCalendarSelections: [String: Bool] = [:], + joinHotkeyEnabled: Bool = false, + joinHotkeyPreset: String = "ctrl-opt-j", + openMeetingsInApps: Bool = false, + startAlertStyle: String = StartAlertStyle.off.rawValue, + startAlertLeadMinutes: Int = 1, + startAlertVideoOnly: Bool = true, + menubarShowsTitle: Bool = true ) { self.oauthClientId = oauthClientId self.oauthClientSecret = oauthClientSecret @@ -651,60 +739,66 @@ struct AppConfig: Codable, Hashable { self.meetingNotesTemplateDocsByAccount = meetingNotesTemplateDocsByAccount self.didApplyDefaultLaunchAtLogin = didApplyDefaultLaunchAtLogin self.skippedMenubarEvents = skippedMenubarEvents + self.macCalendarsEnabled = macCalendarsEnabled + self.macCalendarSelections = macCalendarSelections + self.joinHotkeyEnabled = joinHotkeyEnabled + self.joinHotkeyPreset = joinHotkeyPreset + self.openMeetingsInApps = openMeetingsInApps + self.startAlertStyle = startAlertStyle + self.startAlertLeadMinutes = startAlertLeadMinutes + self.startAlertVideoOnly = startAlertVideoOnly + self.menubarShowsTitle = menubarShowsTitle } init(from decoder: Decoder) throws { let defaults = AppConfig.default let container = try decoder.container(keyedBy: CodingKeys.self) - let oauth = try container.nestedContainer(keyedBy: OAuthKeys.self, forKey: .oauth) - let storedClientId = try oauth.decodeIfPresent(String.self, forKey: .clientId) - let storedClientSecret = try oauth.decodeIfPresent(String.self, forKey: .clientSecret) - // Build-time credentials win over persisted values so rotating .env takes - // effect without clearing config. - oauthClientId = Self.resolvedCredential(buildTime: defaults.oauthClientId, stored: storedClientId) - oauthClientSecret = Self.resolvedCredential(buildTime: defaults.oauthClientSecret, stored: storedClientSecret) - filterRules = try container.decode(.filterRules, default: defaults.filterRules) - selectedCalendarIds = try container.decode(.selectedCalendarIds, default: defaults.selectedCalendarIds) - lookaheadHours = try container.decode(.lookaheadHours, default: defaults.lookaheadHours) - pollIntervalSeconds = try container.decode(.pollIntervalSeconds, default: defaults.pollIntervalSeconds) - maxTitleLength = try container.decode(.maxTitleLength, default: defaults.maxTitleLength) + (oauthClientId, oauthClientSecret) = try Self.decodedCredentials(from: container, defaults: defaults) + filterRules = try container.decode(.filterRules, or: \.filterRules) + selectedCalendarIds = try container.decode(.selectedCalendarIds, or: \.selectedCalendarIds) + lookaheadHours = try container.decode(.lookaheadHours, or: \.lookaheadHours) + pollIntervalSeconds = try container.decode(.pollIntervalSeconds, or: \.pollIntervalSeconds) + maxTitleLength = try container.decode(.maxTitleLength, or: \.maxTitleLength) menubarLeadMinutes = AppConfig.snappedMenubarLead( - try container.decode(.menubarLeadMinutes, default: defaults.menubarLeadMinutes) + try container.decode(.menubarLeadMinutes, or: \.menubarLeadMinutes) ) - menubarShowsNextAlways = try container.decode(.menubarShowsNextAlways, default: defaults.menubarShowsNextAlways) - menubarPrefersImminentNext = try container.decode( - .menubarPrefersImminentNext, - default: defaults.menubarPrefersImminentNext - ) - notifyEnabled = try container.decode(.notifyEnabled, default: defaults.notifyEnabled) - notifyVideoOnly = try container.decode(.notifyVideoOnly, default: defaults.notifyVideoOnly) - notifyLeadMinutes = try container.decode(.notifyLeadMinutes, default: defaults.notifyLeadMinutes) - hotkeyEnabled = try container.decode(.hotkeyEnabled, default: defaults.hotkeyEnabled) - hotkeyPreset = try container.decode(.hotkeyPreset, default: defaults.hotkeyPreset) + menubarShowsNextAlways = try container.decode(.menubarShowsNextAlways, or: \.menubarShowsNextAlways) + menubarPrefersImminentNext = try container.decode(.menubarPrefersImminentNext, or: \.menubarPrefersImminentNext) + notifyEnabled = try container.decode(.notifyEnabled, or: \.notifyEnabled) + notifyVideoOnly = try container.decode(.notifyVideoOnly, or: \.notifyVideoOnly) + notifyLeadMinutes = try container.decode(.notifyLeadMinutes, or: \.notifyLeadMinutes) + hotkeyEnabled = try container.decode(.hotkeyEnabled, or: \.hotkeyEnabled) + hotkeyPreset = try container.decode(.hotkeyPreset, or: \.hotkeyPreset) meetingNotesFoldersByAccount = try container.decode( .meetingNotesFoldersByAccount, - default: defaults.meetingNotesFoldersByAccount + or: \.meetingNotesFoldersByAccount ) meetingNotesFolderNamesByAccount = try container.decode( .meetingNotesFolderNamesByAccount, - default: defaults.meetingNotesFolderNamesByAccount + or: \.meetingNotesFolderNamesByAccount ) meetingNotesTitleTemplatesByAccount = try container.decode( .meetingNotesTitleTemplatesByAccount, - default: defaults.meetingNotesTitleTemplatesByAccount + or: \.meetingNotesTitleTemplatesByAccount ) meetingNotesTemplateDocsByAccount = try container.decode( .meetingNotesTemplateDocsByAccount, - default: defaults.meetingNotesTemplateDocsByAccount + or: \.meetingNotesTemplateDocsByAccount ) didApplyDefaultLaunchAtLogin = try container.decode( .didApplyDefaultLaunchAtLogin, - default: defaults.didApplyDefaultLaunchAtLogin - ) - skippedMenubarEvents = try container.decode( - .skippedMenubarEvents, - default: defaults.skippedMenubarEvents + or: \.didApplyDefaultLaunchAtLogin ) + skippedMenubarEvents = try container.decode(.skippedMenubarEvents, or: \.skippedMenubarEvents) + macCalendarsEnabled = try container.decode(.macCalendarsEnabled, or: \.macCalendarsEnabled) + macCalendarSelections = try container.decode(.macCalendarSelections, or: \.macCalendarSelections) + joinHotkeyEnabled = try container.decode(.joinHotkeyEnabled, or: \.joinHotkeyEnabled) + joinHotkeyPreset = try container.decode(.joinHotkeyPreset, or: \.joinHotkeyPreset) + openMeetingsInApps = try container.decode(.openMeetingsInApps, or: \.openMeetingsInApps) + startAlertStyle = try container.decode(.startAlertStyle, or: \.startAlertStyle) + startAlertLeadMinutes = try container.decode(.startAlertLeadMinutes, or: \.startAlertLeadMinutes) + startAlertVideoOnly = try container.decode(.startAlertVideoOnly, or: \.startAlertVideoOnly) + menubarShowsTitle = try container.decode(.menubarShowsTitle, or: \.menubarShowsTitle) } func encode(to encoder: Encoder) throws { @@ -733,12 +827,39 @@ struct AppConfig: Codable, Hashable { try container.encode(meetingNotesTemplateDocsByAccount, forKey: .meetingNotesTemplateDocsByAccount) try container.encode(didApplyDefaultLaunchAtLogin, forKey: .didApplyDefaultLaunchAtLogin) try container.encode(skippedMenubarEvents, forKey: .skippedMenubarEvents) + try container.encode(macCalendarsEnabled, forKey: .macCalendarsEnabled) + try container.encode(macCalendarSelections, forKey: .macCalendarSelections) + try container.encode(joinHotkeyEnabled, forKey: .joinHotkeyEnabled) + try container.encode(joinHotkeyPreset, forKey: .joinHotkeyPreset) + try container.encode(openMeetingsInApps, forKey: .openMeetingsInApps) + try container.encode(startAlertStyle, forKey: .startAlertStyle) + try container.encode(startAlertLeadMinutes, forKey: .startAlertLeadMinutes) + try container.encode(startAlertVideoOnly, forKey: .startAlertVideoOnly) + try container.encode(menubarShowsTitle, forKey: .menubarShowsTitle) + } +} + +/// How a meeting announces itself on screen as it starts. +enum StartAlertStyle: String, CaseIterable, Identifiable { + case off + case floating + case fullScreen + + var id: String { rawValue } + + var label: String { + switch self { + case .off: return loc("Off") + case .floating: return loc("Floating") + case .fullScreen: return loc("Full screen") + } } } private extension KeyedDecodingContainer where Key == AppConfig.CodingKeys { - func decode(_ key: Key, default defaultValue: T) throws -> T { - try decodeIfPresent(T.self, forKey: key) ?? defaultValue + /// Missing keys (older config files) fall back to the default config. + func decode(_ key: Key, or defaultValue: KeyPath) throws -> T { + try decodeIfPresent(T.self, forKey: key) ?? AppConfig.default[keyPath: defaultValue] } } diff --git a/Sources/Until/Resources/en.lproj/Localizable.strings b/Sources/Until/Resources/en.lproj/Localizable.strings index a719de9..2b08ee8 100644 --- a/Sources/Until/Resources/en.lproj/Localizable.strings +++ b/Sources/Until/Resources/en.lproj/Localizable.strings @@ -340,3 +340,27 @@ "Return to Until." = "Return to Until."; "Login failed" = "Login failed"; "No authorization code returned." = "No authorization code returned."; + +/* MARK: - Calendars on this Mac, onboarding, sample day */ +"Calendars on this Mac" = "Calendars on this Mac"; +"Events from the Calendar app — iCloud, Exchange, Google, and more. No sign-in." = "Events from the Calendar app — iCloud, Exchange, Google, and more. No sign-in."; +"No calendars found in the Calendar app." = "No calendars found in the Calendar app."; +"Other" = "Other"; +"Calendar access" = "Calendar access"; +"Also connected as a Google account" = "Also connected as a Google account"; +"Until can't read your calendars. Allow access in System Settings → Privacy & Security → Calendars." = "Until can't read your calendars. Allow access in System Settings → Privacy & Security → Calendars."; +"Restricted" = "Restricted"; +"Add-only" = "Add-only"; +"Off" = "Off"; +"Floating" = "Floating"; +"Full screen" = "Full screen"; +"Your next meeting, always in the menubar." = "Your next meeting, always in the menubar."; +"Use the events already in the Calendar app — iCloud, Google, Outlook, and more. No sign-in." = "Use the events already in the Calendar app — iCloud, Google, Outlook, and more. No sign-in."; +"Use Calendars on This Mac" = "Use Calendars on This Mac"; +"Open System Settings" = "Open System Settings"; +"or" = "or"; +"Google account" = "Google account"; +"Adds one-click meeting notes and Google Meet links." = "Adds one-click meeting notes and Google Meet links."; +"Try it with a sample day" = "Try it with a sample day"; +"Sample day" = "Sample day"; +"Connect calendars" = "Connect calendars"; diff --git a/Sources/Until/Resources/ja.lproj/Localizable.strings b/Sources/Until/Resources/ja.lproj/Localizable.strings index 0f27e86..06cd78e 100644 --- a/Sources/Until/Resources/ja.lproj/Localizable.strings +++ b/Sources/Until/Resources/ja.lproj/Localizable.strings @@ -340,3 +340,27 @@ "Return to Until." = "Untilに戻ってください。"; "Login failed" = "ログインに失敗しました"; "No authorization code returned." = "認証コードが返されませんでした。"; + +/* MARK: - Calendars on this Mac, onboarding, sample day */ +"Calendars on this Mac" = "このMacのカレンダー"; +"Events from the Calendar app — iCloud, Exchange, Google, and more. No sign-in." = "カレンダーAppの予定を表示します。iCloud・Exchange・Googleなどに対応し、サインインは不要です。"; +"No calendars found in the Calendar app." = "カレンダーAppにカレンダーが見つかりません。"; +"Other" = "その他"; +"Calendar access" = "カレンダーへのアクセス"; +"Also connected as a Google account" = "Googleアカウントとしても接続済み"; +"Until can't read your calendars. Allow access in System Settings → Privacy & Security → Calendars." = "カレンダーを読み取れません。システム設定 → プライバシーとセキュリティ → カレンダー でUntilへのアクセスを許可してください。"; +"Restricted" = "制限あり"; +"Add-only" = "追加のみ"; +"Off" = "オフ"; +"Floating" = "浮動"; +"Full screen" = "全画面"; +"Your next meeting, always in the menubar." = "次の予定を、いつもメニューバーに。"; +"Use the events already in the Calendar app — iCloud, Google, Outlook, and more. No sign-in." = "カレンダーAppにある予定をそのまま使います。iCloud・Google・Outlookなどに対応し、サインインは不要です。"; +"Use Calendars on This Mac" = "このMacのカレンダーを使う"; +"Open System Settings" = "システム設定を開く"; +"or" = "または"; +"Google account" = "Googleアカウント"; +"Adds one-click meeting notes and Google Meet links." = "ワンクリックの議事録作成とGoogle Meetリンクの追加も使えます。"; +"Try it with a sample day" = "サンプルの一日で試す"; +"Sample day" = "サンプルの一日"; +"Connect calendars" = "カレンダーを接続"; diff --git a/Sources/Until/Views.swift b/Sources/Until/Views.swift index 201e64a..bd1b24f 100644 --- a/Sources/Until/Views.swift +++ b/Sources/Until/Views.swift @@ -48,6 +48,9 @@ struct PanelView: View { @ViewBuilder private func content(now: Date) -> some View { let hasEvents = !(model.state.events.isEmpty && model.state.allDayEvents.isEmpty) + if model.isPreviewingSample { + SamplePreviewBanner(model: model) + } if !model.state.auth.authenticated { OnboardingView(model: model) } else if let error = model.state.lastError, !hasEvents { @@ -340,117 +343,147 @@ struct GoogleDataDisclosureView: View { } } -/// First-run experience shown in the popover when no Google account is connected. -/// Lets the user sign in directly instead of digging through Preferences. +/// First-run experience shown in the popover when no calendar is connected. +/// Mac calendars come first (no sign-in); Google adds notes and Meet links; a +/// sample day lets people see the timeline before choosing either. struct OnboardingView: View { @ObservedObject var model: AppModel var body: some View { - VStack(spacing: Theme.Spacing.xl) { - Spacer(minLength: 0) - - VStack(spacing: Theme.Spacing.md) { - ZStack { - Circle() - .fill(Color.accentColor.opacity(0.12)) - .frame(width: 72, height: 72) - Image(nsImage: BrandIcon.menubarImage(size: 36)) - .renderingMode(.template) - .foregroundStyle(Color.accentColor) - } - VStack(spacing: Theme.Spacing.xs) { + ScrollView(.vertical) { + VStack(spacing: Theme.Spacing.lg) { + VStack(spacing: Theme.Spacing.sm) { + ZStack { + Circle() + .fill(Color.accentColor.opacity(0.12)) + .frame(width: 56, height: 56) + Image(nsImage: BrandIcon.menubarImage(size: 28)) + .renderingMode(.template) + .foregroundStyle(Color.accentColor) + } Text("Until") .font(.title2.weight(.semibold)) - Text(loc("Your next Google Calendar event, always in the menubar.")) + Text(loc("Your next meeting, always in the menubar.")) .font(.callout) .foregroundStyle(.secondary) .multilineTextAlignment(.center) - .frame(maxWidth: 280) } - } - VStack(alignment: .leading, spacing: Theme.Spacing.md) { - FeatureRow( - systemImage: "menubar.arrow.up.rectangle", - title: loc("Glance at what's next"), - detail: loc("Your upcoming meeting lives in the menubar.") - ) - FeatureRow( - systemImage: "bell.badge", - title: loc("Never miss a join"), - detail: loc("Native reminders before your video calls start.") - ) - FeatureRow( - systemImage: "doc.text", - title: loc("One-click meeting notes"), - detail: loc("Open or create notes straight from an event.") - ) - } - .frame(maxWidth: 300) + VStack(alignment: .leading, spacing: Theme.Spacing.sm) { + optionHeader(systemImage: "calendar", title: loc("Calendars on this Mac")) + Text(loc("Use the events already in the Calendar app — iCloud, Google, Outlook, and more. No sign-in.")) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Button { + model.enableMacCalendars() + } label: { + Text(loc("Use Calendars on This Mac")) + .frame(maxWidth: .infinity) + } + .controlSize(.large) + .buttonStyle(.borderedProminent) + .disabled(model.isSigningIn) + if let error = model.macCalendarError { + VStack(alignment: .leading, spacing: Theme.Spacing.xs) { + InlineErrorView(message: error) + Button(loc("Open System Settings")) { model.openCalendarPrivacySettings() } + .buttonStyle(.link) + .font(.caption) + } + } + } + .card(.inset, padding: Theme.Spacing.md) - VStack(spacing: Theme.Spacing.sm) { - GoogleDataDisclosureView() - .card(.inset, padding: Theme.Spacing.md) + HStack(spacing: Theme.Spacing.sm) { + Rectangle().fill(Color.secondary.opacity(0.2)).frame(height: 1) + Text(loc("or")) + .font(.caption) + .foregroundStyle(.secondary) + Rectangle().fill(Color.secondary.opacity(0.2)).frame(height: 1) + } - Button { - model.startLogin() - } label: { - HStack(spacing: Theme.Spacing.sm) { - if model.isSigningIn { - ProgressView() - .controlSize(.small) - } else { - Image(systemName: "person.crop.circle.badge.plus") + VStack(alignment: .leading, spacing: Theme.Spacing.sm) { + optionHeader(systemImage: "person.crop.circle", title: loc("Google account")) + Text(loc("Adds one-click meeting notes and Google Meet links.")) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + GoogleDataDisclosureView() + Button { + model.startLogin() + } label: { + HStack(spacing: Theme.Spacing.sm) { + if model.isSigningIn { + ProgressView() + .controlSize(.small) + } + Text(model.isSigningIn ? loc("Opening Google sign-in…") : loc("Sign in with Google")) } - Text(model.isSigningIn ? loc("Opening Google sign-in…") : loc("Sign in with Google")) + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) - } - .controlSize(.large) - .buttonStyle(.borderedProminent) - .disabled(model.isSigningIn) + .controlSize(.large) + .disabled(model.isSigningIn) - if model.isSigningIn { - Button(loc("Cancel")) { - model.cancelSignIn() + if model.isSigningIn { + Button(loc("Cancel")) { + model.cancelSignIn() + } + .buttonStyle(.borderless) + .frame(maxWidth: .infinity) + } + if let error = model.signInError ?? model.state.lastError { + InlineErrorView(message: error) } - .buttonStyle(.borderless) } - } - .frame(maxWidth: 300) + .card(.inset, padding: Theme.Spacing.md) - if let error = model.signInError ?? model.state.lastError { - InlineErrorView(message: error) - .frame(maxWidth: 300) + Button { + model.startSamplePreview() + } label: { + Label(loc("Try it with a sample day"), systemImage: "sparkles") + .font(.callout) + } + .buttonStyle(.link) } + .frame(maxWidth: 320) + .frame(maxWidth: .infinity) + .padding(.horizontal, Theme.Spacing.xl) + .padding(.vertical, Theme.Spacing.lg) + } + } - Spacer(minLength: 0) + private func optionHeader(systemImage: String, title: String) -> some View { + HStack(spacing: Theme.Spacing.sm) { + Image(systemName: systemImage) + .foregroundStyle(Color.accentColor) + Text(title) + .font(.callout.weight(.semibold)) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(Theme.Spacing.xl) } } -private struct FeatureRow: View { - var systemImage: String - var title: String - var detail: String +/// Shown above the timeline while the sample day is on, with the way out. +private struct SamplePreviewBanner: View { + @ObservedObject var model: AppModel var body: some View { - HStack(alignment: .top, spacing: Theme.Spacing.md) { - Image(systemName: systemImage) - .font(.body) - .foregroundStyle(Color.accentColor) - .frame(width: 22) - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.callout.weight(.medium)) - Text(detail) - .font(.caption) - .foregroundStyle(.secondary) + HStack(spacing: Theme.Spacing.sm) { + Image(systemName: "sparkles") + Text(loc("Sample day")) + .font(.caption.weight(.semibold)) + Spacer(minLength: Theme.Spacing.sm) + Button(loc("Connect calendars")) { + model.endSamplePreview() } - Spacer(minLength: 0) + .buttonStyle(.link) + .font(.caption.weight(.semibold)) } + .font(.caption) + .foregroundStyle(Color.orange) + .padding(.horizontal, Theme.Spacing.md) + .padding(.vertical, Theme.Spacing.xs + 2) + .background(Color.orange.opacity(0.12)) } } @@ -2011,7 +2044,7 @@ struct SettingsView: View { var label: String { switch self { - case .accounts: return loc("Accounts") + case .accounts: return loc("Calendars") case .general: return loc("General") case .filter: return loc("Filter") } @@ -2019,7 +2052,7 @@ struct SettingsView: View { var systemImage: String { switch self { - case .accounts: return "person.crop.circle" + case .accounts: return "calendar" case .general: return "gearshape" case .filter: return "line.3.horizontal.decrease.circle" } @@ -2100,6 +2133,7 @@ struct SettingsView: View { private var accountTab: some View { SettingsTab { + MacCalendarsPanel(model: model) ConnectedAccountsPanel(model: model, draft: $draft) } .task { @@ -2435,6 +2469,83 @@ private struct SettingsTab: View { } } +/// Calendars the Mac already syncs (iCloud, Exchange, Google, CalDAV), read +/// through EventKit. No sign-in; nothing leaves the machine. +private struct MacCalendarsPanel: View { + @ObservedObject var model: AppModel + + var body: some View { + SettingsCard( + loc("Calendars on this Mac"), + subtitle: loc("Events from the Calendar app — iCloud, Exchange, Google, and more. No sign-in."), + accessory: { + Toggle("", isOn: Binding( + get: { model.config.macCalendarsEnabled }, + set: { $0 ? model.enableMacCalendars() : model.disableMacCalendars() } + )) + .labelsHidden() + .toggleStyle(.switch) + }, + content: { + if model.config.macCalendarsEnabled || model.macCalendarAccess == .denied { + accessRow + } + if let error = model.macCalendarError { + InlineErrorView(message: error) + } + if model.usesMacCalendars { + let calendars = model.calendars.filter { $0.source == .eventKit } + if calendars.isEmpty { + Text(loc("No calendars found in the Calendar app.")) + .font(.caption) + .foregroundStyle(.secondary) + } else { + ForEach(groupedBySource(calendars), id: \.title) { group in + VStack(alignment: .leading, spacing: Theme.Spacing.xs) { + Text(group.title.isEmpty ? loc("Other") : group.title) + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + ForEach(group.calendars) { calendar in + CalendarSelectionRow(calendar: calendar, model: model) + } + } + .card(.inset, padding: Theme.Spacing.md) + } + } + } + }) + .task { + if model.usesMacCalendars && !model.calendars.contains(where: { $0.source == .eventKit }) { + await model.refreshCalendars() + } + } + } + + @ViewBuilder + private var accessRow: some View { + SettingRow(loc("Calendar access"), subtitle: loc("Granted in macOS System Settings")) { + HStack(spacing: Theme.Spacing.sm) { + Circle() + .fill(model.macCalendarAccess == .authorized ? Color.green : Color.orange) + .frame(width: 8, height: 8) + Text(model.macCalendarAccess.label) + if model.macCalendarAccess != .authorized { + Button(loc("Open")) { model.openCalendarPrivacySettings() } + } + } + } + } + + private func groupedBySource(_ calendars: [CalendarSummary]) -> [(title: String, calendars: [CalendarSummary])] { + let groups = Dictionary(grouping: calendars, by: \.sourceTitle) + return groups.keys.sorted { lhs, rhs in + lhs.localizedStandardCompare(rhs) == .orderedAscending + }.map { key in + (title: key, calendars: groups[key, default: []].sorted { $0.name < $1.name }) + } + } +} + private struct ConnectedAccountsPanel: View { @ObservedObject var model: AppModel @Binding var draft: AppConfig @@ -2764,6 +2875,15 @@ private struct CalendarSelectionRow: View { var calendar: CalendarSummary @ObservedObject var model: AppModel + private var subtitle: String? { + switch calendar.source { + case .google: + return calendar.primary ? loc("primary") : calendar.googleId + case .eventKit: + return calendar.duplicatesGoogleAccount ? loc("Also connected as a Google account") : nil + } + } + var body: some View { HStack(spacing: Theme.Spacing.md) { Toggle("", isOn: Binding( @@ -2780,10 +2900,12 @@ private struct CalendarSelectionRow: View { Text(calendar.name) .font(.callout.weight(.medium)) .lineLimit(1) - Text(calendar.primary ? loc("primary") : calendar.googleId) - .font(.caption) - .foregroundStyle(.secondary) - .lineLimit(1) + if let subtitle { + Text(subtitle) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } } Spacer() } diff --git a/Tests/UntilTests/MacCalendarSourceTests.swift b/Tests/UntilTests/MacCalendarSourceTests.swift new file mode 100644 index 0000000..7064c27 --- /dev/null +++ b/Tests/UntilTests/MacCalendarSourceTests.swift @@ -0,0 +1,204 @@ +import XCTest +@testable import Until + +final class MacCalendarSourceTests: XCTestCase { + private let calendar = CalendarSummary( + id: macCalendarKey("ek-cal-1"), + googleId: "ek-cal-1", + name: "Work", + primary: false, + backgroundColor: "#007AFF", + selected: true, + accountEmail: "me@example.com", + source: .eventKit, + sourceTitle: "me@example.com" + ) + + func testTimedEventMapsFieldsAndMarksSourceAsEventKit() throws { + let start = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let input = MacCalendarEventInput( + itemIdentifier: "item-1", + eventIdentifier: "EV:1", + title: "Design review", + notes: "Agenda: https://docs.google.com/document/d/abc/edit\nJoin: https://meet.google.com/abc-defg-hij", + location: "Room 4", + startDate: start, + endDate: start.addingTimeInterval(45 * 60), + status: "tentative", + isFree: true, + isRecurring: true, + organizerEmail: "boss@example.com", + participants: [ + .init(email: "me@example.com", name: "Me", status: "declined", isCurrentUser: true, isResource: false), + .init(email: "room@example.com", name: "Room 4", status: "accepted", isCurrentUser: false, isResource: true) + ] + ) + + let event = try XCTUnwrap(input.makeEvent(calendar: calendar, now: start.addingTimeInterval(-600))) + + XCTAssertEqual(event.source, .eventKit) + XCTAssertEqual(event.calendar.id, calendar.id) + XCTAssertEqual(event.title, "Design review") + XCTAssertEqual(event.durationMinutes, 45) + XCTAssertEqual(event.startMinutesFromNow, 10) + XCTAssertEqual(event.status, "tentative") + XCTAssertEqual(event.transparency, "free") + XCTAssertTrue(event.isRecurring) + XCTAssertEqual(event.organizer, "boss@example.com") + XCTAssertEqual(event.selfResponse, "declined") + XCTAssertEqual(event.attendees.filter(\.resource).count, 1) + XCTAssertEqual(event.conferenceUrl, "https://meet.google.com/abc-defg-hij") + XCTAssertEqual(event.notesUrl, "https://docs.google.com/document/d/abc/edit") + XCTAssertEqual(event.htmlLink, "ical://ekevent/EV:1?method=show&options=more") + XCTAssertEqual(event.account.email, "me@example.com") + } + + func testMeetingLinkInURLFieldWinsOverNotes() throws { + let start = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let input = MacCalendarEventInput( + itemIdentifier: "item-2", + title: "Sync", + notes: "https://zoom.us/j/111", + url: "https://teams.microsoft.com/l/meetup-join/abc", + startDate: start, + endDate: start.addingTimeInterval(1800) + ) + + let event = try XCTUnwrap(input.makeEvent(calendar: calendar, now: start)) + + XCTAssertEqual(event.conferenceUrl, "https://teams.microsoft.com/l/meetup-join/abc") + XCTAssertEqual(event.selfResponse, "none") + } + + func testAllDayEventEndingAtLastSecondBecomesExclusiveMidnight() throws { + let day = makeDate(year: 2026, month: 9, day: 30) + let input = MacCalendarEventInput( + itemIdentifier: "item-3", + title: "Offsite", + startDate: day, + endDate: makeDate(year: 2026, month: 10, day: 1, hour: 23, minute: 59, second: 59), + isAllDay: true + ) + + let event = try XCTUnwrap(input.makeEvent(calendar: calendar, now: day)) + + XCTAssertTrue(event.allDay) + XCTAssertEqual(event.startDate, day) + XCTAssertEqual(event.endDate, makeDate(year: 2026, month: 10, day: 2)) + } + + func testAllDayEventEndingAtNextMidnightKeepsItsLength() throws { + let day = makeDate(year: 2026, month: 9, day: 30) + let input = MacCalendarEventInput( + itemIdentifier: "item-4", + title: "Holiday", + startDate: day, + endDate: makeDate(year: 2026, month: 10, day: 1), + isAllDay: true + ) + + let event = try XCTUnwrap(input.makeEvent(calendar: calendar, now: day)) + + XCTAssertEqual(event.endDate, makeDate(year: 2026, month: 10, day: 1)) + XCTAssertEqual(event.durationMinutes, 24 * 60) + } + + func testUntitledEventGetsPlaceholderTitle() throws { + let start = makeDate(year: 2026, month: 9, day: 30, hour: 9) + let input = MacCalendarEventInput( + itemIdentifier: "item-5", + title: "", + startDate: start, + endDate: start.addingTimeInterval(600) + ) + + XCTAssertEqual(try XCTUnwrap(input.makeEvent(calendar: calendar, now: start)).title, "(no title)") + } + + func testMacCalendarDefaultsOnUnlessItDuplicatesAGoogleAccount() { + let key = macCalendarKey("abc") + XCTAssertTrue(isMacCalendarSelected(key, duplicatesGoogleAccount: false, selections: [:])) + XCTAssertFalse(isMacCalendarSelected(key, duplicatesGoogleAccount: true, selections: [:])) + XCTAssertTrue(isMacCalendarSelected(key, duplicatesGoogleAccount: true, selections: [key: true])) + XCTAssertFalse(isMacCalendarSelected(key, duplicatesGoogleAccount: false, selections: [key: false])) + } + + func testMergingDropsMacCopiesOfGoogleEventsAndRepeatedMacCopies() { + let macRef = CalendarRef(id: "eventkit::a", googleId: "a", primary: false, backgroundColor: "#888", source: .eventKit) + let otherMacRef = CalendarRef(id: "eventkit::b", googleId: "b", primary: false, backgroundColor: "#888", source: .eventKit) + let google = makeEvent(id: "g1", title: "Standup") + let macCopy = makeEvent(id: "m1", title: " standup ", calendar: macRef) + let macOnly = makeEvent(id: "m2", title: "Dentist", calendar: macRef) + let macOnlyAgain = makeEvent(id: "m3", title: "Dentist", calendar: otherMacRef) + let laterMac = makeEvent( + id: "m4", + title: "Standup", + startISO: "2026-07-06T10:00:00Z", + endISO: "2026-07-06T11:00:00Z", + calendar: macRef + ) + + let merged = mergingCalendarSources([google, macCopy, macOnly, macOnlyAgain, laterMac]) + + XCTAssertEqual(merged.map(\.id), ["g1", "m2", "m4"]) + } + + func testMacEventsOfferNoGoogleOnlyActions() { + let macRef = CalendarRef(id: "eventkit::a", googleId: "a", primary: false, backgroundColor: "#888", source: .eventKit) + let actions = EventActionSet.make(event: makeEvent(calendar: macRef), noteURL: nil, isSkipped: false) + + XCTAssertTrue(actions.addable.isEmpty) + XCTAssertEqual(actions.common, [.copyDetails, .openInCalendar, .skipInMenubar]) + } + + func testEventLinksSkipMeetingAndNotesLinksAndCapAtThree() { + let description = """ + Spec https://docs.google.com/document/d/spec/edit + Design https://www.figma.com/file/xyz + PR https://github.com/acme/app/pull/847 + Board https://linear.app/acme/issue/ACME-1 + Call https://zoom.us/j/123 + """ + let links = eventLinks( + description: description, + excluding: ["https://docs.google.com/document/d/spec/edit"] + ) + + XCTAssertEqual(links.map(\.title), ["Figma", "PR #847", "Linear"]) + } + + func testLinkTitlesAreRecognizable() { + XCTAssertEqual(linkTitle(for: "https://docs.google.com/spreadsheets/d/1"), "Google Sheet") + XCTAssertEqual(linkTitle(for: "https://github.com/acme/app/issues/12"), "Issue #12") + XCTAssertEqual(linkTitle(for: "https://acme.atlassian.net/browse/X-1"), "Jira") + XCTAssertEqual(linkTitle(for: "https://www.example.com/page"), "example.com") + } + + func testLegacyConfigDecodesNewSettingsWithDefaults() throws { + let json = #"{"oauth":{"clientId":"x"},"filterRules":{"kind":"group","op":"and","children":[]}}"# + let config = try JSONDecoder().decode(AppConfig.self, from: Data(json.utf8)) + + XCTAssertFalse(config.macCalendarsEnabled) + XCTAssertTrue(config.macCalendarSelections.isEmpty) + XCTAssertFalse(config.joinHotkeyEnabled) + XCTAssertEqual(config.joinHotkeyPreset, "ctrl-opt-j") + XCTAssertFalse(config.openMeetingsInApps) + XCTAssertEqual(config.startAlertStyle, StartAlertStyle.off.rawValue) + XCTAssertTrue(config.menubarShowsTitle) + } + + func testNewSettingsRoundTrip() throws { + var config = AppConfig.default + config.macCalendarsEnabled = true + config.macCalendarSelections = [macCalendarKey("a"): false] + config.startAlertStyle = StartAlertStyle.fullScreen.rawValue + config.menubarShowsTitle = false + + let decoded = try JSONDecoder().decode(AppConfig.self, from: JSONEncoder().encode(config)) + + XCTAssertTrue(decoded.macCalendarsEnabled) + XCTAssertEqual(decoded.macCalendarSelections, [macCalendarKey("a"): false]) + XCTAssertEqual(decoded.startAlertStyle, StartAlertStyle.fullScreen.rawValue) + XCTAssertFalse(decoded.menubarShowsTitle) + } +} diff --git a/scripts/entitlements/app.entitlements b/scripts/entitlements/app.entitlements index bffae23..9a50be0 100644 --- a/scripts/entitlements/app.entitlements +++ b/scripts/entitlements/app.entitlements @@ -3,6 +3,8 @@ "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + com.apple.security.personal-information.calendars + com.apple.security.application-groups __WIDGET_GROUP__ diff --git a/scripts/entitlements/mas.entitlements b/scripts/entitlements/mas.entitlements index 2ecf5a8..1e9f24f 100644 --- a/scripts/entitlements/mas.entitlements +++ b/scripts/entitlements/mas.entitlements @@ -9,6 +9,8 @@ com.apple.security.network.server + com.apple.security.personal-information.calendars + com.apple.security.application-groups __WIDGET_GROUP__ diff --git a/scripts/package-app.sh b/scripts/package-app.sh index de2b811..5c69925 100755 --- a/scripts/package-app.sh +++ b/scripts/package-app.sh @@ -171,6 +171,10 @@ cat > "$APP_DIR/Contents/Info.plist" < NSUserNotificationAlertStyle alert + NSCalendarsUsageDescription + Until shows your upcoming events from the Calendar app in the menu bar. Nothing leaves your Mac. + NSCalendarsFullAccessUsageDescription + Until shows your upcoming events from the Calendar app in the menu bar. Nothing leaves your Mac. UntilWidgetGroupIdentifier ${WIDGET_GROUP_ID} GoogleOAuthClientID From d3fe8472df7716c734a545d1f64f964c0d40c2d8 Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:45:03 +0900 Subject: [PATCH 02/11] Localize the Calendar access prompt Ship en/ja InfoPlist.strings for the NSCalendars usage descriptions and declare both localizations in the app's Info.plist. --- scripts/package-app.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scripts/package-app.sh b/scripts/package-app.sh index 5c69925..a7e0d49 100755 --- a/scripts/package-app.sh +++ b/scripts/package-app.sh @@ -157,6 +157,13 @@ cat > "$APP_DIR/Contents/Info.plist" <CFBundleURLSchemesuntil + CFBundleDevelopmentRegion + en + CFBundleLocalizations + + en + ja + CFBundleShortVersionString ${APP_VERSION} CFBundleVersion @@ -186,6 +193,17 @@ ${SPARKLE_PLIST_KEYS} PLIST +# Localized Info.plist strings (the Calendar access prompt). +mkdir -p "$APP_DIR/Contents/Resources/en.lproj" "$APP_DIR/Contents/Resources/ja.lproj" +cat > "$APP_DIR/Contents/Resources/en.lproj/InfoPlist.strings" <<'STRINGS' +"NSCalendarsUsageDescription" = "Until shows your upcoming events from the Calendar app in the menu bar. Nothing leaves your Mac."; +"NSCalendarsFullAccessUsageDescription" = "Until shows your upcoming events from the Calendar app in the menu bar. Nothing leaves your Mac."; +STRINGS +cat > "$APP_DIR/Contents/Resources/ja.lproj/InfoPlist.strings" <<'STRINGS' +"NSCalendarsUsageDescription" = "カレンダーAppの予定をメニューバーに表示するために使います。予定がこのMacの外に送られることはありません。"; +"NSCalendarsFullAccessUsageDescription" = "カレンダーAppの予定をメニューバーに表示するために使います。予定がこのMacの外に送られることはありません。"; +STRINGS + # App icon. Generated once from scripts/make-icon.swift, then reused so dev # rebuilds stay fast; delete scripts/Until.icns to regenerate after a redesign. ICON_SRC="$ROOT/scripts/Until.icns" From 53923d9e0a7e4b26d2f900e1d222cc827740d041 Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:45:03 +0900 Subject: [PATCH 03/11] Add meeting workflow features: join shortcut, start alert, more services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A second global shortcut joins the menubar meeting, or the joinable meeting that is running or starts within 15 minutes. - An optional start alert puts the meeting on screen as it starts, as a floating card or a full-screen takeover on every display, until the user joins, snoozes a minute, or dismisses it. - Meeting link detection covers 30+ services (Slack huddles, Discord, Jitsi, FaceTime, Chime, Gather, Tuple, Lark, Tencent Meeting, …), with path checks for hosts that also serve ordinary pages. Zoom and Teams links can open in their desktop apps. - The menubar can hide the event title and show only the countdown. - The popover footer summarizes today (events, booked time, longest free stretch left), and the upcoming card shows up to three links from the invite (docs, designs, PRs). --- Sources/Until/AppDelegate.swift | 20 +- Sources/Until/AppModel+Timeline.swift | 63 +++- Sources/Until/AppModel.swift | 60 +++- Sources/Until/DemoCalendarData.swift | 10 +- Sources/Until/DemoFixture.swift | 6 +- Sources/Until/EventLinks.swift | 167 ++++++++- Sources/Until/EventNotifier.swift | 21 +- Sources/Until/HotkeyManager.swift | 42 ++- .../Resources/en.lproj/Localizable.strings | 23 ++ .../Resources/ja.lproj/Localizable.strings | 23 ++ Sources/Until/StartAlert.swift | 323 ++++++++++++++++++ Sources/Until/Views.swift | 168 ++++++++- Tests/UntilTests/MeetingWorkflowTests.swift | 196 +++++++++++ 13 files changed, 1070 insertions(+), 52 deletions(-) create mode 100644 Sources/Until/StartAlert.swift create mode 100644 Tests/UntilTests/MeetingWorkflowTests.swift diff --git a/Sources/Until/AppDelegate.swift b/Sources/Until/AppDelegate.swift index 27ef7c1..588de0a 100644 --- a/Sources/Until/AppDelegate.swift +++ b/Sources/Until/AppDelegate.swift @@ -77,6 +77,7 @@ final class StatusBarController: NSObject, NSPopoverDelegate { private var cancellables = Set() private var settingsWindow: NSWindow? private var hotkeyManager: HotkeyManager? + private var joinHotkeyManager: HotkeyManager? /// When true the menubar shows only the icon, hiding the event text. Toggled by /// right-clicking the status item; in-memory only (resets on relaunch). private var collapsed = false @@ -87,9 +88,13 @@ final class StatusBarController: NSObject, NSPopoverDelegate { init(model: AppModel) { self.model = model super.init() - hotkeyManager = HotkeyManager { [weak self] in + hotkeyManager = HotkeyManager(id: 1) { [weak self] in self?.hotkeyToggle() } + joinHotkeyManager = HotkeyManager(id: 2) { [weak self] in + guard let self else { return } + if !self.model.joinNextMeeting() { self.shakeStatusItem() } + } popover.behavior = .transient popover.delegate = self popover.contentSize = NSSize(width: 390, height: 520) @@ -114,6 +119,17 @@ final class StatusBarController: NSObject, NSPopoverDelegate { self?.applyHotkey(enabled: enabled, preset: preset) } .store(in: &cancellables) + model.$config + .map { ($0.joinHotkeyEnabled, $0.joinHotkeyPreset) } + .removeDuplicates { $0 == $1 } + .sink { [weak self] enabled, preset in + if enabled { + self?.joinHotkeyManager?.register(presetId: preset) + } else { + self?.joinHotkeyManager?.unregister() + } + } + .store(in: &cancellables) observeMenubarAvailability() } @@ -252,7 +268,7 @@ final class StatusBarController: NSObject, NSPopoverDelegate { } nextContent = .event( when: when, - title: next.title, + title: config.menubarShowsTitle ? next.title : "", maximumTitleCharacters: config.maxTitleLength ) } else if state.auth.authenticated && state.events.isEmpty && state.allDayEvents.isEmpty && state.lastError == nil { diff --git a/Sources/Until/AppModel+Timeline.swift b/Sources/Until/AppModel+Timeline.swift index be55860..d7e4410 100644 --- a/Sources/Until/AppModel+Timeline.swift +++ b/Sources/Until/AppModel+Timeline.swift @@ -47,7 +47,7 @@ extension AppModel { /// Gaps at or above this length between two consecutive timed rows earn a /// free-time divider in the popover list. - static let freeGapThresholdMinutes = 30 + nonisolated static let freeGapThresholdMinutes = 30 /// Pure: interleaves `FreeGap` dividers into `rows` wherever two /// consecutive TIMED rows are separated by at least @@ -448,3 +448,64 @@ extension AppModel { return .free(next: next) } } + +/// Today at a glance, for the line under the timeline's day header. +struct DaySummary: Equatable { + var eventCount: Int + var bookedMinutes: Int + /// The longest stretch still free between now and the day's last busy + /// event; nil once nothing busy is left. + var longestFreeMinutes: Int? + + var text: String { + var parts = [eventCount == 1 ? loc("1 event") : loc("%d events", eventCount)] + if bookedMinutes > 0 { + parts.append(loc("%@ booked", relativeWhen(bookedMinutes))) + } + if let longestFreeMinutes, longestFreeMinutes >= AppModel.freeGapThresholdMinutes { + parts.append(loc("longest free %@", relativeWhen(longestFreeMinutes))) + } + return parts.joined(separator: " · ") + } +} + +extension AppModel { + /// Pure: summarizes the busy timed events on `day`. Overlapping events are + /// counted once toward booked time; free time is measured from `now`. + nonisolated static func daySummary(timed: [CalendarEvent], day: Date, now: Date) -> DaySummary? { + let calendar = Calendar.current + let dayStart = calendar.startOfDay(for: day) + guard let dayEnd = calendar.date(byAdding: .day, value: 1, to: dayStart) else { return nil } + let events = timed.filter { + !$0.allDay && $0.transparency != "free" && $0.startDate < dayEnd && $0.endDate > dayStart + } + guard !events.isEmpty else { return nil } + + var merged: [(start: Date, end: Date)] = [] + for event in events.sorted(by: { $0.startDate < $1.startDate }) { + let start = max(event.startDate, dayStart) + let end = min(event.endDate, dayEnd) + if let last = merged.last, start <= last.end { + merged[merged.count - 1].end = max(last.end, end) + } else { + merged.append((start, end)) + } + } + let booked = merged.reduce(0) { $0 + $1.end.timeIntervalSince($1.start) } + + var cursor = now + var longestFree: TimeInterval? + for interval in merged where interval.end > now { + if interval.start > cursor { + longestFree = max(longestFree ?? 0, interval.start.timeIntervalSince(cursor)) + } + cursor = max(cursor, interval.end) + } + + return DaySummary( + eventCount: events.count, + bookedMinutes: Int((booked / 60).rounded()), + longestFreeMinutes: longestFree.map { Int(($0 / 60).rounded()) } + ) + } +} diff --git a/Sources/Until/AppModel.swift b/Sources/Until/AppModel.swift index 076e59f..377590c 100644 --- a/Sources/Until/AppModel.swift +++ b/Sources/Until/AppModel.swift @@ -56,6 +56,7 @@ final class AppModel: ObservableObject { private let store = ConfigStore() private let notifier = EventNotifier() private let macCalendars = MacCalendarSource() + private let startAlerts = StartAlertController() private var macCalendarObserver: NSObjectProtocol? private var accounts: [GoogleAuth] = [] private var rawEvents: [CalendarEvent] = [] @@ -79,6 +80,8 @@ final class AppModel: ObservableObject { ? (demoFixture?.appConfig(base: demoConfig) ?? demoConfig) : store.load() observeWake() + startAlerts.onJoin = { [weak self] event in self?.join(event) } + startAlerts.onOpen = { [weak self] event in self?.open(event) } refreshLaunchAtLoginState() applyDefaultLaunchAtLoginIfNeeded() if runtimeOptions.demoMode { @@ -689,6 +692,51 @@ final class AppModel: ObservableObject { // MARK: - Join menubar meeting + /// Shows the start alert for the next meeting right away so the chosen + /// style can be seen without waiting for a real meeting. + func previewStartAlert() { + let now = Date() + let sample = state.events.first { $0.endDate > now && !$0.conferenceUrl.isEmpty } + ?? state.events.first { $0.endDate > now } + ?? DemoCalendarData.events(now: now, selectedIds: [], scenario: .upcoming).first { !$0.allDay } + guard let sample else { return } + startAlerts.preview(sample, style: StartAlertStyle(rawValue: config.startAlertStyle) ?? .floating) + } + + /// The meeting the join shortcut opens: the menubar's meeting when it has a + /// link, otherwise the joinable meeting that is running or starts soonest + /// within the next 15 minutes. + nonisolated static func joinTarget( + menubarEvent: CalendarEvent?, + timed: [CalendarEvent], + now: Date + ) -> CalendarEvent? { + if let menubarEvent, !menubarEvent.conferenceUrl.isEmpty { + return menubarEvent + } + let soon = now.addingTimeInterval(15 * 60) + return timed + .filter { !$0.allDay && !$0.conferenceUrl.isEmpty && $0.endDate > now && $0.startDate <= soon } + .min { lhs, rhs in + // A running meeting beats one that hasn't started; among running + // ones the most recently started wins, like the menubar. + let lhsRunning = lhs.startDate <= now + let rhsRunning = rhs.startDate <= now + if lhsRunning != rhsRunning { return lhsRunning } + return lhsRunning ? lhs.startDate > rhs.startDate : lhs.startDate < rhs.startDate + } + } + + /// The global join shortcut. Returns false when there is nothing to join. + @discardableResult + func joinNextMeeting() -> Bool { + guard let event = Self.joinTarget(menubarEvent: state.next, timed: state.events, now: Date()) else { + return false + } + join(event) + return true + } + @discardableResult /// Join the meeting currently shown in the menubar (`state.next`). Returns /// false when there's nothing shown or it has no conference URL — the caller @@ -717,7 +765,7 @@ final class AppModel: ObservableObject { func join(_ event: CalendarEvent) { guard let url = EventLinks.conferenceURL(for: event) else { return } - NSWorkspace.shared.open(url) + EventLinks.openMeeting(url, preferDesktopApp: config.openMeetingsInApps) } func noteURL(for event: CalendarEvent) -> String { @@ -949,10 +997,20 @@ final class AppModel: ObservableObject { state.next = AppModel.pickMenubarEvent(config: config, timed: timed, allDay: allDay, now: now) publishWidgetSnapshot() let activeEvents = timed.filter { $0.endDate > now } + // Local UI only, so demo runs may show it too; the sample preview may not. + startAlerts.sync( + events: isPreviewingSample ? [] : activeEvents, + rules: StartAlertSchedule.Rules( + style: StartAlertStyle(rawValue: config.startAlertStyle) ?? .off, + leadMinutes: config.startAlertLeadMinutes, + videoOnly: config.startAlertVideoOnly + ) + ) let notificationEvents = config.notifyVideoOnly ? activeEvents.filter { !$0.conferenceUrl.isEmpty } : activeEvents guard runtimeOptions.allowsNotifications, !isPreviewingSample else { return } + notifier.prefersDesktopApps = config.openMeetingsInApps Task { await notifier.sync( events: notificationEvents, diff --git a/Sources/Until/DemoCalendarData.swift b/Sources/Until/DemoCalendarData.swift index bba8964..fceee55 100644 --- a/Sources/Until/DemoCalendarData.swift +++ b/Sources/Until/DemoCalendarData.swift @@ -387,7 +387,12 @@ enum DemoCalendarData { DemoEventSpec( id: "design-review", title: "Design review: website screenshots", - description: "Pick the strongest Until states for the landing page and press kit.", + description: """ + Pick the strongest Until states for the landing page and press kit. + Brief: https://docs.google.com/document/d/until-launch-brief/edit + Layouts: https://www.figma.com/design/until-website + Site PR: https://github.com/combinatrix-ai/until/pull/12 + """, location: "Google Meet", start: ctx.nextSlot, end: minutes(from: ctx.nextSlot, 45), @@ -590,7 +595,8 @@ enum DemoCalendarData { notesUrl: spec.notesUrl, colorId: spec.colorId, transparency: spec.transparency, - htmlLink: "https://calendar.google.com/calendar/event?eid=demo-\(spec.id)" + htmlLink: "https://calendar.google.com/calendar/event?eid=demo-\(spec.id)", + links: eventLinks(description: spec.description, excluding: [spec.conferenceUrl, spec.notesUrl]) ) } diff --git a/Sources/Until/DemoFixture.swift b/Sources/Until/DemoFixture.swift index afa0846..054924d 100644 --- a/Sources/Until/DemoFixture.swift +++ b/Sources/Until/DemoFixture.swift @@ -266,7 +266,11 @@ extension DemoFixture { notesUrl: event.notesUrl ?? "", colorId: event.colorId ?? "9", transparency: event.transparency ?? "busy", - htmlLink: "https://calendar.google.com/calendar/event?eid=demo-\(event.id)" + htmlLink: "https://calendar.google.com/calendar/event?eid=demo-\(event.id)", + links: eventLinks( + description: event.description, + excluding: [event.conferenceUrl ?? "", event.notesUrl ?? ""] + ) ) } diff --git a/Sources/Until/EventLinks.swift b/Sources/Until/EventLinks.swift index 9aa2ad7..0526312 100644 --- a/Sources/Until/EventLinks.swift +++ b/Sources/Until/EventLinks.swift @@ -1,7 +1,8 @@ +import AppKit import Foundation enum EventLinks { - enum MeetingProvider { + enum MeetingProvider: CaseIterable { case googleMeet case zoom case teams @@ -10,6 +11,29 @@ enum EventLinks { case goToMeeting case whereby case around + case slackHuddle + case discord + case jitsi + case eightByEight + case faceTime + case chime + case gather + case tuple + case ringCentral + case skype + case dialpad + case zohoMeeting + case larkMeetings + case tencentMeeting + case livestorm + case streamYard + case riverside + case pop + case calVideo + case liveKit + case demio + case huddle01 + case signal var label: String { switch self { @@ -21,6 +45,81 @@ enum EventLinks { case .goToMeeting: return "GoTo Meeting" case .whereby: return "Whereby" case .around: return "Around" + case .slackHuddle: return "Slack huddle" + case .discord: return "Discord" + case .jitsi: return "Jitsi Meet" + case .eightByEight: return "8x8" + case .faceTime: return "FaceTime" + case .chime: return "Amazon Chime" + case .gather: return "Gather" + case .tuple: return "Tuple" + case .ringCentral: return "RingCentral" + case .skype: return "Skype" + case .dialpad: return "Dialpad" + case .zohoMeeting: return "Zoho Meeting" + case .larkMeetings: return "Lark" + case .tencentMeeting: return "Tencent Meeting" + case .livestorm: return "Livestorm" + case .streamYard: return "StreamYard" + case .riverside: return "Riverside" + case .pop: return "Pop" + case .calVideo: return "Cal Video" + case .liveKit: return "LiveKit Meet" + case .demio: return "Demio" + case .huddle01: return "Huddle01" + case .signal: return "Signal" + } + } + + /// Domains whose links are calls, matched exactly or as a subdomain. + /// Hosts that also serve ordinary pages (Slack, Discord, Cal.com, + /// Signal) additionally need a call-shaped path; see `pathPrefixes`. + fileprivate var domains: [String] { + switch self { + case .googleMeet: return ["meet.google.com"] + case .zoom: return ["zoom.us", "zoom.com", "zoomgov.com"] + case .teams: return ["teams.microsoft.com", "teams.live.com", "teams.microsoft.us"] + case .webex: return ["webex.com"] + case .blueJeans: return ["bluejeans.com"] + case .goToMeeting: return ["gotomeeting.com", "goto.com", "gotomeet.me"] + case .whereby: return ["whereby.com"] + case .around: return ["around.co"] + case .slackHuddle: return ["slack.com"] + case .discord: return ["discord.gg", "discord.com"] + case .jitsi: return ["meet.jit.si"] + case .eightByEight: return ["8x8.vc"] + case .faceTime: return ["facetime.apple.com"] + case .chime: return ["chime.aws"] + case .gather: return ["gather.town"] + case .tuple: return ["tuple.app"] + case .ringCentral: return ["meetings.ringcentral.com", "v.ringcentral.com", "video.ringcentral.com"] + case .skype: return ["join.skype.com", "meet.lync.com"] + case .dialpad: return ["meetings.dialpad.com"] + case .zohoMeeting: return ["meeting.zoho.com", "meeting.zoho.eu", "meeting.zoho.in", "meeting.zoho.jp"] + case .larkMeetings: return ["vc.larksuite.com", "vc.feishu.cn"] + case .tencentMeeting: return ["meeting.tencent.com", "voovmeeting.com"] + case .livestorm: return ["app.livestorm.co"] + case .streamYard: return ["streamyard.com"] + case .riverside: return ["riverside.fm"] + case .pop: return ["pop.com"] + case .calVideo: return ["app.cal.com"] + case .liveKit: return ["meet.livekit.io"] + case .demio: return ["demio.com"] + case .huddle01: return ["huddle01.com"] + case .signal: return ["signal.link"] + } + } + + /// For hosts that are mostly not calls, the path that marks a call link. + fileprivate var pathPrefixes: [String]? { + switch self { + case .slackHuddle: return ["/huddle"] + case .discord: return ["/channels", "/invite"] + case .calVideo: return ["/video"] + case .signal: return ["/call"] + case .pop: return ["/j"] + case .riverside: return ["/studio"] + default: return nil } } } @@ -53,16 +152,62 @@ enum EventLinks { } static func meetingProvider(for rawValue: String) -> MeetingProvider? { - guard !rawValue.isEmpty, let host = URL(string: rawValue)?.host?.lowercased() else { return nil } - if host == "meet.google.com" { return .googleMeet } - if matches(host, "zoom.us") { return .zoom } - if matches(host, "teams.microsoft.com") { return .teams } - if matches(host, "webex.com") { return .webex } - if matches(host, "bluejeans.com") { return .blueJeans } - if matches(host, "gotomeeting.com") || matches(host, "goto.com") { return .goToMeeting } - if matches(host, "whereby.com") { return .whereby } - if matches(host, "around.co") { return .around } - return nil + guard !rawValue.isEmpty, let url = URL(string: rawValue), let host = url.host?.lowercased() else { return nil } + return MeetingProvider.allCases.first { provider in + guard provider.domains.contains(where: { matches(host, $0) }) else { return false } + guard let prefixes = provider.pathPrefixes else { return true } + // discord.gg invites are always calls; discord.com needs a channel path. + if provider == .discord && host.hasSuffix("discord.gg") { return true } + let path = url.path.lowercased() + return prefixes.contains { path.hasPrefix($0) } + } + } + + /// The desktop-app form of a meeting link, for services whose apps register + /// their own URL scheme. Nil when the link should just open as is. + static func desktopAppURL(for url: URL) -> URL? { + guard let host = url.host?.lowercased() else { return nil } + switch meetingProvider(for: url.absoluteString) { + case .zoom: + return zoomAppURL(for: url, host: host) + case .teams: + return URL(string: url.absoluteString.replacingOccurrences(of: "https://", with: "msteams://")) + default: + return nil + } + } + + /// zoom.us/j/123?pwd=abc → zoommtg://zoom.us/join?action=join&confno=123&pwd=abc + private static func zoomAppURL(for url: URL, host: String) -> URL? { + let parts = url.path.split(separator: "/") + guard parts.count >= 2, parts[0] == "j" || parts[0] == "w" else { return nil } + var components = URLComponents() + components.scheme = "zoommtg" + components.host = host + components.path = "/join" + var items = [ + URLQueryItem(name: "action", value: "join"), + URLQueryItem(name: "confno", value: String(parts[1])) + ] + if let pwd = URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems?.first(where: { $0.name == "pwd" })?.value { + items.append(URLQueryItem(name: "pwd", value: pwd)) + } + components.queryItems = items + return components.url + } + + /// Opens a meeting link, in the service's desktop app when asked to and + /// the app is installed; otherwise in the browser as before. + @MainActor + static func openMeeting(_ url: URL, preferDesktopApp: Bool) { + if preferDesktopApp, + let appURL = desktopAppURL(for: url), + NSWorkspace.shared.urlForApplication(toOpen: appURL) != nil { + NSWorkspace.shared.open(appURL) + return + } + NSWorkspace.shared.open(url) } /// True when `host` is exactly `domain` or a subdomain of it (`*.domain`). diff --git a/Sources/Until/EventNotifier.swift b/Sources/Until/EventNotifier.swift index 3c2439e..e3a0cb0 100644 --- a/Sources/Until/EventNotifier.swift +++ b/Sources/Until/EventNotifier.swift @@ -19,6 +19,9 @@ final class EventNotifier: NSObject, UNUserNotificationCenterDelegate { private let backend: Backend + /// Mirrors `AppConfig.openMeetingsInApps` for the Join action. + var prefersDesktopApps = false + /// Script backend only: fire-once timers, since osascript has no /// UNUserNotificationCenter to schedule against. private var scriptTimers: [String: Timer] = [:] @@ -333,10 +336,10 @@ final class EventNotifier: NSObject, UNUserNotificationCenterDelegate { return "event-reminder-teams" case .webex: return "event-reminder-webex" - case .blueJeans, .goToMeeting, .whereby, .around: - return "event-reminder-video" case nil: return "event-reminder" + default: + return "event-reminder-video" } } @@ -377,15 +380,15 @@ final class EventNotifier: NSObject, UNUserNotificationCenterDelegate { } let userInfo = response.notification.request.content.userInfo + let joinURL = (userInfo["joinURL"] as? String).flatMap { $0.isEmpty ? nil : $0 } let preferredURL: String? switch response.actionIdentifier { case "join": - preferredURL = userInfo["joinURL"] as? String + preferredURL = joinURL case "open": preferredURL = userInfo["eventURL"] as? String case UNNotificationDefaultActionIdentifier: - preferredURL = (userInfo["joinURL"] as? String).flatMap { $0.isEmpty ? nil : $0 } - ?? (userInfo["eventURL"] as? String) + preferredURL = joinURL ?? (userInfo["eventURL"] as? String) default: preferredURL = userInfo["eventURL"] as? String } @@ -395,8 +398,12 @@ final class EventNotifier: NSObject, UNUserNotificationCenterDelegate { let url = URL(string: urlString) else { return } - _ = await MainActor.run { - NSWorkspace.shared.open(url) + await MainActor.run { + if urlString == joinURL { + EventLinks.openMeeting(url, preferDesktopApp: prefersDesktopApps) + } else { + NSWorkspace.shared.open(url) + } } } } diff --git a/Sources/Until/HotkeyManager.swift b/Sources/Until/HotkeyManager.swift index ebb79e7..2548181 100644 --- a/Sources/Until/HotkeyManager.swift +++ b/Sources/Until/HotkeyManager.swift @@ -1,10 +1,11 @@ import AppKit import Carbon.HIToolbox -/// Registers a single global hotkey via the Carbon HotKey API. This works on +/// Registers one global hotkey via the Carbon HotKey API. This works on /// modern macOS without Accessibility/Input-Monitoring permissions, unlike a /// global `NSEvent` monitor. The Carbon event handler is a C function pointer, /// so we route the callback through a stored closure keyed off the hotkey id. +/// Each manager owns one id, so the popover and join shortcuts can coexist. @MainActor final class HotkeyManager { /// Named modifier + key-code combinations offered in Settings. The raw string @@ -24,7 +25,13 @@ final class HotkeyManager { Preset(id: "ctrl-opt-n", label: "⌃⌥N", keyCode: UInt32(kVK_ANSI_N), modifiers: UInt32(controlKey | optionKey)), Preset(id: "ctrl-shift-space", label: "⌃⇧Space", - keyCode: UInt32(kVK_Space), modifiers: UInt32(controlKey | shiftKey)) + keyCode: UInt32(kVK_Space), modifiers: UInt32(controlKey | shiftKey)), + Preset(id: "ctrl-opt-j", label: "⌃⌥J", + keyCode: UInt32(kVK_ANSI_J), modifiers: UInt32(controlKey | optionKey)), + Preset(id: "cmd-shift-j", label: "⌘⇧J", + keyCode: UInt32(kVK_ANSI_J), modifiers: UInt32(cmdKey | shiftKey)), + Preset(id: "ctrl-opt-m", label: "⌃⌥M", + keyCode: UInt32(kVK_ANSI_M), modifiers: UInt32(controlKey | optionKey)) ] static func preset(for id: String) -> Preset { @@ -36,15 +43,16 @@ final class HotkeyManager { private var handler: (() -> Void)? private var currentPresetId: String? - /// The Carbon hotkey identifier signature/id used for our single hotkey. - private static let signature: OSType = { + /// The Carbon hotkey signature shared by all of Until's hotkeys. + nonisolated fileprivate static let signature: OSType = { // 'UNTL' let chars: [UInt8] = [0x55, 0x4E, 0x54, 0x4C] return chars.reduce(0) { ($0 << 8) | OSType($1) } }() - private static let hotKeyID: UInt32 = 1 + nonisolated fileprivate let hotKeyID: UInt32 - init(handler: @escaping () -> Void) { + init(id: UInt32 = 1, handler: @escaping () -> Void) { + hotKeyID = id self.handler = handler } @@ -58,7 +66,7 @@ final class HotkeyManager { installEventHandlerIfNeeded() var ref: EventHotKeyRef? - let hotKeyID = EventHotKeyID(signature: Self.signature, id: Self.hotKeyID) + let hotKeyID = EventHotKeyID(signature: Self.signature, id: self.hotKeyID) let status = RegisterEventHotKey( preset.keyCode, preset.modifiers, @@ -113,14 +121,30 @@ final class HotkeyManager { } /// C-compatible Carbon event callback. Recovers the `HotkeyManager` from -/// `userData` and hops to the main actor to run the stored handler. +/// `userData` and hops to the main actor to run the stored handler. Every +/// manager's handler sees every Until hotkey, so one that isn't ours is passed +/// on to the next handler. private func hotkeyEventCallback( _: EventHandlerCallRef?, - _: EventRef?, + _ event: EventRef?, _ userData: UnsafeMutableRawPointer? ) -> OSStatus { guard let userData else { return noErr } let manager = Unmanaged.fromOpaque(userData).takeUnretainedValue() + var pressed = EventHotKeyID() + let status = GetEventParameter( + event, + EventParamName(kEventParamDirectObject), + EventParamType(typeEventHotKeyID), + nil, + MemoryLayout.size, + nil, + &pressed + ) + guard status == noErr, + pressed.signature == HotkeyManager.signature, + pressed.id == manager.hotKeyID + else { return OSStatus(eventNotHandledErr) } DispatchQueue.main.async { MainActor.assumeIsolated { manager.fire() diff --git a/Sources/Until/Resources/en.lproj/Localizable.strings b/Sources/Until/Resources/en.lproj/Localizable.strings index 2b08ee8..17acb58 100644 --- a/Sources/Until/Resources/en.lproj/Localizable.strings +++ b/Sources/Until/Resources/en.lproj/Localizable.strings @@ -364,3 +364,26 @@ "Try it with a sample day" = "Try it with a sample day"; "Sample day" = "Sample day"; "Connect calendars" = "Connect calendars"; + +/* MARK: - Meetings: join shortcut, start alert, day summary */ +"Dismiss" = "Dismiss"; +"Snooze 1 min" = "Snooze 1 min"; +"Starting now" = "Starting now"; +"Starts in %@" = "Starts in %@"; +"Join shortcut" = "Join shortcut"; +"Join the current or next meeting from anywhere" = "Join the current or next meeting from anywhere"; +"Show event title" = "Show event title"; +"Turn off to show only the countdown, e.g. while presenting" = "Turn off to show only the countdown, e.g. while presenting"; +"Meetings" = "Meetings"; +"Open in desktop apps" = "Open in desktop apps"; +"Zoom and Teams links open in their apps when installed" = "Zoom and Teams links open in their apps when installed"; +"Start alert" = "Start alert"; +"Put the meeting on screen as it starts, until you join or dismiss it" = "Put the meeting on screen as it starts, until you join or dismiss it"; +"Alert timing" = "Alert timing"; +"How long before the meeting the alert appears" = "How long before the meeting the alert appears"; +"Alert for" = "Alert for"; +"Preview alert" = "Preview alert"; +"1 event" = "1 event"; +"%d events" = "%d events"; +"%@ booked" = "%@ booked"; +"longest free %@" = "longest free %@"; diff --git a/Sources/Until/Resources/ja.lproj/Localizable.strings b/Sources/Until/Resources/ja.lproj/Localizable.strings index 06cd78e..880457c 100644 --- a/Sources/Until/Resources/ja.lproj/Localizable.strings +++ b/Sources/Until/Resources/ja.lproj/Localizable.strings @@ -364,3 +364,26 @@ "Try it with a sample day" = "サンプルの一日で試す"; "Sample day" = "サンプルの一日"; "Connect calendars" = "カレンダーを接続"; + +/* MARK: - Meetings: join shortcut, start alert, day summary */ +"Dismiss" = "閉じる"; +"Snooze 1 min" = "1分後に再通知"; +"Starting now" = "開始"; +"Starts in %@" = "%@後に開始"; +"Join shortcut" = "参加ショートカット"; +"Join the current or next meeting from anywhere" = "どこからでも、進行中または直近の会議に参加"; +"Show event title" = "予定名を表示"; +"Turn off to show only the countdown, e.g. while presenting" = "オフにするとカウントダウンだけを表示します(発表中などに)"; +"Meetings" = "会議"; +"Open in desktop apps" = "デスクトップアプリで開く"; +"Zoom and Teams links open in their apps when installed" = "ZoomとTeamsのリンクを、インストール済みのアプリで開きます"; +"Start alert" = "開始アラート"; +"Put the meeting on screen as it starts, until you join or dismiss it" = "会議の開始時に画面へ表示し、参加するか閉じるまで出し続けます"; +"Alert timing" = "アラートのタイミング"; +"How long before the meeting the alert appears" = "会議の何分前にアラートを出すか"; +"Alert for" = "アラートの対象"; +"Preview alert" = "アラートを試す"; +"1 event" = "予定1件"; +"%d events" = "予定%d件"; +"%@ booked" = "計%@"; +"longest free %@" = "最長の空き%@"; diff --git a/Sources/Until/StartAlert.swift b/Sources/Until/StartAlert.swift new file mode 100644 index 0000000..4de6b92 --- /dev/null +++ b/Sources/Until/StartAlert.swift @@ -0,0 +1,323 @@ +import AppKit +import SwiftUI + +/// Decides which meeting the start alert is for and when it should appear. +/// Pure so the timing rules can be tested without windows. +enum StartAlertSchedule { + /// A started meeting still gets its alert for this long, e.g. after the Mac + /// wakes from sleep a minute late. + static let lateGrace: TimeInterval = 5 * 60 + + struct Pending: Equatable { + var event: CalendarEvent + var fireDate: Date + } + + /// The user's start-alert settings. + struct Rules: Equatable { + var style: StartAlertStyle + var leadMinutes: Int + var videoOnly: Bool + } + + static func key(for event: CalendarEvent) -> String { + "\(event.actionKey)@\(event.startDate.timeIntervalSince1970)" + } + + static func next( + events: [CalendarEvent], + rules: Rules, + handled: Set, + snoozed: [String: Date], + now: Date + ) -> Pending? { + let lead = TimeInterval(max(0, rules.leadMinutes)) * 60 + return events + .filter { event in + !event.allDay + && event.endDate > now + && now < event.startDate.addingTimeInterval(lateGrace) + && (!rules.videoOnly || !event.conferenceUrl.isEmpty) + } + .compactMap { event -> Pending? in + let key = key(for: event) + if let until = snoozed[key] { + return Pending(event: event, fireDate: until) + } + guard !handled.contains(key) else { return nil } + return Pending(event: event, fireDate: event.startDate.addingTimeInterval(-lead)) + } + .min { $0.fireDate < $1.fireDate } + } +} + +/// Shows an on-screen alert as a meeting starts: a small floating card at the +/// top of the screen, or a full-screen takeover on every display. It stays +/// until the user joins, snoozes, or dismisses it. +@MainActor +final class StartAlertController { + private var timer: Timer? + private var handled: Set = [] + private var snoozed: [String: Date] = [:] + private var panels: [NSPanel] = [] + private var shownKey: String? + private var lastEvents: [CalendarEvent] = [] + private var lastRules: StartAlertSchedule.Rules? + + var onJoin: (CalendarEvent) -> Void = { _ in } + var onOpen: (CalendarEvent) -> Void = { _ in } + + func sync(events: [CalendarEvent], rules: StartAlertSchedule.Rules) { + lastEvents = events + lastRules = rules + timer?.invalidate() + timer = nil + guard rules.style != .off else { + close() + return + } + // Keep the alert that's up; the next one waits until it's handled. + guard shownKey == nil else { return } + let now = Date() + guard let pending = StartAlertSchedule.next( + events: events, + rules: rules, + handled: handled, + snoozed: snoozed, + now: now + ) else { return } + if pending.fireDate <= now { + show(pending.event, style: rules.style) + } else { + let timer = Timer(fire: pending.fireDate, interval: 0, repeats: false) { [weak self] _ in + Task { @MainActor in self?.resync() } + } + RunLoop.main.add(timer, forMode: .common) + self.timer = timer + } + } + + /// Previews the alert for the given event right away (Settings' test button). + func preview(_ event: CalendarEvent, style: StartAlertStyle) { + close() + show(event, style: style == .off ? .floating : style, isPreview: true) + } + + private func resync() { + guard let lastRules else { return } + sync(events: lastEvents, rules: lastRules) + } + + private func show(_ event: CalendarEvent, style: StartAlertStyle, isPreview: Bool = false) { + let key = StartAlertSchedule.key(for: event) + if !isPreview { + snoozed.removeValue(forKey: key) + handled.insert(key) + } + shownKey = key + let actions = StartAlertActions( + join: { [weak self] in + self?.onJoin(event) + self?.finish() + }, + snooze: { [weak self] in + if !isPreview { + self?.snoozed[key] = Date().addingTimeInterval(60) + } + self?.finish() + }, + dismiss: { [weak self] in self?.finish() }, + open: { [weak self] in + self?.onOpen(event) + self?.finish() + } + ) + switch style { + case .off: + return + case .floating: + panels = [makeFloatingPanel(event: event, actions: actions)] + panels.forEach { $0.orderFrontRegardless() } + case .fullScreen: + panels = NSScreen.screens.map { makeFullScreenPanel(event: event, actions: actions, screen: $0) } + NSApp.activate(ignoringOtherApps: true) + panels.forEach { $0.orderFrontRegardless() } + // The main display's panel takes the keyboard so Return joins and + // Escape dismisses. + panels.first?.makeKey() + } + } + + private func finish() { + close() + resync() + } + + private func close() { + panels.forEach { $0.orderOut(nil) } + panels = [] + shownKey = nil + } + + private func makeFloatingPanel(event: CalendarEvent, actions: StartAlertActions) -> NSPanel { + let size = NSSize(width: 380, height: 132) + let screen = NSScreen.main ?? NSScreen.screens.first + let visible = screen?.visibleFrame ?? .zero + let origin = NSPoint(x: visible.midX - size.width / 2, y: visible.maxY - size.height - 10) + let panel = StartAlertPanel( + contentRect: NSRect(origin: origin, size: size), + styleMask: [.borderless, .nonactivatingPanel], + backing: .buffered, + defer: false + ) + panel.level = .statusBar + configure(panel) + panel.contentView = NSHostingView(rootView: StartAlertView(event: event, style: .floating, actions: actions)) + return panel + } + + private func makeFullScreenPanel(event: CalendarEvent, actions: StartAlertActions, screen: NSScreen) -> NSPanel { + let panel = StartAlertPanel( + contentRect: screen.frame, + styleMask: [.borderless], + backing: .buffered, + defer: false + ) + // Above full-screen apps and the menubar, like a screen saver would be. + panel.level = .screenSaver + configure(panel) + panel.setFrame(screen.frame, display: false) + panel.contentView = NSHostingView(rootView: StartAlertView(event: event, style: .fullScreen, actions: actions)) + return panel + } + + private func configure(_ panel: NSPanel) { + panel.isReleasedWhenClosed = false + panel.isOpaque = false + panel.backgroundColor = .clear + panel.hasShadow = false + panel.hidesOnDeactivate = false + panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary] + } +} + +/// A borderless panel that can still take the keyboard. +private final class StartAlertPanel: NSPanel { + override var canBecomeKey: Bool { true } +} + +struct StartAlertActions { + var join: () -> Void + var snooze: () -> Void + var dismiss: () -> Void + var open: () -> Void +} + +struct StartAlertView: View { + var event: CalendarEvent + var style: StartAlertStyle + var actions: StartAlertActions + + var body: some View { + switch style { + case .fullScreen: + ZStack { + Color.black.opacity(0.55) + .ignoresSafeArea() + .onTapGesture {} + card + .frame(width: 420) + .padding(Theme.Spacing.xl) + .background( + Color(nsColor: .windowBackgroundColor), + in: RoundedRectangle(cornerRadius: 18, style: .continuous) + ) + .shadow(color: .black.opacity(0.35), radius: 30, y: 12) + } + default: + card + .padding(Theme.Spacing.md) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .background( + Color(nsColor: .windowBackgroundColor), + in: RoundedRectangle(cornerRadius: 14, style: .continuous) + ) + .overlay( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .strokeBorder(Color.primary.opacity(0.08)) + ) + .padding(6) + .shadow(color: .black.opacity(0.22), radius: 12, y: 6) + } + } + + private var card: some View { + TimelineView(.periodic(from: .now, by: 1)) { context in + let started = context.date >= event.startDate + let tint: Color = started ? .green : .accentColor + VStack(alignment: style == .fullScreen ? .center : .leading, spacing: Theme.Spacing.xs) { + HStack(alignment: .firstTextBaseline) { + Text(kicker(now: context.date)) + .font(.caption.weight(.bold)) + .foregroundStyle(tint) + if style != .fullScreen { + Spacer(minLength: Theme.Spacing.sm) + Text(clock(event.startDate)) + .font(.caption.weight(.semibold)) + .monospacedDigit() + .foregroundStyle(tint) + } + } + Text(event.title) + .font(.system(size: style == .fullScreen ? 22 : 15, weight: .bold)) + .lineLimit(2) + .multilineTextAlignment(style == .fullScreen ? .center : .leading) + if !detail.isEmpty { + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + buttons(tint: tint) + .padding(.top, Theme.Spacing.sm) + } + .frame(maxWidth: .infinity, alignment: style == .fullScreen ? .center : .leading) + } + } + + @ViewBuilder + private func buttons(tint: Color) -> some View { + HStack(spacing: Theme.Spacing.sm) { + if !event.conferenceUrl.isEmpty { + Button(action: actions.join) { + Label(loc("Join"), systemImage: "video.fill") + .frame(minWidth: style == .fullScreen ? 90 : nil) + } + .buttonStyle(.borderedProminent) + .tint(tint) + .controlSize(style == .fullScreen ? .large : .regular) + .keyboardShortcut(.defaultAction) + } else { + Button(loc("Open Event"), action: actions.open) + .controlSize(style == .fullScreen ? .large : .regular) + .keyboardShortcut(.defaultAction) + } + Button(loc("Snooze 1 min"), action: actions.snooze) + .controlSize(style == .fullScreen ? .large : .regular) + Button(loc("Dismiss"), action: actions.dismiss) + .controlSize(style == .fullScreen ? .large : .regular) + .keyboardShortcut(.cancelAction) + } + } + + private func kicker(now: Date) -> String { + if now >= event.startDate { + return loc("Starting now") + } + return loc("Starts in %@", relativeWhen(max(1, roundedMinutes(from: now, to: event.startDate)))) + } + + private var detail: String { + heroMetadataParts(for: event).joined(separator: " · ") + } +} diff --git a/Sources/Until/Views.swift b/Sources/Until/Views.swift index bd1b24f..a55cdeb 100644 --- a/Sources/Until/Views.swift +++ b/Sources/Until/Views.swift @@ -284,10 +284,13 @@ struct PanelView: View { Button(loc("Cancel"), role: .cancel) {} } - Text(statusText(now: now)) + // Today's shape when there is one; the sync time moves to the tooltip. + let summary = AppModel.daySummary(timed: model.state.events, day: now, now: now) + Text(summary?.text ?? statusText(now: now)) .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) + .help(statusText(now: now)) Spacer() @@ -351,22 +354,26 @@ struct OnboardingView: View { var body: some View { ScrollView(.vertical) { - VStack(spacing: Theme.Spacing.lg) { - VStack(spacing: Theme.Spacing.sm) { + VStack(spacing: Theme.Spacing.md) { + VStack(spacing: Theme.Spacing.xs) { ZStack { Circle() .fill(Color.accentColor.opacity(0.12)) - .frame(width: 56, height: 56) - Image(nsImage: BrandIcon.menubarImage(size: 28)) + .frame(width: 44, height: 44) + Image(nsImage: BrandIcon.menubarImage(size: 22)) .renderingMode(.template) .foregroundStyle(Color.accentColor) } - Text("Until") - .font(.title2.weight(.semibold)) Text(loc("Your next meeting, always in the menubar.")) - .font(.callout) - .foregroundStyle(.secondary) + .font(.callout.weight(.medium)) .multilineTextAlignment(.center) + Button { + model.startSamplePreview() + } label: { + Label(loc("Try it with a sample day"), systemImage: "sparkles") + .font(.caption) + } + .buttonStyle(.link) } VStack(alignment: .leading, spacing: Theme.Spacing.sm) { @@ -437,19 +444,11 @@ struct OnboardingView: View { } } .card(.inset, padding: Theme.Spacing.md) - - Button { - model.startSamplePreview() - } label: { - Label(loc("Try it with a sample day"), systemImage: "sparkles") - .font(.callout) - } - .buttonStyle(.link) } .frame(maxWidth: 320) .frame(maxWidth: .infinity) .padding(.horizontal, Theme.Spacing.xl) - .padding(.vertical, Theme.Spacing.lg) + .padding(.vertical, Theme.Spacing.md) } } @@ -804,6 +803,10 @@ private struct HeroTimelineRow: View { } } + if !event.links.isEmpty { + EventLinkChips(event: event) + } + // Without an attached action the row would hold nothing but the // hover-revealed ellipsis, stretching the card for no content. if hasAttachedActions { @@ -926,6 +929,41 @@ private struct HeroTimelineRow: View { } +/// What the meeting is about — the doc, design, or PR linked from the invite. +private struct EventLinkChips: View { + var event: CalendarEvent + + var body: some View { + HStack(spacing: Theme.Spacing.xs) { + ForEach(event.links, id: \.url) { link in + Button { + if let url = EventLinks.authenticatedURL(from: link.url, accountEmail: event.account.email) { + NSWorkspace.shared.open(url) + } + } label: { + Label(link.title, systemImage: systemImage(for: link)) + .font(.caption) + .lineLimit(1) + .padding(.horizontal, Theme.Spacing.sm) + .padding(.vertical, 2) + .background(Color.primary.opacity(0.06), in: Capsule()) + } + .buttonStyle(.plain) + .help(link.url) + } + } + } + + private func systemImage(for link: EventLink) -> String { + let title = link.title + if title.hasPrefix("Google") { return "doc.text" } + if title == "Figma" || title == "Miro" { return "paintpalette" } + if title.hasPrefix("PR #") { return "arrow.triangle.pull" } + if title.hasPrefix("Issue #") || title == "Jira" || title == "Linear" { return "checklist" } + return "link" + } +} + private struct CondensedHeroStrip: View { var event: CalendarEvent @ObservedObject var model: AppModel @@ -2180,6 +2218,21 @@ struct SettingsView: View { .labelsHidden() } } + Divider() + SettingRow(loc("Join shortcut"), subtitle: loc("Join the current or next meeting from anywhere")) { + HStack(spacing: Theme.Spacing.sm) { + Picker("", selection: $draft.joinHotkeyPreset) { + ForEach(joinHotkeyPresets) { preset in + Text(preset.label).tag(preset.id) + } + } + .labelsHidden() + .frame(width: 130) + .disabled(!draft.joinHotkeyEnabled) + Toggle("", isOn: $draft.joinHotkeyEnabled) + .labelsHidden() + } + } #if SPARKLE Divider() SettingRow( @@ -2226,11 +2279,20 @@ struct SettingsView: View { } SettingsCard(loc("Menubar")) { + SettingRow( + loc("Show event title"), + subtitle: loc("Turn off to show only the countdown, e.g. while presenting") + ) { + Toggle("", isOn: $draft.menubarShowsTitle) + .labelsHidden() + } + Divider() stepperRow( loc("Max title length"), subtitle: loc("Longer event titles are shortened with “…”"), value: $draft.maxTitleLength, range: 10...120, step: 1, unit: loc("characters") ) + .disabled(!draft.menubarShowsTitle) Divider() SettingRow(loc("Show upcoming event"), subtitle: loc("When the next event appears in the menubar")) { Picker("", selection: menubarWindowSelection) { @@ -2259,6 +2321,8 @@ struct SettingsView: View { .frame(maxWidth: .infinity, alignment: .leading) } + meetingsCard + SettingsCard(loc("Notifications")) { SettingRow(loc("Event reminders"), subtitle: loc("Send a notification before an event starts")) { Toggle("", isOn: $draft.notifyEnabled) @@ -2336,6 +2400,74 @@ struct SettingsView: View { } } + /// The join shortcut can't reuse the popover shortcut's keys while that + /// one is on. + private var joinHotkeyPresets: [HotkeyManager.Preset] { + HotkeyManager.presets.filter { !draft.hotkeyEnabled || $0.id != draft.hotkeyPreset } + } + + private var startAlertStyle: Binding { + Binding( + get: { StartAlertStyle(rawValue: draft.startAlertStyle) ?? .off }, + set: { draft.startAlertStyle = $0.rawValue } + ) + } + + private var meetingsCard: some View { + SettingsCard(loc("Meetings")) { + SettingRow( + loc("Open in desktop apps"), + subtitle: loc("Zoom and Teams links open in their apps when installed") + ) { + Toggle("", isOn: $draft.openMeetingsInApps) + .labelsHidden() + } + Divider() + SettingRow( + loc("Start alert"), + subtitle: loc("Put the meeting on screen as it starts, until you join or dismiss it") + ) { + Picker("", selection: startAlertStyle) { + ForEach(StartAlertStyle.allCases) { style in + Text(style.label).tag(style) + } + } + .pickerStyle(.segmented) + .labelsHidden() + .frame(width: 240) + } + Divider() + stepperRow( + loc("Alert timing"), + subtitle: loc("How long before the meeting the alert appears"), + value: $draft.startAlertLeadMinutes, range: 0...10, step: 1, unit: loc("min before") + ) + .disabled(startAlertStyle.wrappedValue == .off) + Divider() + SettingRow(loc("Alert for"), subtitle: nil) { + Picker("", selection: $draft.startAlertVideoOnly) { + Text(loc("All events")).tag(false) + Text(loc("Video only")).tag(true) + } + .pickerStyle(.segmented) + .labelsHidden() + .frame(width: 210) + } + .disabled(startAlertStyle.wrappedValue == .off) + Divider() + HStack { + Button { + model.saveConfig(draft) + model.previewStartAlert() + } label: { + Label(loc("Preview alert"), systemImage: "rectangle.inset.topright.filled") + } + .disabled(startAlertStyle.wrappedValue == .off) + Spacer() + } + } + } + private var filtersTab: some View { let preview = model.filterPreview(for: draft.filterRules) return SettingsTab(maxColumnWidth: .infinity) { diff --git a/Tests/UntilTests/MeetingWorkflowTests.swift b/Tests/UntilTests/MeetingWorkflowTests.swift new file mode 100644 index 0000000..f28674e --- /dev/null +++ b/Tests/UntilTests/MeetingWorkflowTests.swift @@ -0,0 +1,196 @@ +import XCTest +@testable import Until + +final class MeetingWorkflowTests: XCTestCase { + // MARK: - Meeting services + + func testAddedServicesAreDetected() { + let cases: [(String, EventLinks.MeetingProvider)] = [ + ("https://app.slack.com/huddle/T123/C456", .slackHuddle), + ("https://discord.gg/abc", .discord), + ("https://discord.com/channels/1/2", .discord), + ("https://meet.jit.si/Room", .jitsi), + ("https://facetime.apple.com/join#v=1", .faceTime), + ("https://app.chime.aws/meetings/123", .chime), + ("https://app.gather.town/app/x", .gather), + ("https://zoomgov.com/j/123", .zoom), + ("https://teams.live.com/meet/123", .teams), + ("https://vc.larksuite.com/j/123", .larkMeetings), + ("https://meeting.tencent.com/dm/abc", .tencentMeeting), + ("https://app.cal.com/video/abc", .calVideo), + ("https://signal.link/call/#key=abc", .signal) + ] + for (url, provider) in cases { + XCTAssertEqual(EventLinks.meetingProvider(for: url), provider, url) + } + XCTAssertGreaterThanOrEqual(EventLinks.MeetingProvider.allCases.count, 30) + } + + func testOrdinaryPagesOnCallHostsAreNotMeetings() { + XCTAssertNil(EventLinks.meetingProvider(for: "https://acme.slack.com/archives/C123")) + XCTAssertNil(EventLinks.meetingProvider(for: "https://discord.com/developers")) + XCTAssertNil(EventLinks.meetingProvider(for: "https://app.cal.com/team/acme")) + XCTAssertNil(EventLinks.meetingProvider(for: "https://signal.link/")) + } + + func testZoomLinkBecomesAppJoinURLWithPassword() { + let url = URL(string: "https://acme.zoom.us/j/123456789?pwd=secret")! + XCTAssertEqual( + EventLinks.desktopAppURL(for: url)?.absoluteString, + "zoommtg://acme.zoom.us/join?action=join&confno=123456789&pwd=secret" + ) + } + + func testTeamsLinkUsesTeamsScheme() { + let url = URL(string: "https://teams.microsoft.com/l/meetup-join/abc")! + XCTAssertEqual(EventLinks.desktopAppURL(for: url)?.absoluteString, "msteams://teams.microsoft.com/l/meetup-join/abc") + } + + func testOtherLinksStayInTheBrowser() { + XCTAssertNil(EventLinks.desktopAppURL(for: URL(string: "https://meet.google.com/abc-defg-hij")!)) + XCTAssertNil(EventLinks.desktopAppURL(for: URL(string: "https://zoom.us/my/vanity")!)) + } + + // MARK: - Join shortcut + + func testJoinPrefersTheMenubarMeetingWhenItHasALink() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let menubar = event("menubar", now: now, startOffset: 20, link: true) + let running = event("running", now: now, startOffset: -5, link: true) + + XCTAssertEqual(AppModel.joinTarget(menubarEvent: menubar, timed: [running, menubar], now: now)?.id, "menubar") + } + + func testJoinFallsBackToTheRunningMeeting() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let menubar = event("menubar", now: now, startOffset: 3, link: false) + let older = event("older", now: now, startOffset: -40, link: true) + let recent = event("recent", now: now, startOffset: -5, link: true) + let soon = event("soon", now: now, startOffset: 5, link: true) + + XCTAssertEqual( + AppModel.joinTarget(menubarEvent: menubar, timed: [older, recent, soon, menubar], now: now)?.id, + "recent" + ) + } + + func testJoinTakesTheSoonestUpcomingMeetingWithinFifteenMinutes() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let soon = event("soon", now: now, startOffset: 10, link: true) + let later = event("later", now: now, startOffset: 30, link: true) + + XCTAssertEqual(AppModel.joinTarget(menubarEvent: nil, timed: [later, soon], now: now)?.id, "soon") + XCTAssertNil(AppModel.joinTarget(menubarEvent: nil, timed: [later], now: now)) + } + + // MARK: - Start alert + + func testStartAlertFiresLeadMinutesBeforeTheMeeting() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let meeting = event("m", now: now, startOffset: 10, link: true) + + let pending = StartAlertSchedule.next( + events: [meeting], rules: .init(style: .floating, leadMinutes: 2, videoOnly: true), handled: [], snoozed: [:], now: now + ) + + XCTAssertEqual(pending?.event.id, "m") + XCTAssertEqual(pending?.fireDate, meeting.startDate.addingTimeInterval(-120)) + } + + func testStartAlertSkipsHandledLinklessAndLongStartedMeetings() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let handled = event("handled", now: now, startOffset: 5, link: true) + let noLink = event("no-link", now: now, startOffset: 6, link: false) + let longStarted = event("long-started", now: now, startOffset: -10, link: true) + + let pending = StartAlertSchedule.next( + events: [handled, noLink, longStarted], + rules: .init(style: .floating, leadMinutes: 1, videoOnly: true), + handled: [StartAlertSchedule.key(for: handled)], + snoozed: [:], + now: now + ) + XCTAssertNil(pending) + + let allEvents = StartAlertSchedule.next( + events: [noLink], rules: .init(style: .floating, leadMinutes: 1, videoOnly: false), handled: [], snoozed: [:], now: now + ) + XCTAssertEqual(allEvents?.event.id, "no-link") + } + + func testSnoozedAlertReturnsAtTheSnoozeTime() { + let now = makeDate(year: 2026, month: 9, day: 30, hour: 10) + let meeting = event("m", now: now, startOffset: 0, link: true) + let key = StartAlertSchedule.key(for: meeting) + let until = now.addingTimeInterval(60) + + let pending = StartAlertSchedule.next( + events: [meeting], rules: .init(style: .floating, leadMinutes: 1, videoOnly: true), handled: [key], snoozed: [key: until], now: now + ) + + XCTAssertEqual(pending?.fireDate, until) + } + + // MARK: - Day summary + + func testDaySummaryMergesOverlapsAndFindsTheLongestRemainingGap() throws { + let day = makeDate(year: 2026, month: 9, day: 30) + let now = makeDate(year: 2026, month: 9, day: 30, hour: 9) + let events = [ + timed("a", day: day, from: (9, 0), to: (10, 0)), + timed("b", day: day, from: (9, 30), to: (10, 30)), + timed("c", day: day, from: (12, 0), to: (13, 0)), + timed("d", day: day, from: (13, 30), to: (14, 0)), + timed("free", day: day, from: (15, 0), to: (16, 0), transparency: "free") + ] + + let summary = try XCTUnwrap(AppModel.daySummary(timed: events, day: day, now: now)) + + XCTAssertEqual(summary.eventCount, 4) + XCTAssertEqual(summary.bookedMinutes, 180) + XCTAssertEqual(summary.longestFreeMinutes, 90) + } + + func testDaySummaryHasNoFreeTimeOnceTheDayIsDone() throws { + let day = makeDate(year: 2026, month: 9, day: 30) + let now = makeDate(year: 2026, month: 9, day: 30, hour: 18) + let summary = try XCTUnwrap( + AppModel.daySummary(timed: [timed("a", day: day, from: (9, 0), to: (10, 0))], day: day, now: now) + ) + + XCTAssertNil(summary.longestFreeMinutes) + XCTAssertNil(AppModel.daySummary(timed: [], day: day, now: now)) + } + + // MARK: - Helpers + + private func event(_ id: String, now: Date, startOffset minutes: Int, link: Bool) -> CalendarEvent { + let start = now.addingTimeInterval(TimeInterval(minutes) * 60) + return makeEvent( + id: id, + title: id, + startISO: isoString(from: start), + endISO: isoString(from: start.addingTimeInterval(30 * 60)), + conferenceUrl: link ? "https://meet.google.com/\(id)" : "" + ) + } + + private func timed( + _ id: String, + day: Date, + from start: (Int, Int), + to end: (Int, Int), + transparency: String = "busy" + ) -> CalendarEvent { + let calendar = Calendar.current + let startDate = calendar.date(bySettingHour: start.0, minute: start.1, second: 0, of: day)! + let endDate = calendar.date(bySettingHour: end.0, minute: end.1, second: 0, of: day)! + return makeEvent( + id: id, + title: id, + startISO: isoString(from: startDate), + endISO: isoString(from: endDate), + transparency: transparency + ) + } +} From 49b19fa529b61c4b374f3bdeabb90f2b6e314569 Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:48:07 +0900 Subject: [PATCH 04/11] Build universal releases and publish a stable Until.dmg - UNIVERSAL=1 builds the app and the widget extension for Apple Silicon and Intel; release.sh turns it on by default. - The release workflow also uploads the installer as Until.dmg so releases/latest/download/Until.dmg always points at the newest one. - Add a Homebrew cask template for the combinatrix-ai/homebrew-tap repo. - Document the new sources and the release steps in AGENTS.md. --- .github/workflows/release.yml | 5 ++++- AGENTS.md | 19 +++++++++++++++---- packaging/homebrew/until.rb | 30 ++++++++++++++++++++++++++++++ scripts/package-app.sh | 26 +++++++++++++++++++++----- scripts/release.sh | 2 ++ 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 packaging/homebrew/until.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd45175..22cbd83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,6 +79,9 @@ jobs: run: | TAG="${GITHUB_REF_NAME:-$(git rev-parse --short HEAD)}" cp .build/release/Until.dmg "Until-${TAG}.dmg" + # A stable name too, so releases/latest/download/Until.dmg always + # points at the newest installer (the website and README link it). + cp .build/release/Until.dmg Until.dmg # Sparkle updates from a .zip of the notarized + stapled .app (the .dmg # is only for first-time install). Stapled ticket rides inside the zip # so the update validates offline. @@ -110,7 +113,7 @@ jobs: # releases/latest/download/appcast.xml URL that the app's SUFeedURL # points at; the .zip is the update payload, the .dmg the installer. gh release create "$GITHUB_REF_NAME" \ - Until-*.dmg Until-*.zip appcast.xml \ + Until.dmg Until-*.dmg Until-*.zip appcast.xml \ --title "$GITHUB_REF_NAME" --generate-notes - name: Upload app artifact diff --git a/AGENTS.md b/AGENTS.md index 16c7e1f..75dfabc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ # Until - build & run -macOS menubar calendar app (Swift + SwiftUI/AppKit). Shows your next Google -Calendar event in the menubar, filtered by user-authored structured rules. +macOS menubar calendar app (Swift + SwiftUI/AppKit). Shows your next event in +the menubar — from the Mac's Calendar app (EventKit) and/or Google accounts — +filtered by user-authored structured rules. The SwiftPM package, target, and product are **Until** (bundle id `ai.combinatrix.until`). ## Development workflow @@ -45,8 +46,13 @@ checklist. A release-status question stays read-only. ``` scripts/release.sh ``` -Builds release, Developer ID-signs with the hardened runtime, notarizes, and -staples. Needs a notarytool keychain profile (`until-notary` by default) — see +Builds a universal (Apple Silicon + Intel) release, Developer ID-signs with the +hardened runtime, notarizes, and staples. `UNIVERSAL=0` builds only this Mac's +architecture. The release workflow uploads the installer twice: as +`Until-vX.Y.Z.dmg` and as `Until.dmg`, which `releases/latest/download/Until.dmg` +(website and README) resolves to. After a release, update the Homebrew cask: +copy `packaging/homebrew/until.rb` into `combinatrix-ai/homebrew-tap` as +`Casks/until.rb` with the new `version` and `sha256`. Needs a notarytool keychain profile (`until-notary` by default) — see the header of `scripts/release.sh`. The app icon is generated by `scripts/make-icon.swift` into `scripts/Until.icns` (delete it to regenerate). @@ -77,6 +83,11 @@ the header of `scripts/release.sh`. The app icon is generated by - `Views.swift` — SwiftUI panel and preferences UI - `GoogleAuth.swift` — Google OAuth PKCE + loopback, token refresh - `CalendarClient.swift` — Calendar REST fetch + event normalization + - `MacCalendarSource.swift` — EventKit (Calendar app) calendars and events, + cross-source de-duplication, invite link extraction + - `EventLinks.swift` — meeting service detection and desktop-app links + - `StartAlert.swift` — the optional floating / full-screen start alert + - `HotkeyManager.swift` — Carbon global hotkeys (popover toggle, join) - `RuleEngine.swift` — native structured filter evaluator - `AppModel.swift` — config, polling, derived `AppState`, notification sync - `EventNotifier.swift` — native UserNotifications reminders diff --git a/packaging/homebrew/until.rb b/packaging/homebrew/until.rb new file mode 100644 index 0000000..60c3e61 --- /dev/null +++ b/packaging/homebrew/until.rb @@ -0,0 +1,30 @@ +# Homebrew cask for Until, published from the combinatrix-ai/homebrew-tap +# repository as Casks/until.rb. After each release, update `version` and +# `sha256` (shasum -a 256 Until-v.dmg) and push the tap. +cask "until" do + version "1.2.0" + sha256 "REPLACE_WITH_SHA256_OF_Until-v1.2.0.dmg" + + url "https://github.com/combinatrix-ai/until/releases/download/v#{version}/Until-v#{version}.dmg" + name "Until" + desc "Next meeting countdown, day timeline, and meeting notes in the menu bar" + homepage "https://until.combinatrix.ai/" + + livecheck do + url :url + strategy :github_latest + end + + auto_updates true + depends_on macos: ">= :ventura" + + app "Until.app" + + zap trash: [ + "~/Library/Application Support/Until", + "~/Library/Caches/ai.combinatrix.until", + "~/Library/Group Containers/3Y275A5TZ8.ai.combinatrix.until", + "~/Library/HTTPStorages/ai.combinatrix.until", + "~/Library/Preferences/ai.combinatrix.until.plist", + ] +end diff --git a/scripts/package-app.sh b/scripts/package-app.sh index a7e0d49..7aaf8b8 100755 --- a/scripts/package-app.sh +++ b/scripts/package-app.sh @@ -19,6 +19,8 @@ CONFIGURATION="${CONFIGURATION:-debug}" APP_VERSION="${APP_VERSION:-0.1.0}" BUILD_NUMBER="${BUILD_NUMBER:-1}" MAS="${MAS:-0}" +# UNIVERSAL=1 builds one binary for Apple Silicon and Intel (release.sh sets it). +UNIVERSAL="${UNIVERSAL:-0}" APP_NAME="Until" APP_DIR="$ROOT/.build/$CONFIGURATION/$APP_NAME.app" EXECUTABLE="$ROOT/.build/$CONFIGURATION/Until" @@ -83,6 +85,9 @@ fi if [[ "$MAS" == "1" ]]; then build_args+=(--disable-default-traits) fi +if [[ "$UNIVERSAL" == "1" ]]; then + build_args+=(--arch arm64 --arch x86_64) +fi swift build "${build_args[@]}" rm -rf "$APP_DIR" @@ -225,7 +230,6 @@ widget_compile_args=( # when WidgetKit asks ExtensionFoundation to initialize the widget. -Xlinker -e -Xlinker _NSExtensionMain -swift-version 5 - -target "$(uname -m)-apple-macos14.0" -module-cache-path "$ROOT/.build/widget-module-cache" -framework SwiftUI -framework WidgetKit @@ -233,10 +237,22 @@ widget_compile_args=( if [[ "$CONFIGURATION" == "release" ]]; then widget_compile_args+=(-O) fi -xcrun swiftc "${widget_compile_args[@]}" \ - "$ROOT/Sources/Until/WidgetAgendaSnapshot.swift" \ - "$ROOT/Sources/UntilWidget/UntilWidget.swift" \ - -o "$WIDGET_DIR/Contents/MacOS/UntilWidget" +widget_archs=("$(uname -m)") +if [[ "$UNIVERSAL" == "1" ]]; then + widget_archs=(arm64 x86_64) +fi +widget_slices=() +for arch in "${widget_archs[@]}"; do + slice="$ROOT/.build/widget-$arch/UntilWidget" + mkdir -p "$(dirname "$slice")" + xcrun swiftc "${widget_compile_args[@]}" \ + -target "$arch-apple-macos14.0" \ + "$ROOT/Sources/Until/WidgetAgendaSnapshot.swift" \ + "$ROOT/Sources/UntilWidget/UntilWidget.swift" \ + -o "$slice" + widget_slices+=("$slice") +done +lipo -create "${widget_slices[@]}" -output "$WIDGET_DIR/Contents/MacOS/UntilWidget" cp "$ROOT/Sources/UntilWidget/Info.plist" "$WIDGET_DIR/Contents/Info.plist" plutil -replace CFBundleShortVersionString -string "$APP_VERSION" "$WIDGET_DIR/Contents/Info.plist" plutil -replace CFBundleVersion -string "$BUILD_NUMBER" "$WIDGET_DIR/Contents/Info.plist" diff --git a/scripts/release.sh b/scripts/release.sh index 56d8a20..3f52600 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -16,6 +16,7 @@ set -euo pipefail # Usage: # scripts/release.sh # build + sign + notarize + staple # NOTARIZE=0 scripts/release.sh # build + Developer ID sign only +# UNIVERSAL=0 scripts/release.sh # this Mac's architecture only (faster) # # Env overrides: # CODESIGN_IDENTITY signing identity, e.g. "Developer ID Application: Example Corp (TEAMID)" @@ -33,6 +34,7 @@ CODESIGN_IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY to your Developer APP_DIR="$( CONFIGURATION=release \ DISTRIBUTION=1 \ + UNIVERSAL="${UNIVERSAL:-1}" \ TEAM_ID="$TEAM_ID" \ CODESIGN_IDENTITY="$CODESIGN_IDENTITY" \ "$ROOT/scripts/package-app.sh" | tail -n 1 From 087e54c51a3ed1efcfe117406f59bf7dd5fe708f Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:48:08 +0900 Subject: [PATCH 05/11] Rewrite the README and add contributor docs Lead with install, the three reasons to use Until, calendar sources, a comparison with similar apps, and an FAQ; add a Japanese README, CONTRIBUTING, SECURITY, issue templates, and a changelog for 1.2.0. --- .github/ISSUE_TEMPLATE/bug_report.yml | 35 +++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 15 ++ .github/ISSUE_TEMPLATE/meeting_link.yml | 28 ++++ CHANGELOG.md | 32 +++++ CONTRIBUTING.md | 42 ++++++ README.ja.md | 77 ++++++++++ README.md | 159 ++++++++++++++------- SECURITY.md | 8 ++ 9 files changed, 346 insertions(+), 55 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/meeting_link.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 README.ja.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..f6f03f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,35 @@ +name: Bug report +description: Something in Until doesn't work as expected. +labels: [bug] +body: + - type: textarea + id: what + attributes: + label: What happened? + description: What you did, what you expected, and what you saw. Screenshots help. + validations: + required: true + - type: input + id: version + attributes: + label: Until version + description: Settings → the version under the sidebar. + placeholder: "1.2.0" + validations: + required: true + - type: input + id: macos + attributes: + label: macOS version + placeholder: "15.5" + - type: dropdown + id: source + attributes: + label: Calendar source + options: + - Calendars on this Mac + - Google account + - Both + - type: markdown + attributes: + value: Please remove private details (event titles, attendees, links) from anything you paste. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ba953ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions and ideas + url: https://github.com/combinatrix-ai/until/discussions + about: Ask a question or share how you use Until. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..ea87b1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,15 @@ +name: Feature request +description: Suggest an idea for Until. +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: What are you trying to do? + description: The situation and what gets in the way today. + validations: + required: true + - type: textarea + id: idea + attributes: + label: What would help? diff --git a/.github/ISSUE_TEMPLATE/meeting_link.yml b/.github/ISSUE_TEMPLATE/meeting_link.yml new file mode 100644 index 0000000..74cb531 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/meeting_link.yml @@ -0,0 +1,28 @@ +name: Meeting link not detected +description: Until doesn't show a Join button for a meeting service. +labels: [meeting-service, good first issue] +body: + - type: input + id: service + attributes: + label: Service + placeholder: "e.g. Pumble, Butter, a self-hosted Jitsi" + validations: + required: true + - type: input + id: example + attributes: + label: Example link format + description: Replace IDs and passwords with placeholders. + placeholder: "https://meet.example.com/j/XXXX" + validations: + required: true + - type: dropdown + id: field + attributes: + label: Where the link is in the event + options: + - Location + - Description / notes + - URL field + - Not sure diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5875e8e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +Release notes for each version are on +[GitHub Releases](https://github.com/combinatrix-ai/until/releases). This file +collects what is ready for the next one. + +## 1.2.0 (unreleased) + +### New +- **Calendars on this Mac.** Until can show everything the Calendar app + already syncs — iCloud, Google, Outlook/Exchange, CalDAV — without signing + in. Mac calendars and Google accounts work side by side, and events that + appear through both are merged. +- **Try it with a sample day.** The first-run popover can show a sample day + before you connect a calendar. +- **Start alert.** Optionally put the meeting on screen as it starts, as a + floating card or a full-screen takeover on every display, with Join, Snooze + 1 min, and Dismiss. +- **Join shortcut.** A global shortcut joins the meeting in the menubar, or + the one that is running or about to start. +- **Desktop widgets** with today's agenda, in medium and large sizes. +- **Today at a glance.** The popover footer shows how many events are left, + how much of the day is booked, and the longest free stretch. +- **Links from the invite.** The upcoming card shows up to three docs, + designs, or pull requests linked from the event. + +### Changed +- Meeting links from 30+ services are recognized, including Slack huddles, + Discord, Jitsi, FaceTime, Amazon Chime, Gather, Tuple, Lark, and Tencent + Meeting. Zoom and Teams links can open in their desktop apps. +- The menubar can show only the countdown, hiding the event title. +- Until runs natively on Intel Macs as well as Apple Silicon. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e468314 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to Until + +Thanks for helping. Focused fixes, new meeting services, translations, and +documentation are all welcome. + +## Build and run + +Until is a plain SwiftPM package (Swift + SwiftUI/AppKit), no Xcode project. + +```sh +swift build # compile +swift test # run the tests +scripts/dev.sh # rebuild and relaunch as a real .app bundle +scripts/dev.sh --demo-mode # the same, with a synthetic calendar +``` + +Mac calendars work without any setup. Google sign-in needs your own OAuth +desktop client: + +1. In Google Cloud Console, create a project, enable the **Google Calendar + API** (and the Drive and Docs APIs for meeting notes), and create an OAuth + client ID of type **Desktop app**. +2. `cp .env.example .env` and fill in `GOOGLE_OAUTH_CLIENT_ID` and + `GOOGLE_OAUTH_CLIENT_SECRET`. The packaging script bakes them into the app's + `Info.plist`; `.env` is gitignored. + +[AGENTS.md](AGENTS.md) has the full development notes: layout, demo mode for +screenshots, and the release and Mac App Store builds. + +## Good first contributions + +- **A meeting service Until doesn't recognize.** Add its domains to + `EventLinks.MeetingProvider` in `Sources/Until/EventLinks.swift`, plus a test + in `Tests/UntilTests/MeetingWorkflowTests.swift`. +- **A translation.** Strings live in `Sources/Until/Resources/.lproj/`; + English keys double as the source text. + +## Pull requests + +Work on a branch and open a pull request against `main`. Please run +`swift test` and `swiftlint` before you push, and keep each pull request to one +change. diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 0000000..5d89991 --- /dev/null +++ b/README.ja.md @@ -0,0 +1,77 @@ +

+ Until のロゴ +

+ +

Until

+ +

+ 次の予定を、いつもメニューバーに。
+ macOS のメニューバーに次の会議までのカウントダウン。クリックすれば一日がひとつのタイムラインに。議事録はワンクリックで。 +

+ +

+ macOS 版をダウンロード + · Webサイト + · English +

+ +

+ メニューバーのカウントダウンから一日のタイムラインを開いたところ。次の予定のカードに参加ボタンと議事録ボタンがある +

+ +## インストール + +**ダウンロード:** [`Until.dmg`](https://github.com/combinatrix-ai/until/releases/latest/download/Until.dmg) を開き、`Until.app` をアプリケーションフォルダへドラッグします。Apple の公証済みで、アップデートは自動です。 + +**Homebrew:** + +```sh +brew install --cask combinatrix-ai/tap/until +``` + +無料・オープンソース。macOS 13 以降、Apple シリコンと Intel の両方に対応。 + +## Until の特徴 + +- **カウントダウンはいつも目に入る場所に。** 次の予定をメニューバーに表示し、始まったら残り時間に切り替わります。⌥クリックか参加ショートカットで、すぐ会議に入れます。 +- **一日をひとつのタイムラインで。** メニューバーをクリックすると、終わった予定・いまの時刻・この先の予定が一本のレールに並び、会議の間の空き時間も見えます。下部には今日の予定の件数・合計時間・残りの最長の空き時間を表示します。 +- **議事録はワンクリック。** Google アカウントを接続すると、テンプレートから Google ドキュメントを作り、参加者と共有して、予定に添付するまでをワンクリックで行います。次の予定のカードには、招待に含まれる資料・デザイン・PR へのリンクも表示します。 + +## 使えるカレンダー + +- **このMacのカレンダー** — カレンダーAppに入っている予定(iCloud・Google・Outlook/Exchange・CalDAV)をそのまま表示します。サインイン不要で、予定がこのMacの外に送られることはありません。 +- **Google アカウント** — 直接サインインすると(複数アカウント可)、議事録の作成と Google Meet リンクの追加ができます。 + +どちらか一方でも、両方でも使えます。両方に出てくる同じ予定は1つにまとめます。 + +## 機能 + +- **メニューバーのカウントダウン**。開始後は残り時間を表示。発表中は予定名を隠せます。 +- **開始アラート**(任意)。会議の開始時に、浮動カードか、すべてのディスプレイへの全画面表示で知らせます。参加・1分後に再通知・閉じるを選べます。 +- **ネイティブの通知**(スヌーズ付き)。 +- **ワンクリック参加**。Google Meet・Zoom・Microsoft Teams・Webex・Slack ハドル・Discord・Jitsi・FaceTime など30以上のサービスに対応。Zoom と Teams はデスクトップアプリで開けます。 +- **デスクトップウィジェット**で今日の予定を一覧。 +- **細かいフィルター**。カレンダー・タイトル・参加者・自分の返答・長さなどのルールで、表示する予定を決められます。 +- **接続前に試せる**サンプルの一日。 +- グローバルショートカット、ログイン時に起動、アイコンの右クリックで表示を折りたたみ。 +- **日本語と英語**に対応。 + +## よくある質問 + +**無料ですか?** はい。MIT ライセンスで、アカウント登録もサブスクリプションもありません。 + +**予定のデータはどこかに送られますか?** いいえ。Until のサーバーはありません。Mac のカレンダーは Mac の中で読み取り、Google のデータは Google の API から直接 Mac に届きます。サインイン情報は macOS のキーチェーンに保存します。詳しくは[プライバシーポリシー](https://until.combinatrix.ai/privacy.html)をご覧ください。 + +**Mac のカレンダーで使えるなら、Google にサインインする理由は?** 議事録ドキュメントの作成と Meet リンクの追加のためだけです。それ以外は Mac のカレンダーだけで使えます。 + +**会社の Google Workspace でサードパーティ製アプリが許可されていません。** 「このMacのカレンダー」を使ってください。仕事用カレンダーがカレンダーAppに入っていれば、Google へのサインインなしで表示できます。 + +**Outlook は使えますか?** Exchange や Microsoft 365 のアカウントをカレンダーApp(システム設定 → インターネットアカウント)に追加し、「このMacのカレンダー」をオンにしてください。 + +## ソースからのビルド + +[CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。 + +## ライセンス + +[MIT](LICENSE) diff --git a/README.md b/README.md index 53c1330..39cfc49 100644 --- a/README.md +++ b/README.md @@ -2,78 +2,127 @@ Until logo

-# Until +

Until

-**Next up, always visible.** Until keeps your next Google Calendar event in the -macOS menubar, with join links, meeting notes, reminders, and precise filters -close at hand. +

+ Next up, always visible.
+ Your next meeting in the macOS menu bar — a live countdown, your whole day on one timeline, and meeting notes in one click. +

+ +

+ Latest release + macOS 13 or later + Apple Silicon and Intel + MIT license +

-Website: · -Privacy policy: +

+ Download for macOS + · Website + · 日本語 +

Until's menubar countdown expanded into the day timeline, with the next event's Join and Open notes actions inline

-## Features +## Install -- **Menubar countdown** — the next event stays in the menubar all day with a - live countdown, then shows the time remaining once the meeting starts. -- **Your day on one timeline** — the popover opens on the present moment: the - current or next event sits inline as a card, the rest of the day runs above - and below it on a single rail, and free gaps are called out between them. -- **Today at a glance** — native macOS widgets show today's agenda, highlighting - the current or next event alongside all-day and upcoming events. -- **Never lose the current meeting** — when the next event closes in, the - countdown switches ahead while the meeting you're still in stays marked NOW - on the timeline. -- **One-click join** — click an event's meeting link, or option-click the - menubar item to join the meeting it's showing (Meet, Zoom, Teams, - Whereby, and friends). -- **Native reminders** — sleep-safe notifications before events, with snooze. -- **Meeting notes** — one click creates a Google Doc from your template, - shares it with every attendee, and attaches it to the event; docs are - organized in an app-managed Drive folder. -- **Precise filters** — a structured rule builder decides which events count - (by calendar, title, attendees, response status, and more). -- **Quick access** — global hotkey, launch at login, right-click the menubar - icon to hide the event text, event context menus. -- **Localized** — English and Japanese. -- **Private by design** — OAuth tokens live in the macOS Keychain, the Drive - scope is the non-sensitive `drive.file` tier (the app only sees files it - created), and there are no servers: your data goes straight from Google's - APIs to your Mac. +**Download:** grab [`Until.dmg`](https://github.com/combinatrix-ai/until/releases/latest/download/Until.dmg), +open it, and drag `Until.app` into Applications. It is signed and notarized by +Apple and updates itself. -## Install +**Homebrew:** -Download the latest notarized build from -[Releases](https://github.com/combinatrix-ai/until/releases/latest), open the -`.dmg`, and drag `Until.app` into `/Applications`. +```sh +brew install --cask combinatrix-ai/tap/until +``` -Requires macOS 13 or later. +Free and open source. macOS 13 or later, Apple Silicon and Intel. -## Build from source +## Why Until -Until is a plain SwiftPM package (Swift + SwiftUI/AppKit). +- **The countdown, where you already look.** The next event sits in the menu + bar with a live countdown and flips to time left once it starts. ⌥-click it, + or press the join shortcut from anywhere, to jump into the call. +- **Your day on one timeline.** Click the menu bar and the whole day unfolds on + a single rail: finished events above, the current moment in the middle, and + free time called out between meetings. The footer sums the day up — how many + events, how much is booked, and the longest free stretch left. +- **Meeting notes in one click.** With a Google account connected, one click + creates a Google Doc from your template, shares it with the attendees, and + attaches it to the event. The upcoming card also surfaces the docs, designs, + and pull requests linked from the invite. -```sh -swift build # compile -swift run Until # run the bare executable -scripts/dev.sh # rebuild + relaunch as a real .app bundle (recommended) -scripts/package-app.sh # produce .build/debug/Until.app -``` +## Works with your calendars -Google sign-in needs your own OAuth desktop client (one-time setup): +- **Calendars on this Mac** — everything the Calendar app already syncs: + iCloud, Google, Outlook/Exchange, CalDAV. No sign-in; events are read on your + Mac and never leave it. +- **Google accounts** — sign in directly (several accounts at once) to add + meeting notes docs and Google Meet links to events. -1. In Google Cloud Console, create a project, enable the **Google Calendar - API** (and the Drive/Docs APIs if you use meeting notes), and create an - OAuth client ID of type **Desktop app**. -2. `cp .env.example .env` and fill in `GOOGLE_OAUTH_CLIENT_ID` / - `GOOGLE_OAUTH_CLIENT_SECRET`. The packaging script bakes them into the - app's `Info.plist`; `.env` is gitignored. +Use either, or both: events that show up through both are merged. + +## Features + +- **Menu bar countdown** that switches to time left once a meeting starts, and + can hide the title while you present. +- **Start alert** (optional) — a floating card or a full-screen takeover on + every display as a meeting starts, with Join, Snooze, and Dismiss. +- **Native reminders** before events, with snooze. +- **One-click join** for 30+ services — Google Meet, Zoom, Microsoft Teams, + Webex, Slack huddles, Discord, Jitsi, FaceTime, Whereby, Around, and more. + Zoom and Teams can open in their desktop apps. +- **Desktop widgets** with today's agenda. +- **Precise filters** — a rule builder decides which events count (calendar, + title, attendees, your response, duration, and more). +- **Try before connecting** — a sample day shows the timeline before you pick a + calendar. +- **Quick access** — global shortcuts, launch at login, right-click the icon to + collapse it. +- **English and Japanese.** + +## How it compares + +| | Until | MeetingBar | Dato | Dot | +|---|---|---|---|---| +| Price | Free, open source | Free, open source | $18 | $14.99 | +| Calendars | macOS Calendar + Google sign-in | macOS Calendar + Google | macOS Calendar | macOS Calendar | +| Menu bar countdown | ✓ | ✓ | ✓ | ✓ | +| One-click join | ✓ | ✓ | ✓ | ✓ | +| Full-screen alert | ✓ | ✓ | ✓ | ✓ | +| Day view | One timeline with free time | Event list | Month + event list | Month + event list | +| Meeting notes doc (create, share, attach) | ✓ | — | — | — | +| Rule-based filters | ✓ | Toggles | — | — | + +Based on each app's own website as of September 2026. They are all good apps; +Until is for people who want the shape of their day and their meeting notes +one click away. + +## FAQ + +**Is it free?** Yes. Until is MIT-licensed, with no account and no subscription. + +**Does my calendar data go anywhere?** No. There is no Until server. Mac +calendars are read locally, Google data goes straight from Google's API to your +Mac, and sign-in tokens live in the macOS Keychain. See the +[privacy policy](https://until.combinatrix.ai/privacy.html). + +**Why sign in with Google if the Mac calendars work?** Only for the Google +extras: creating meeting notes docs and adding Meet links. Everything else +works with the Mac calendars alone. + +**My company's Google Workspace blocks third-party apps.** Use Calendars on +this Mac — if your work calendar is in the Calendar app, Until can show it +without any Google sign-in. + +**Outlook?** Add your Exchange or Microsoft 365 account to the Calendar app +(System Settings → Internet Accounts), then turn on Calendars on this Mac. + +## Build from source -See [AGENTS.md](AGENTS.md) for the full development notes, including the -distributable (Developer ID + notarization) build via `scripts/release.sh`. +See [CONTRIBUTING.md](CONTRIBUTING.md). ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2c4cfaf --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# Security + +Please report security issues privately to **until@combinatrix.ai** rather than +opening a public issue. We will reply as soon as we can. + +Until has no server: calendar data is read on your Mac or fetched directly from +Google, and sign-in tokens are stored in the macOS Keychain. Reports about +token handling, the OAuth flow, or data leaving the Mac are especially welcome. From eac8732993e7062c04b7c3ae6e03a365439bf7aa Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 11:52:37 +0900 Subject: [PATCH 06/11] Draw start alert buttons with their own style The floating alert never becomes the key window, where bordered system buttons lose their accent color; give Join a fixed tinted fill. --- Sources/Until/StartAlert.swift | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Sources/Until/StartAlert.swift b/Sources/Until/StartAlert.swift index 4de6b92..dd77b78 100644 --- a/Sources/Until/StartAlert.swift +++ b/Sources/Until/StartAlert.swift @@ -287,25 +287,23 @@ struct StartAlertView: View { @ViewBuilder private func buttons(tint: Color) -> some View { + let large = style == .fullScreen HStack(spacing: Theme.Spacing.sm) { if !event.conferenceUrl.isEmpty { Button(action: actions.join) { Label(loc("Join"), systemImage: "video.fill") - .frame(minWidth: style == .fullScreen ? 90 : nil) } - .buttonStyle(.borderedProminent) - .tint(tint) - .controlSize(style == .fullScreen ? .large : .regular) + .buttonStyle(StartAlertButtonStyle(fill: tint, large: large)) .keyboardShortcut(.defaultAction) } else { Button(loc("Open Event"), action: actions.open) - .controlSize(style == .fullScreen ? .large : .regular) + .buttonStyle(StartAlertButtonStyle(fill: tint, large: large)) .keyboardShortcut(.defaultAction) } Button(loc("Snooze 1 min"), action: actions.snooze) - .controlSize(style == .fullScreen ? .large : .regular) + .buttonStyle(StartAlertButtonStyle(fill: nil, large: large)) Button(loc("Dismiss"), action: actions.dismiss) - .controlSize(style == .fullScreen ? .large : .regular) + .buttonStyle(StartAlertButtonStyle(fill: nil, large: large)) .keyboardShortcut(.cancelAction) } } @@ -321,3 +319,23 @@ struct StartAlertView: View { heroMetadataParts(for: event).joined(separator: " · ") } } + +/// The floating alert never becomes the key window, where system bordered +/// buttons lose their accent color, so the alert draws its own. +private struct StartAlertButtonStyle: ButtonStyle { + var fill: Color? + var large: Bool + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(large ? .body.weight(.semibold) : .callout.weight(.semibold)) + .foregroundStyle(fill == nil ? Color.primary : Color.white) + .padding(.horizontal, large ? 18 : 12) + .padding(.vertical, large ? 8 : 5) + .background( + (fill ?? Color.primary.opacity(0.08)).opacity(configuration.isPressed ? 0.75 : 1), + in: RoundedRectangle(cornerRadius: large ? 9 : 7, style: .continuous) + ) + .contentShape(Rectangle()) + } +} From cc0fdd6d231d4d666c4719ff367f1e1956def978 Mon Sep 17 00:00:00 2001 From: hmirin Date: Wed, 30 Sep 2026 14:10:12 +0900 Subject: [PATCH 07/11] Rebuild the website around install, calendars, and comparisons - Hero links straight to Until.dmg, offers the Homebrew command, and states the supported Macs; a trust strip follows. - New sections for calendar sources (Mac calendars / Google), start alerts, desktop widgets, the smaller details, a comparison with MeetingBar, Dato, and Dot, and an FAQ. - A Japanese page at /ja/ with hreflang links, JSON-LD, a sitemap, robots.txt, and a dedicated 1200x630 Open Graph image. - Widget and start-alert images are rendered from the real views. - The privacy policy covers Calendar app access and the widget snapshot. --- docs/index.html | 186 ++++++++++---- docs/ja/index.html | 452 +++++++++++++++++++++++++++++++++ docs/og-image.png | Bin 0 -> 224181 bytes docs/privacy.html | 35 ++- docs/robots.txt | 4 + docs/script.js | 18 ++ docs/site-preview-desktop.png | Bin 191626 -> 0 bytes docs/sitemap.xml | 16 ++ docs/styles.css | 341 +++++++++++++++++++++++++ docs/until-start-alert-ja.webp | Bin 0 -> 18102 bytes docs/until-start-alert.webp | Bin 0 -> 16968 bytes docs/until-widget-ja.webp | Bin 0 -> 15408 bytes docs/until-widget.webp | Bin 0 -> 17342 bytes 13 files changed, 990 insertions(+), 62 deletions(-) create mode 100644 docs/ja/index.html create mode 100644 docs/og-image.png create mode 100644 docs/robots.txt delete mode 100644 docs/site-preview-desktop.png create mode 100644 docs/sitemap.xml create mode 100644 docs/until-start-alert-ja.webp create mode 100644 docs/until-start-alert.webp create mode 100644 docs/until-widget-ja.webp create mode 100644 docs/until-widget.webp diff --git a/docs/index.html b/docs/index.html index bcaecc3..546f580 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,35 +3,32 @@ - Until — Google Calendar in your macOS menubar - + Until — your next meeting in the macOS menu bar + + + + + - - - - - + + + + + + - - - + + + + - + @@ -47,9 +44,9 @@ Until

Next up, always visible.

-

Google Calendar in your macOS menubar — countdown above, your day below.

+

Your next meeting in the macOS menu bar — a live countdown, your whole day on one timeline, and meeting notes in one click.

-

Free & open source English / 日本語

+
brew install --cask combinatrix-ai/tap/until
+

Free & open source macOS 13+ Apple Silicon & Intel English / 日本語

@@ -73,6 +71,10 @@

Next up, always visible.

+
+
  • No servers — your data stays between your Mac and your calendar
  • Signed and notarized by Apple
  • Open source (MIT)
  • Sign-in tokens in your Keychain
+
+ - -
-
-
-

Notification

-

Reminded — and one click from joining.

-

A native reminder arrives before every meeting; click it to join instantly, or hold it to open the event or snooze five minutes.

-
- -
- A macOS notification from Until for Design review, showing its start time and Google Meet link. -
+

⌥-click the icon or press the join shortcut to jump into the call · hide the title while you present · launch at login

@@ -145,6 +127,7 @@

Plan your day at a glance.

alt="Until's full-day timeline in a macOS popover, showing the current event, free gaps, and upcoming calendar events." > +

The footer sums the day up: how many events are left, how much is booked, and the longest free stretch.

@@ -153,14 +136,10 @@

Plan your day at a glance.

Meeting notes

Notes, ready before you are.

-

One click on any event:

+

One click on any Google Calendar event:

-
    -
  1. Creates a Google Doc from your template
  2. -
  3. Shares it with every attendee
  4. -
  5. Attaches it to the calendar event
  6. -
+
  1. Creates a Google Doc from your template
  2. Shares it with every attendee
  3. Attaches it to the calendar event
+

The upcoming card also surfaces the docs, designs, and pull requests linked from the invite.

+ + + +
+
+
+

Calendars

+

Works with the calendars you have.

+

Use the Calendar app, a Google account, or both — events that show up twice are merged.

+
+ +
+
+ No sign-in +

Calendars on this Mac

+

Everything the Calendar app already syncs, read right on your Mac.

+
  • iCloud, Google, Outlook / Exchange, CalDAV
  • Works when your company blocks third-party apps
  • Events never leave your Mac
+
+
+ Adds notes & Meet +

Google accounts

+

Sign in directly for the Google extras.

+
  • Meeting notes docs in one click
  • Add a Google Meet link to any event
  • Several accounts side by side
+
+
+
+
+ +
+
+
+

Alerts

+

Hard to miss, easy to join.

+

A native reminder before every meeting — and, if you want it, an alert that stays on screen as the meeting starts: floating, or full screen on every display.

+
+ +
+
+ Until's start alert: Design review starts in 8 minutes, with Join, Snooze 1 min, and Dismiss buttons. +
+
+ A macOS notification from Until for Design review, showing its start time and Google Meet link. +
+
+
+
+ +
+
+
+

Widgets

+

Today, on your desktop.

+

Medium and large widgets show today's agenda, with the next meeting highlighted and a live countdown.

+
+ +
+ Until's medium desktop widget: an all-day event, the current time, the next meeting with a countdown, and the following event. +
@@ -332,20 +370,56 @@

Only the events that matter.

+
+
+
+

Details

+

The little things, done.

+
+

30+ meeting services

Google Meet, Zoom, Teams, Webex, Slack huddles, Discord, Jitsi, FaceTime, Whereby, and more. Zoom and Teams can open in their apps.

Global shortcuts

Open the timeline, or join the current meeting, from anywhere.

Try before connecting

A sample day shows the timeline before you pick a calendar.

English and Japanese

Every screen, both languages.

+
+
+ +
+
+
+

Compare

+

How Until compares.

+
+
+ + + +
UntilMeetingBarDatoDot
PriceFree, open sourceFree, open source$18$14.99
CalendarsCalendar app + Google sign-inCalendar app + GoogleCalendar appCalendar app
Menu bar countdown✓✓✓✓
One-click join✓✓✓✓
Full-screen alert✓✓✓✓
Day viewOne timeline with free timeEvent listMonth + event listMonth + event list
Meeting notes doc (create, share, attach)✓———
Rule-based filters✓Toggles——
+
+

From each app's own website, September 2026. They are all good apps — Until is for people who want the shape of their day and their meeting notes one click away.

+
+
+ +
+
+
+

FAQ

+

Questions, answered.

+
+
Is Until free?

Yes. It is MIT-licensed open source, with no account and no subscription.

Does my calendar data go anywhere?

No. There is no Until server. Mac calendars are read on your Mac, Google data goes straight from Google to your Mac, and sign-in tokens stay in the macOS Keychain. Privacy policy

Do I need a Google account?

No. Calendars on this Mac covers iCloud, Google, Outlook/Exchange, and CalDAV without signing in. Sign in with Google only for meeting notes docs and Meet links.

Does it work with Outlook or Microsoft 365?

Yes — add the account to the Calendar app (System Settings → Internet Accounts) and turn on Calendars on this Mac.

Which Macs?

macOS 13 Ventura or later, on Apple Silicon and Intel. Updates install automatically.

+
+
+

Private by design.

-

Until runs entirely on your Mac, talks straight to Google, has no account with us and no servers in between; tokens stay in the macOS Keychain, and Drive access is limited to files it created (drive.file scope). Full privacy policy →

+

Until runs entirely on your Mac and has no servers and no account with us. Calendar app events are read locally; Google data goes straight from Google to your Mac, tokens stay in the macOS Keychain, and Drive access is limited to files Until created (drive.file scope). Full privacy policy →

-

Look up. You're ready.

+

Look up. You’re ready.

+
brew install --cask combinatrix-ai/tap/until
@@ -364,9 +439,8 @@

Look up. You're ready.

Until diff --git a/docs/ja/index.html b/docs/ja/index.html new file mode 100644 index 0000000..e07e1e8 --- /dev/null +++ b/docs/ja/index.html @@ -0,0 +1,452 @@ + + + + + + Until — 次の会議をmacOSのメニューバーに + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + Until +
+

次の予定を、いつも目の前に。

+

macOS のメニューバーに次の会議までのカウントダウン。クリックすれば一日がひとつのタイムラインに。議事録はワンクリックで。

+ +
brew install --cask combinatrix-ai/tap/until
+

無料・オープンソース macOS 13 以降 Apple シリコン / Intel 日本語 / English

+
+ +
+ メニューバーのカウントダウンから一日のタイムラインを開いたところ。次の予定が大きく表示され、その後の予定が続く。 + +
+
+ +
+
  • サーバーなし。予定は Mac とカレンダーの間だけ
  • Apple の署名・公証済み
  • オープンソース(MIT)
  • サインイン情報はキーチェーンに保存
+
+ + + +
+
+
+

Timeline

+

一日を、ひと目で。

+

メニューバーをクリックすると一日が開きます。会議の一覧ではなく、空き時間まで形になって見える一本のタイムライン。

+
+ +
+ ポップオーバーに表示された一日のタイムライン。進行中の予定、空き時間、この後の予定が並ぶ。 +
+

下部には今日の予定の件数・予定の合計時間・残りの最長の空き時間を表示します。

+
+
+ +
+
+
+

Meeting notes

+

議事録は、会議より先に。

+

Google カレンダーの予定でワンクリックすると:

+
+ +
  1. テンプレートから Google ドキュメントを作成
  2. 参加者全員と共有
  3. 予定に添付
+ +

次の予定のカードには、招待に含まれる資料・デザイン・PR へのリンクも表示します。

+
+
+ +
+
+
+

Calendars

+

いま使っているカレンダーで。

+

カレンダーApp、Google アカウント、その両方。2か所に出てくる同じ予定は1つにまとめます。

+
+ +
+
+ サインイン不要 +

このMacのカレンダー

+

カレンダーAppに入っている予定を、Mac の中で読み取ります。

+
  • iCloud・Google・Outlook / Exchange・CalDAV
  • 会社でサードパーティ製アプリが禁止されていても使える
  • 予定が Mac の外に出ない
+
+
+ 議事録と Meet +

Google アカウント

+

直接サインインすると、Google ならではの機能が使えます。

+
  • 議事録ドキュメントをワンクリックで作成
  • 予定に Google Meet リンクを追加
  • 複数アカウントを並べて表示
+
+
+
+
+ +
+
+
+

Alerts

+

見逃さず、すぐ参加。

+

会議の前にはネイティブの通知。さらに希望すれば、開始時に画面へ出し続けるアラートも。浮動カードか、すべてのディスプレイへの全画面表示を選べます。

+
+ +
+
+ Until の開始アラート。「デザインレビュー」が8分後に開始、参加・1分後に再通知・閉じるのボタン。 +
+
+ Until の macOS 通知。予定の開始時刻と Google Meet のリンクを表示。 +
+
+
+
+ +
+
+
+

Widgets

+

今日の予定を、デスクトップに。

+

中・大サイズのウィジェットで今日の予定を一覧。次の会議はカウントダウン付きで強調表示します。

+
+ +
+ Until の中サイズのウィジェット。終日の予定、現在時刻、カウントダウン付きの次の会議、その次の予定。 +
+
+
+ +
+
+
+

Focus

+

大事な予定だけを。

+

辞退した招待、重要度の低いカレンダー、終日の仮押さえを細かいフィルターで除外。残るのは、注意を向けるべき予定だけです。

+
+ +

フィルター編集画面のルール例: 自分の回答が「辞退」でない、カレンダーが選択した2つのいずれでもない、終日でない。

+ +
+
+ +
+
+
+

Details

+

細部まで、きちんと。

+
+

30以上の会議サービス

Google Meet・Zoom・Teams・Webex・Slack ハドル・Discord・Jitsi・FaceTime・Whereby など。Zoom と Teams はアプリで開けます。

グローバルショートカット

どこからでもタイムラインを開いたり、会議に参加したり。

接続前に試せる

カレンダーを選ぶ前に、サンプルの一日でタイムラインを確認できます。

日本語と英語

すべての画面が両方の言語に対応。

+
+
+ +
+
+
+

Compare

+

ほかのアプリとの違い。

+
+
+ + + +
UntilMeetingBarDatoDot
価格無料・オープンソース無料・オープンソース$18$14.99
カレンダーカレンダーApp + Google サインインカレンダーApp + GoogleカレンダーAppカレンダーApp
メニューバーのカウントダウン✓✓✓✓
ワンクリック参加✓✓✓✓
全画面アラート✓✓✓✓
一日の表示空き時間つきの一本のタイムライン予定の一覧月表示+一覧月表示+一覧
議事録ドキュメント(作成・共有・添付)✓———
ルールによるフィルター✓トグル——
+
+

2026年9月時点、各アプリの公式サイトの記載によります。どれも良いアプリです。Until は、一日の形と議事録をワンクリックの距離に置きたい人のためのアプリです。

+
+
+ +
+
+
+

FAQ

+

よくある質問。

+
+
無料ですか?

はい。MIT ライセンスのオープンソースで、アカウント登録もサブスクリプションもありません。

予定のデータはどこかに送られますか?

いいえ。Until のサーバーはありません。Mac のカレンダーは Mac の中で読み取り、Google のデータは Google から直接 Mac に届きます。サインイン情報は macOS のキーチェーンに保存します。プライバシーポリシー

Google アカウントは必要ですか?

いいえ。「このMacのカレンダー」で iCloud・Google・Outlook/Exchange・CalDAV の予定をサインインなしで表示できます。Google へのサインインは議事録の作成と Meet リンクの追加に使うときだけです。

Outlook や Microsoft 365 でも使えますか?

はい。アカウントをカレンダーApp(システム設定 → インターネットアカウント)に追加し、「このMacのカレンダー」をオンにしてください。

対応する Mac は?

macOS 13 Ventura 以降の Apple シリコン搭載 Mac と Intel Mac です。アップデートは自動でインストールされます。

+
+
+ +
+
+
+

プライバシーを第一に。

+
+

Until はすべて Mac の中で動き、サーバーもアカウント登録もありません。カレンダーAppの予定は Mac の中で読み取り、Google のデータは Google から直接 Mac に届きます。サインイン情報は macOS のキーチェーンに保存し、Drive へのアクセスは Until が作成したファイルだけ(drive.file スコープ)に限っています。プライバシーポリシー(英語)→

+
+
+ +
+
+

顔を上げて。準備はできています。

+ +
brew install --cask combinatrix-ai/tap/until
+
+
+ + +
+ + + + diff --git a/docs/og-image.png b/docs/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..29921f63b34ddc6b08c0411f6266f0b918075c3a GIT binary patch literal 224181 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%0D&r(5F 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

8}qJ!+oJ;ECicM6ddWWH6F$0l5sOl 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


1nT7axHQLug}cD(EEVVCmRd$xjE>OOY17d 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

)t|f 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 l^z*0Hd$u2N0p3PCyFeN)

Privacy Policy

- +