Release 1.10.0
Mirror "Maybe" (tentative RSVP) events instead of dropping them. Previously, with "Mirror accepted events only" on, events you'd replied tentative to were skipped entirely. They're now mirrored with a "Maybe: " marker in the title (after the normal prefix) and written with tentative availability so Calendar shows them hatched. The marker is applied for any tentative RSVP regardless of the accepted-only setting; an accepted<-> tentative flip re-syncs the title. Not carried on the merge path (mergeGapMin > 0), which already discards per-event titles. Block gains a `tentative` flag (in ==/hash); accepted-only gate lets .tentative through alongside .accepted. All unit tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ struct Block: Hashable {
|
||||
let notes: String? // source notes (for optional copy)
|
||||
let occurrence: Date? // occurrenceDate for recurring instances
|
||||
let alarmOffsets: [TimeInterval]? // relative alarm offsets copied from source event
|
||||
var tentative: Bool = false // source "Maybe" RSVP; mirrored with a marker
|
||||
// ponytail: lost on the merge path (mergeGapMin > 0) along with the title; only tracked in per-event mode
|
||||
|
||||
/// Convenience factory for time-only blocks (used internally for occupancy tracking).
|
||||
static func span(start: Date, end: Date) -> Block {
|
||||
@@ -22,7 +24,8 @@ struct Block: Hashable {
|
||||
lhs.srcStableID == rhs.srcStableID &&
|
||||
lhs.label == rhs.label &&
|
||||
lhs.notes == rhs.notes &&
|
||||
lhs.occurrence == rhs.occurrence
|
||||
lhs.occurrence == rhs.occurrence &&
|
||||
lhs.tentative == rhs.tentative
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
@@ -32,6 +35,7 @@ struct Block: Hashable {
|
||||
hasher.combine(label)
|
||||
hasher.combine(notes)
|
||||
hasher.combine(occurrence)
|
||||
hasher.combine(tentative)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,10 +145,12 @@ final class MirrorEngine {
|
||||
var skippedStatus = 0
|
||||
for ev in srcEvents {
|
||||
if Task.isCancelled { break }
|
||||
let myStatus = ev.attendees?.first(where: { $0.isCurrentUser })?.participantStatus
|
||||
let isTentative = (myStatus == .tentative)
|
||||
if config.mirrorAcceptedOnly, ev.hasAttendees {
|
||||
let attendees = ev.attendees ?? []
|
||||
if let me = attendees.first(where: { $0.isCurrentUser }) {
|
||||
if me.participantStatus != .accepted {
|
||||
if let me = ev.attendees?.first(where: { $0.isCurrentUser }) {
|
||||
// "Maybe" replies still get mirrored, just marked (see displayTitle below)
|
||||
if me.participantStatus != .accepted && me.participantStatus != .tentative {
|
||||
skippedStatus += 1
|
||||
continue
|
||||
}
|
||||
@@ -178,7 +180,7 @@ final class MirrorEngine {
|
||||
guard let s = ev.startDate, let e = ev.endDate, e > s else { continue }
|
||||
guard ev.calendar.calendarIdentifier == srcCal.calendarIdentifier else { continue }
|
||||
let srcID = stableSourceIdentifier(for: ev)
|
||||
srcBlocks.append(Block(start: s, end: e, srcStableID: srcID, label: ev.title, notes: ev.notes, occurrence: ev.occurrenceDate, alarmOffsets: alarmOffsets(for: ev)))
|
||||
srcBlocks.append(Block(start: s, end: e, srcStableID: srcID, label: ev.title, notes: ev.notes, occurrence: ev.occurrenceDate, alarmOffsets: alarmOffsets(for: ev), tentative: isTentative))
|
||||
}
|
||||
if skippedMirrors > 0 {
|
||||
log("- SKIP mirrored-on-source: \(skippedMirrors) instance(s)")
|
||||
@@ -379,7 +381,8 @@ final class MirrorEngine {
|
||||
let baseSourceTitle = stripPrefix(blk.label, prefix: config.titlePrefix)
|
||||
let effectiveTitle = config.hideDetails ? config.placeholderTitle : (baseSourceTitle.isEmpty ? config.placeholderTitle : baseSourceTitle)
|
||||
let titleSuffix = config.hideDetails ? "" : (baseSourceTitle.isEmpty ? "" : " — \(baseSourceTitle)")
|
||||
let displayTitle = (config.titlePrefix.isEmpty ? "" : config.titlePrefix) + effectiveTitle
|
||||
let maybeMark = blk.tentative ? "Maybe: " : ""
|
||||
let displayTitle = (config.titlePrefix.isEmpty ? "" : config.titlePrefix) + maybeMark + effectiveTitle
|
||||
let notes = desiredNotes(for: blk)
|
||||
let desiredURL = buildMirrorURL(
|
||||
targetCalID: tgt.calendarIdentifier,
|
||||
@@ -409,6 +412,7 @@ final class MirrorEngine {
|
||||
return
|
||||
}
|
||||
existing.title = displayTitle
|
||||
existing.availability = blk.tentative ? .tentative : .busy
|
||||
existing.startDate = blk.start
|
||||
existing.endDate = blk.end
|
||||
existing.isAllDay = false
|
||||
@@ -467,7 +471,7 @@ final class MirrorEngine {
|
||||
newEv.notes = notes
|
||||
newEv.url = desiredURL
|
||||
newEv.alarms = desiredAlarms(for: blk)
|
||||
newEv.availability = .busy
|
||||
newEv.availability = blk.tentative ? .tentative : .busy
|
||||
do {
|
||||
try store.save(newEv, span: .thisEvent, commit: true)
|
||||
created += 1
|
||||
|
||||
Reference in New Issue
Block a user