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:
Rocky
2026-07-10 12:39:41 +08:00
parent dcb66a9849
commit cdf833935a
104 changed files with 5794 additions and 853 deletions
+28 -3
View File
@@ -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> {