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:
@@ -14,6 +14,10 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
public var providerId: SyncedField<String>
|
||||
public var baseURL: SyncedField<String>
|
||||
public var model: SyncedField<String>
|
||||
/// Cloud ASR provider — independent from polish `providerId`.
|
||||
public var asrProviderId: SyncedField<String>
|
||||
public var asrBaseURL: SyncedField<String>
|
||||
public var asrModel: SyncedField<String>
|
||||
public var modeId: SyncedField<String>
|
||||
public var localeId: SyncedField<String>
|
||||
public var engineMode: SyncedField<String>
|
||||
@@ -31,6 +35,9 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
providerId: SyncedField<String>,
|
||||
baseURL: SyncedField<String>,
|
||||
model: SyncedField<String>,
|
||||
asrProviderId: SyncedField<String>,
|
||||
asrBaseURL: SyncedField<String>,
|
||||
asrModel: SyncedField<String>,
|
||||
modeId: SyncedField<String>,
|
||||
localeId: SyncedField<String>,
|
||||
engineMode: SyncedField<String>,
|
||||
@@ -47,6 +54,9 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
self.providerId = providerId
|
||||
self.baseURL = baseURL
|
||||
self.model = model
|
||||
self.asrProviderId = asrProviderId
|
||||
self.asrBaseURL = asrBaseURL
|
||||
self.asrModel = asrModel
|
||||
self.modeId = modeId
|
||||
self.localeId = localeId
|
||||
self.engineMode = engineMode
|
||||
@@ -60,12 +70,88 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
self.flowInactivityDuration = flowInactivityDuration
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case schemaVersion
|
||||
case providerId
|
||||
case baseURL
|
||||
case model
|
||||
case asrProviderId
|
||||
case asrBaseURL
|
||||
case asrModel
|
||||
case modeId
|
||||
case localeId
|
||||
case engineMode
|
||||
case hasAcknowledgedCloudSharing
|
||||
case uiLanguage
|
||||
case translationTargetLocaleId
|
||||
case handednessPreference
|
||||
case cursorDragNavigationEnabled
|
||||
case polishIntensity
|
||||
case flowSkipAppSwitch
|
||||
case flowInactivityDuration
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
schemaVersion = try container.decode(Int.self, forKey: .schemaVersion)
|
||||
providerId = try container.decode(SyncedField<String>.self, forKey: .providerId)
|
||||
baseURL = try container.decode(SyncedField<String>.self, forKey: .baseURL)
|
||||
model = try container.decode(SyncedField<String>.self, forKey: .model)
|
||||
modeId = try container.decode(SyncedField<String>.self, forKey: .modeId)
|
||||
localeId = try container.decode(SyncedField<String>.self, forKey: .localeId)
|
||||
engineMode = try container.decode(SyncedField<String>.self, forKey: .engineMode)
|
||||
hasAcknowledgedCloudSharing = try container.decode(SyncedField<Bool>.self, forKey: .hasAcknowledgedCloudSharing)
|
||||
uiLanguage = try container.decode(SyncedField<AppUILanguage>.self, forKey: .uiLanguage)
|
||||
translationTargetLocaleId = try container.decode(
|
||||
SyncedField<String>.self,
|
||||
forKey: .translationTargetLocaleId
|
||||
)
|
||||
handednessPreference = try container.decode(
|
||||
SyncedField<HandednessPreference>.self,
|
||||
forKey: .handednessPreference
|
||||
)
|
||||
cursorDragNavigationEnabled = try container.decode(
|
||||
SyncedField<Bool>.self,
|
||||
forKey: .cursorDragNavigationEnabled
|
||||
)
|
||||
polishIntensity = try container.decode(SyncedField<PolishIntensity>.self, forKey: .polishIntensity)
|
||||
flowSkipAppSwitch = try container.decode(SyncedField<Bool>.self, forKey: .flowSkipAppSwitch)
|
||||
flowInactivityDuration = try container.decode(
|
||||
SyncedField<FlowInactivityDuration>.self,
|
||||
forKey: .flowInactivityDuration
|
||||
)
|
||||
|
||||
if let asrProvider = try container.decodeIfPresent(SyncedField<String>.self, forKey: .asrProviderId) {
|
||||
asrProviderId = asrProvider
|
||||
} else {
|
||||
asrProviderId = providerId
|
||||
}
|
||||
if let asrURL = try container.decodeIfPresent(SyncedField<String>.self, forKey: .asrBaseURL) {
|
||||
asrBaseURL = asrURL
|
||||
} else {
|
||||
asrBaseURL = baseURL
|
||||
}
|
||||
if let asrModelField = try container.decodeIfPresent(SyncedField<String>.self, forKey: .asrModel) {
|
||||
asrModel = asrModelField
|
||||
} else {
|
||||
let fallbackModel = CloudASRModelCatalog.defaultModel(for: providerId.value)
|
||||
asrModel = SyncedField(
|
||||
value: fallbackModel,
|
||||
updatedAt: model.updatedAt,
|
||||
deviceID: model.deviceID
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Monotonic stamp used for `settingsCloudUpdatedAt` bookkeeping.
|
||||
public var latestUpdatedAt: Date {
|
||||
[
|
||||
providerId.updatedAt,
|
||||
baseURL.updatedAt,
|
||||
model.updatedAt,
|
||||
asrProviderId.updatedAt,
|
||||
asrBaseURL.updatedAt,
|
||||
asrModel.updatedAt,
|
||||
modeId.updatedAt,
|
||||
localeId.updatedAt,
|
||||
engineMode.updatedAt,
|
||||
@@ -99,6 +185,9 @@ public extension SyncedAppSettingsV2 {
|
||||
providerId: field(configuration.providerId),
|
||||
baseURL: field(configuration.baseURL),
|
||||
model: field(configuration.model),
|
||||
asrProviderId: field(configuration.asrProviderId),
|
||||
asrBaseURL: field(configuration.asrBaseURL),
|
||||
asrModel: field(configuration.asrModel),
|
||||
modeId: field(configuration.modeId),
|
||||
localeId: field(configuration.localeId),
|
||||
engineMode: field(configuration.engineMode),
|
||||
@@ -119,10 +208,14 @@ public extension SyncedAppSettingsV2 {
|
||||
func field<T>(_ value: T) -> SyncedField<T> {
|
||||
SyncedField(value: value, updatedAt: stamp, deviceID: deviceID)
|
||||
}
|
||||
let provider = field(legacy.providerId)
|
||||
return SyncedAppSettingsV2(
|
||||
providerId: field(legacy.providerId),
|
||||
providerId: provider,
|
||||
baseURL: field(legacy.baseURL),
|
||||
model: field(legacy.model),
|
||||
asrProviderId: provider,
|
||||
asrBaseURL: field(legacy.baseURL),
|
||||
asrModel: field(CloudASRModelCatalog.defaultModel(for: legacy.providerId)),
|
||||
modeId: field(legacy.modeId),
|
||||
localeId: field(legacy.localeId),
|
||||
engineMode: field(legacy.engineMode),
|
||||
@@ -142,6 +235,9 @@ public extension SyncedAppSettingsV2 {
|
||||
providerId: .merge(local: local.providerId, remote: remote.providerId),
|
||||
baseURL: .merge(local: local.baseURL, remote: remote.baseURL),
|
||||
model: .merge(local: local.model, remote: remote.model),
|
||||
asrProviderId: .merge(local: local.asrProviderId, remote: remote.asrProviderId),
|
||||
asrBaseURL: .merge(local: local.asrBaseURL, remote: remote.asrBaseURL),
|
||||
asrModel: .merge(local: local.asrModel, remote: remote.asrModel),
|
||||
modeId: .merge(local: local.modeId, remote: remote.modeId),
|
||||
localeId: .merge(local: local.localeId, remote: remote.localeId),
|
||||
engineMode: .merge(local: local.engineMode, remote: remote.engineMode),
|
||||
@@ -172,6 +268,9 @@ public extension SyncedAppSettingsV2 {
|
||||
configuration.providerId = providerId.value
|
||||
configuration.baseURL = baseURL.value
|
||||
configuration.model = model.value
|
||||
configuration.asrProviderId = asrProviderId.value
|
||||
configuration.asrBaseURL = asrBaseURL.value
|
||||
configuration.asrModel = asrModel.value
|
||||
configuration.modeId = modeId.value
|
||||
configuration.localeId = localeId.value
|
||||
configuration.engineMode = engineMode.value
|
||||
@@ -195,6 +294,9 @@ public extension SyncedAppSettingsV2 {
|
||||
patch(©.providerId, value: configuration.providerId)
|
||||
patch(©.baseURL, value: configuration.baseURL)
|
||||
patch(©.model, value: configuration.model)
|
||||
patch(©.asrProviderId, value: configuration.asrProviderId)
|
||||
patch(©.asrBaseURL, value: configuration.asrBaseURL)
|
||||
patch(©.asrModel, value: configuration.asrModel)
|
||||
patch(©.modeId, value: configuration.modeId)
|
||||
patch(©.localeId, value: configuration.localeId)
|
||||
patch(©.engineMode, value: configuration.engineMode)
|
||||
@@ -221,6 +323,9 @@ public extension SyncedAppSettingsV2 {
|
||||
touch(©.providerId, value: configuration.providerId)
|
||||
touch(©.baseURL, value: configuration.baseURL)
|
||||
touch(©.model, value: configuration.model)
|
||||
touch(©.asrProviderId, value: configuration.asrProviderId)
|
||||
touch(©.asrBaseURL, value: configuration.asrBaseURL)
|
||||
touch(©.asrModel, value: configuration.asrModel)
|
||||
touch(©.modeId, value: configuration.modeId)
|
||||
touch(©.localeId, value: configuration.localeId)
|
||||
touch(©.engineMode, value: configuration.engineMode)
|
||||
|
||||
Reference in New Issue
Block a user