feat: harden Flow cold-start/force-quit and polish macOS dictation UX
Fix cold-start overlay recursion that overflowed the main-thread stack when recording began while the ready overlay was still up; also remove temporary on-screen Flow DEBUG panels after the orange-mic investigation, and land the macOS overlay/catalog/layout polish plus related Flow recovery hardening.
This commit is contained in:
@@ -16,11 +16,36 @@ public struct SyncedField<T: Codable & Equatable & Sendable>: Codable, Equatable
|
||||
self.deviceID = deviceID
|
||||
}
|
||||
|
||||
/// A remote timestamp may be at most this far in OUR future before we
|
||||
/// stop trusting it. Wall-clock LWW breaks down when one device's clock
|
||||
/// runs fast: its edits would win every merge forever, silently
|
||||
/// discarding later edits from correct-clock devices. Anything beyond
|
||||
/// this skew is a broken clock, not a newer edit.
|
||||
public static var maxTrustedFutureSkew: TimeInterval { 6 * 60 * 60 }
|
||||
|
||||
/// Pick the field with the newer `updatedAt`; ties break lexicographically on `deviceID`.
|
||||
///
|
||||
/// Broken-clock containment: comparing with clamped stamps alone is not
|
||||
/// enough — a far-future stamp stored in the winner would keep beating
|
||||
/// every later genuine edit (whose stamps are merely "now") until that
|
||||
/// wall-clock date actually arrived. So when the winner carries an
|
||||
/// untrusted future stamp, the stamp itself is REWRITTEN to "now" in the
|
||||
/// merged result: from then on any real edit, made later, outranks it.
|
||||
public static func merge(local: SyncedField<T>, remote: SyncedField<T>) -> SyncedField<T> {
|
||||
if remote.updatedAt > local.updatedAt { return remote }
|
||||
if local.updatedAt > remote.updatedAt { return local }
|
||||
return remote.deviceID >= local.deviceID ? remote : local
|
||||
let now = Date()
|
||||
let horizon = now.addingTimeInterval(maxTrustedFutureSkew)
|
||||
let remoteAt = remote.updatedAt > horizon ? now : remote.updatedAt
|
||||
let localAt = local.updatedAt > horizon ? now : local.updatedAt
|
||||
let winner: SyncedField<T>
|
||||
if remoteAt > localAt {
|
||||
winner = remote
|
||||
} else if localAt > remoteAt {
|
||||
winner = local
|
||||
} else {
|
||||
winner = remote.deviceID >= local.deviceID ? remote : local
|
||||
}
|
||||
guard winner.updatedAt > horizon else { return winner }
|
||||
return SyncedField(value: winner.value, updatedAt: now, deviceID: winner.deviceID)
|
||||
}
|
||||
|
||||
public static func make(value: T, deviceID: String) -> SyncedField<T> {
|
||||
|
||||
Reference in New Issue
Block a user