From 3d52f185e3362ed3675bed309d25fef32da427d6 Mon Sep 17 00:00:00 2001 From: agent-p1p Date: Sun, 12 Jul 2026 05:40:34 +0200 Subject: [PATCH 1/3] fix: refresh relative timestamps after midnight Refresh chat and message timestamp projections when the calendar day changes or the app returns to the foreground. Keep formatting outside ordinary row renders and cover the day-boundary transition with a regression test. Fixes #470 --- whitenoise-mac/ContentView.swift | 24 ++++++++++ whitenoise-mac/Models/MessengerModels.swift | 39 +++++++++++++++- whitenoise-mac/Views/MessageMediaViews.swift | 21 +++++++-- whitenoise-mac/Views/MessengerShellView.swift | 6 ++- whitenoise-mac/Views/SidebarViews.swift | 21 ++++++++- whitenoise-macTests/whitenoise_macTests.swift | 46 +++++++++++++++++++ 6 files changed, 150 insertions(+), 7 deletions(-) diff --git a/whitenoise-mac/ContentView.swift b/whitenoise-mac/ContentView.swift index 528d0b3..7e8d697 100644 --- a/whitenoise-mac/ContentView.swift +++ b/whitenoise-mac/ContentView.swift @@ -8,14 +8,27 @@ import AppKit import SwiftUI +private struct TimestampReferenceDateKey: EnvironmentKey { + static let defaultValue = Date() +} + +extension EnvironmentValues { + var timestampReferenceDate: Date { + get { self[TimestampReferenceDateKey.self] } + set { self[TimestampReferenceDateKey.self] = newValue } + } +} + struct ContentView: View { @Environment(WorkspaceState.self) private var workspace + @State private var timestampReferenceDate = Date() var body: some View { MessengerShellView() .frame(minWidth: 940, minHeight: 620) .preferredColorScheme(workspace.preferredColorScheme) .environment(\.locale, workspace.preferredLocale) + .environment(\.timestampReferenceDate, timestampReferenceDate) .environment( \.openURL, OpenURLAction { url in @@ -40,11 +53,17 @@ struct ContentView: View { ) { _ in workspace.refreshSystemLanguageIfNeeded() } + .onReceive( + NotificationCenter.default.publisher(for: .NSCalendarDayChanged) + ) { _ in + timestampReferenceDate = Date() + } .onReceive( NotificationCenter.default.publisher( for: NSApplication.didBecomeActiveNotification ) ) { _ in + refreshTimestampReferenceDateIfNeeded() workspace.refreshSystemLanguageIfNeeded() // The app just regained focus. Flush any read-marking that was deferred // while it was in the background so the selected chat clears its unread @@ -70,6 +89,11 @@ struct ContentView: View { private func applyAppearance(_ preference: AppearancePreference) { NativeAppearanceController.apply(preference) } + + private func refreshTimestampReferenceDateIfNeeded(now: Date = Date()) { + guard !Calendar.autoupdatingCurrent.isDate(timestampReferenceDate, inSameDayAs: now) else { return } + timestampReferenceDate = now + } } #Preview { diff --git a/whitenoise-mac/Models/MessengerModels.swift b/whitenoise-mac/Models/MessengerModels.swift index e5df2e0..dd21c56 100644 --- a/whitenoise-mac/Models/MessengerModels.swift +++ b/whitenoise-mac/Models/MessengerModels.swift @@ -134,6 +134,17 @@ nonisolated struct ChatItem: Identifiable, Hashable { /// True when the local account can use the outbound composer for this chat. var canUseComposer: Bool { !pendingConfirmation && !isNoLongerMember } + /// Re-derives the relative spelling against a wall-clock reference date. Views use + /// this when the calendar day changes; `timestampLabel` remains the cheap mapping-time + /// value used during ordinary renders. + nonisolated func timestampLabel( + at now: Date, + locale: Locale = AppLanguage.currentLocale + ) -> String { + guard let updatedAt else { return "" } + return DisplayText.relativeTimestamp(for: updatedAt, now: now, locale: locale) + } + init( id: String, title: String, @@ -1571,6 +1582,30 @@ nonisolated struct MessageItem: Identifiable, Hashable { /// Whether the bubble should render the parsed Markdown AST instead of plain text. var rendersMarkdown: Bool { contentMarkdown != nil } + /// Re-derives the date-sensitive portion of the label after a calendar-day change. + nonisolated func timeLabel( + at now: Date, + locale: Locale = AppLanguage.currentLocale + ) -> String { + DisplayText.messageTimestamp(for: sentAt, now: now, locale: locale) + } + + /// Rebuilds the rendered metadata while preserving the message's static edited and + /// delivery-state suffixes. + nonisolated func metadataLabel( + at now: Date, + locale: Locale = AppLanguage.currentLocale + ) -> String { + var parts = [timeLabel(at: now, locale: locale)] + if isEdited { + parts.append(L10n.string("Edited")) + } + if let statusLabel { + parts.append(statusLabel) + } + return parts.joined(separator: " ") + } + // `nonisolated` so the timeline record → view-model mapping (`MessageItem.timeline`) // can build items in the off-main window/projection closure (whitenoise-mac#285) // without inheriting the module's default main-actor isolation. @@ -2289,7 +2324,7 @@ nonisolated enum DisplayText { static func relativeTimestamp(for date: Date, now: Date = Date(), locale: Locale = AppLanguage.currentLocale) -> String { - if calendar.isDateInToday(date) { + if calendar.isDate(date, inSameDayAs: now) { return date.formatted(timeOnlyStyle.locale(locale)) } if calendar.isDate(date, equalTo: now, toGranularity: .weekOfYear) { @@ -2301,7 +2336,7 @@ nonisolated enum DisplayText { static func messageTimestamp(for date: Date, now: Date = Date(), locale: Locale = AppLanguage.currentLocale) -> String { - if calendar.isDateInToday(date) { + if calendar.isDate(date, inSameDayAs: now) { return date.formatted(timeOnlyStyle.locale(locale)) } return dateTimeTimestamp(for: date, locale: locale) diff --git a/whitenoise-mac/Views/MessageMediaViews.swift b/whitenoise-mac/Views/MessageMediaViews.swift index 60308b2..7cf9906 100644 --- a/whitenoise-mac/Views/MessageMediaViews.swift +++ b/whitenoise-mac/Views/MessageMediaViews.swift @@ -75,6 +75,8 @@ struct ConversationMessageRow: View, Equatable { /// Keep the debug toggle as a row input so `.equatable()` still updates rows when the /// diagnostics presentation changes, while hover/selection churn is scoped below the row. var showsDebugMetadata = false + var timestampReferenceDate = Date() + var timestampLocale = AppLanguage.currentLocale let onOpenImageGallery: (MessageImageGalleryPresentation) -> Void // Receives the resolved MessageItem by value (not via a shared @Observable lookup), @@ -85,16 +87,25 @@ struct ConversationMessageRow: View, Equatable { MessageBubble( message: message, showsDebugMetadata: showsDebugMetadata, + timestampReferenceDate: timestampReferenceDate, + timestampLocale: timestampLocale, onOpenImageGallery: onOpenImageGallery ) } else { - TimelineNoticeRow(message: message, showsDebugMetadata: showsDebugMetadata) + TimelineNoticeRow( + message: message, + showsDebugMetadata: showsDebugMetadata, + timestampReferenceDate: timestampReferenceDate, + timestampLocale: timestampLocale + ) } } static func == (lhs: ConversationMessageRow, rhs: ConversationMessageRow) -> Bool { lhs.message == rhs.message && lhs.showsDebugMetadata == rhs.showsDebugMetadata + && lhs.timestampReferenceDate == rhs.timestampReferenceDate + && lhs.timestampLocale == rhs.timestampLocale } } @@ -102,6 +113,8 @@ struct TimelineNoticeRow: View { @Environment(WorkspaceState.self) private var workspace let message: MessageItem let showsDebugMetadata: Bool + let timestampReferenceDate: Date + let timestampLocale: Locale var body: some View { HStack { @@ -118,7 +131,7 @@ struct TimelineNoticeRow: View { .lineLimit(3) .multilineTextAlignment(.center) - Text(message.timeLabel) + Text(message.timeLabel(at: timestampReferenceDate, locale: timestampLocale)) .font(.caption2.monospacedDigit()) .foregroundStyle(.tertiary) } @@ -155,6 +168,8 @@ struct MessageBubble: View { @State private var isSelectable = false let message: MessageItem let showsDebugMetadata: Bool + let timestampReferenceDate: Date + let timestampLocale: Locale let onOpenImageGallery: (MessageImageGalleryPresentation) -> Void var body: some View { @@ -195,7 +210,7 @@ struct MessageBubble: View { bubbleContent } - Text(message.metadataLabel) + Text(message.metadataLabel(at: timestampReferenceDate, locale: timestampLocale)) .font(.caption2.monospacedDigit()) .foregroundStyle(.tertiary) .padding(.horizontal, 5) diff --git a/whitenoise-mac/Views/MessengerShellView.swift b/whitenoise-mac/Views/MessengerShellView.swift index 93db848..ff01bc6 100644 --- a/whitenoise-mac/Views/MessengerShellView.swift +++ b/whitenoise-mac/Views/MessengerShellView.swift @@ -276,6 +276,8 @@ func timelineNewestMessageScrollAction( private struct ConversationView: View { @Environment(WorkspaceState.self) private var workspace + @Environment(\.locale) private var locale + @Environment(\.timestampReferenceDate) private var timestampReferenceDate /// The top message captured before an older-history prepend, so its on-screen position /// can be restored afterward; also gates re-triggering `loadOlder` until the prepend lands. @State private var pendingPrependAnchorId: String? @@ -338,7 +340,9 @@ private struct ConversationView: View { ForEach(messages) { message in ConversationMessageRow( message: message, - showsDebugMetadata: workspace.streamingDebugEnabled + showsDebugMetadata: workspace.streamingDebugEnabled, + timestampReferenceDate: timestampReferenceDate, + timestampLocale: locale ) { gallery in imageGallery = gallery } diff --git a/whitenoise-mac/Views/SidebarViews.swift b/whitenoise-mac/Views/SidebarViews.swift index c5f6816..af035f0 100644 --- a/whitenoise-mac/Views/SidebarViews.swift +++ b/whitenoise-mac/Views/SidebarViews.swift @@ -437,6 +437,8 @@ struct SettingsSidebarRow: View { } struct ChatRowContent: View { + @Environment(\.locale) private var locale + @Environment(\.timestampReferenceDate) private var timestampReferenceDate let chat: ChatItem let isSelected: Bool @@ -464,7 +466,12 @@ struct ChatRowContent: View { PendingInviteBadge() } Spacer(minLength: 8) - Text(chat.timestampLabel) + ChatTimestampText( + chat: chat, + referenceDate: timestampReferenceDate, + locale: locale + ) + .equatable() .font(.caption) .foregroundStyle(.secondary) } @@ -506,6 +513,18 @@ struct ChatRowContent: View { } } +/// Keeps date formatting out of ordinary chat-row render passes while still allowing +/// the label to change when the app's shared calendar-day reference advances. +private struct ChatTimestampText: View, Equatable { + let chat: ChatItem + let referenceDate: Date + let locale: Locale + + var body: some View { + Text(chat.timestampLabel(at: referenceDate, locale: locale)) + } +} + struct PendingInviteBadge: View { var body: some View { Label("Invite", systemImage: "envelope.badge") diff --git a/whitenoise-macTests/whitenoise_macTests.swift b/whitenoise-macTests/whitenoise_macTests.swift index 128c138..35ec568 100644 --- a/whitenoise-macTests/whitenoise_macTests.swift +++ b/whitenoise-macTests/whitenoise_macTests.swift @@ -2996,6 +2996,52 @@ struct whitenoise_macTests { #expect(DisplayText.messageTimestamp(for: messageDate) == expected) } + @MainActor + @Test func timestampLabelsRefreshForReferenceDay() async throws { + let calendar = Calendar.autoupdatingCurrent + let dayStart = calendar.startOfDay(for: Date()) + let sentAt = try #require(calendar.date(byAdding: .hour, value: 15, to: dayStart)) + let sameDayNow = try #require(calendar.date(byAdding: .hour, value: 16, to: dayStart)) + let nextDayNow = try #require(calendar.date(byAdding: .day, value: 1, to: sameDayNow)) + let locale = Locale(identifier: "en_US") + let expectedTime = sentAt.formatted( + Date.FormatStyle(date: .omitted, time: .shortened).locale(locale) + ) + let expectedDateTime = sentAt.formatted( + Date.FormatStyle(date: .abbreviated, time: .shortened).locale(locale) + ) + + #expect(DisplayText.messageTimestamp(for: sentAt, now: sameDayNow, locale: locale) == expectedTime) + #expect(DisplayText.messageTimestamp(for: sentAt, now: nextDayNow, locale: locale) == expectedDateTime) + + let chat = ChatItem( + id: "day-boundary-chat", + title: "Day Boundary", + subtitle: "", + preview: "", + updatedAt: sentAt, + avatarSeed: "day-boundary-chat", + pictureURL: nil, + unreadCount: 0 + ) + let message = MessageItem( + id: "day-boundary-message", + senderName: "Alice", + body: "Still here", + sentAt: sentAt, + isEdited: true, + isOutgoing: false + ) + + #expect(chat.timestampLabel(at: sameDayNow, locale: locale) == expectedTime) + #expect(chat.timestampLabel(at: nextDayNow, locale: locale) != expectedTime) + #expect(message.timeLabel(at: nextDayNow, locale: locale) == expectedDateTime) + #expect( + message.metadataLabel(at: nextDayNow, locale: locale) + == "\(expectedDateTime) \(L10n.string("Edited"))" + ) + } + @MainActor @Test func localizedStringUsesSelectedAppLanguage() async throws { let previousLanguage = UserDefaults.standard.object(forKey: AppLanguage.storageKey) From a4289d2ebda5f12b7eb52d704632a0bf29a58b5b Mon Sep 17 00:00:00 2001 From: agent-p1p Date: Sun, 12 Jul 2026 05:42:44 +0200 Subject: [PATCH 2/3] style: align timestamp view modifiers --- whitenoise-mac/Views/SidebarViews.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/whitenoise-mac/Views/SidebarViews.swift b/whitenoise-mac/Views/SidebarViews.swift index af035f0..11f5042 100644 --- a/whitenoise-mac/Views/SidebarViews.swift +++ b/whitenoise-mac/Views/SidebarViews.swift @@ -472,8 +472,8 @@ struct ChatRowContent: View { locale: locale ) .equatable() - .font(.caption) - .foregroundStyle(.secondary) + .font(.caption) + .foregroundStyle(.secondary) } Text(chat.preview) .font(.caption) From efc1e5a4a3e3e55fc515bec1b6000b6e6fee58be Mon Sep 17 00:00:00 2001 From: agent-p1p Date: Sun, 12 Jul 2026 06:06:59 +0200 Subject: [PATCH 3/3] refactor: simplify timestamp refresh state Remove stale mapping-time labels, narrow the sidebar's equatable timestamp inputs, require explicit message-row clock inputs, and assert the exact weekday transition in the regression test. --- whitenoise-mac/Models/MessengerModels.swift | 36 +------------------ whitenoise-mac/Views/MessageMediaViews.swift | 4 +-- whitenoise-mac/Views/SidebarViews.swift | 10 ++++-- whitenoise-macTests/whitenoise_macTests.swift | 23 ++++++++---- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/whitenoise-mac/Models/MessengerModels.swift b/whitenoise-mac/Models/MessengerModels.swift index dd21c56..9d35ec3 100644 --- a/whitenoise-mac/Models/MessengerModels.swift +++ b/whitenoise-mac/Models/MessengerModels.swift @@ -121,9 +121,6 @@ nonisolated struct ChatItem: Identifiable, Hashable { let isDirect: Bool let pendingConfirmation: Bool let selfMembership: ChatSelfMembership - /// Precomputed once from `updatedAt` (which is immutable for a given value) to - /// avoid re-formatting the date on every chat-row render. - let timestampLabel: String /// Whether this chat has at least one unread @-mention of the active account. var hasMention: Bool { unreadMentionCount > 0 } @@ -134,17 +131,6 @@ nonisolated struct ChatItem: Identifiable, Hashable { /// True when the local account can use the outbound composer for this chat. var canUseComposer: Bool { !pendingConfirmation && !isNoLongerMember } - /// Re-derives the relative spelling against a wall-clock reference date. Views use - /// this when the calendar day changes; `timestampLabel` remains the cheap mapping-time - /// value used during ordinary renders. - nonisolated func timestampLabel( - at now: Date, - locale: Locale = AppLanguage.currentLocale - ) -> String { - guard let updatedAt else { return "" } - return DisplayText.relativeTimestamp(for: updatedAt, now: now, locale: locale) - } - init( id: String, title: String, @@ -174,11 +160,6 @@ nonisolated struct ChatItem: Identifiable, Hashable { self.isDirect = isDirect self.pendingConfirmation = pendingConfirmation self.selfMembership = selfMembership - if let updatedAt { - self.timestampLabel = DisplayText.relativeTimestamp(for: updatedAt) - } else { - self.timestampLabel = "" - } } } @@ -1575,9 +1556,7 @@ nonisolated struct MessageItem: Identifiable, Hashable { let nonvisualMediaAttachments: [MessageMediaAttachment] let hasBubbleContent: Bool let presentation: MessagePresentation - let timeLabel: String let statusLabel: String? - let metadataLabel: String /// Whether the bubble should render the parsed Markdown AST instead of plain text. var rendersMarkdown: Bool { contentMarkdown != nil } @@ -1677,8 +1656,6 @@ nonisolated struct MessageItem: Identifiable, Hashable { self.nonvisualMediaAttachments = partitionedAttachments.nonvisual self.hasBubbleContent = replyContext != nil || !trimmedBody.isEmpty self.presentation = presentation - let timeLabel = DisplayText.messageTimestamp(for: sentAt) - self.timeLabel = timeLabel let statusLabel: String? if presentation.isChatBubble { if invalidationStatus != nil { @@ -1690,14 +1667,6 @@ nonisolated struct MessageItem: Identifiable, Hashable { statusLabel = nil } self.statusLabel = statusLabel - var metadataParts = [timeLabel] - if isEdited { - metadataParts.append(L10n.string("Edited")) - } - if let statusLabel { - metadataParts.append(statusLabel) - } - self.metadataLabel = metadataParts.joined(separator: " ") } nonisolated private static func partitionMediaAttachments(_ attachments: [MessageMediaAttachment]) -> ( @@ -1790,8 +1759,7 @@ extension MessageItem { // while avoiding an O(AST) traversal on every comparison. That traversal otherwise // ran for each row whenever SwiftUI diffed the transcript (and on any Set/Dictionary // use), adding up across a live-update burst. The remaining fields are the - // independent inputs; rendered labels are compared too because they are cheap and - // directly displayed by the row. The rest of the stored properties (`trimmedBody`, the + // independent inputs. The rest of the stored properties (`trimmedBody`, the // media partitions, `hasBubbleContent`) are pure functions of these, so comparing them // too would be redundant. nonisolated static func == (lhs: MessageItem, rhs: MessageItem) -> Bool { @@ -1814,9 +1782,7 @@ extension MessageItem { && lhs.replyContext == rhs.replyContext && lhs.mediaAttachments == rhs.mediaAttachments && lhs.presentation == rhs.presentation - && lhs.timeLabel == rhs.timeLabel && lhs.statusLabel == rhs.statusLabel - && lhs.metadataLabel == rhs.metadataLabel } // Hashes a cheap, well-distributed subset of the equality fields. Hashing only a diff --git a/whitenoise-mac/Views/MessageMediaViews.swift b/whitenoise-mac/Views/MessageMediaViews.swift index 7cf9906..f935307 100644 --- a/whitenoise-mac/Views/MessageMediaViews.swift +++ b/whitenoise-mac/Views/MessageMediaViews.swift @@ -75,8 +75,8 @@ struct ConversationMessageRow: View, Equatable { /// Keep the debug toggle as a row input so `.equatable()` still updates rows when the /// diagnostics presentation changes, while hover/selection churn is scoped below the row. var showsDebugMetadata = false - var timestampReferenceDate = Date() - var timestampLocale = AppLanguage.currentLocale + let timestampReferenceDate: Date + let timestampLocale: Locale let onOpenImageGallery: (MessageImageGalleryPresentation) -> Void // Receives the resolved MessageItem by value (not via a shared @Observable lookup), diff --git a/whitenoise-mac/Views/SidebarViews.swift b/whitenoise-mac/Views/SidebarViews.swift index 11f5042..0f5c449 100644 --- a/whitenoise-mac/Views/SidebarViews.swift +++ b/whitenoise-mac/Views/SidebarViews.swift @@ -467,7 +467,7 @@ struct ChatRowContent: View { } Spacer(minLength: 8) ChatTimestampText( - chat: chat, + updatedAt: chat.updatedAt, referenceDate: timestampReferenceDate, locale: locale ) @@ -516,12 +516,16 @@ struct ChatRowContent: View { /// Keeps date formatting out of ordinary chat-row render passes while still allowing /// the label to change when the app's shared calendar-day reference advances. private struct ChatTimestampText: View, Equatable { - let chat: ChatItem + let updatedAt: Date? let referenceDate: Date let locale: Locale var body: some View { - Text(chat.timestampLabel(at: referenceDate, locale: locale)) + Text( + updatedAt.map { + DisplayText.relativeTimestamp(for: $0, now: referenceDate, locale: locale) + } ?? "" + ) } } diff --git a/whitenoise-macTests/whitenoise_macTests.swift b/whitenoise-macTests/whitenoise_macTests.swift index 35ec568..36886be 100644 --- a/whitenoise-macTests/whitenoise_macTests.swift +++ b/whitenoise-macTests/whitenoise_macTests.swift @@ -191,13 +191,17 @@ private final class AtomicMax: @unchecked Sendable { private struct TranscriptPerformanceRows: View { let messages: [MessageItem] + private let timestampReferenceDate = Date() + private let timestampLocale = AppLanguage.currentLocale var body: some View { VStack(spacing: 12) { ForEach(messages) { message in ConversationMessageRow( message: message, - showsDebugMetadata: false + showsDebugMetadata: false, + timestampReferenceDate: timestampReferenceDate, + timestampLocale: timestampLocale ) { _ in } .equatable() } @@ -2999,7 +3003,8 @@ struct whitenoise_macTests { @MainActor @Test func timestampLabelsRefreshForReferenceDay() async throws { let calendar = Calendar.autoupdatingCurrent - let dayStart = calendar.startOfDay(for: Date()) + let weekStart = try #require(calendar.dateInterval(of: .weekOfYear, for: Date())?.start) + let dayStart = try #require(calendar.date(byAdding: .day, value: 1, to: weekStart)) let sentAt = try #require(calendar.date(byAdding: .hour, value: 15, to: dayStart)) let sameDayNow = try #require(calendar.date(byAdding: .hour, value: 16, to: dayStart)) let nextDayNow = try #require(calendar.date(byAdding: .day, value: 1, to: sameDayNow)) @@ -3010,6 +3015,9 @@ struct whitenoise_macTests { let expectedDateTime = sentAt.formatted( Date.FormatStyle(date: .abbreviated, time: .shortened).locale(locale) ) + let expectedWeekday = sentAt.formatted( + Date.FormatStyle.dateTime.weekday(.abbreviated).locale(locale) + ) #expect(DisplayText.messageTimestamp(for: sentAt, now: sameDayNow, locale: locale) == expectedTime) #expect(DisplayText.messageTimestamp(for: sentAt, now: nextDayNow, locale: locale) == expectedDateTime) @@ -3033,8 +3041,9 @@ struct whitenoise_macTests { isOutgoing: false ) - #expect(chat.timestampLabel(at: sameDayNow, locale: locale) == expectedTime) - #expect(chat.timestampLabel(at: nextDayNow, locale: locale) != expectedTime) + let chatTimestamp = try #require(chat.updatedAt) + #expect(DisplayText.relativeTimestamp(for: chatTimestamp, now: sameDayNow, locale: locale) == expectedTime) + #expect(DisplayText.relativeTimestamp(for: chatTimestamp, now: nextDayNow, locale: locale) == expectedWeekday) #expect(message.timeLabel(at: nextDayNow, locale: locale) == expectedDateTime) #expect( message.metadataLabel(at: nextDayNow, locale: locale) @@ -3297,7 +3306,7 @@ struct whitenoise_macTests { isOutgoing: false ) - #expect(!outgoing.timeLabel.isEmpty) + #expect(!outgoing.timeLabel(at: outgoing.sentAt).isEmpty) #expect(outgoing.statusLabel == "Sent") #expect(incoming.statusLabel == nil) } @@ -3452,7 +3461,7 @@ struct whitenoise_macTests { #expect(message.timelineKind == 9) #expect(message.body == "Edited twice") #expect(message.isEdited) - #expect(message.metadataLabel.contains("Edited")) + #expect(message.metadataLabel(at: message.sentAt).contains("Edited")) #expect(message.contentMarkdown == nil) } @@ -3488,7 +3497,7 @@ struct whitenoise_macTests { #expect(message.id == "target") #expect(message.body == "Original") #expect(!message.isEdited) - #expect(!message.metadataLabel.contains("Edited")) + #expect(!message.metadataLabel(at: message.sentAt).contains("Edited")) } @MainActor