feat(keyboard): ship AI hint carousel, home library cards, and clipboard polish

Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
This commit is contained in:
Rocky
2026-08-13 01:00:51 +08:00
parent fd6e0d3e7e
commit 9f308fadd2
202 changed files with 10897 additions and 5962 deletions
+15 -2
View File
@@ -12,6 +12,9 @@
import Foundation
import Combine
/// UI-owned ObservableObject; construct and mutate it on the main thread.
/// `@unchecked Sendable` does not make `@Published` thread-safe. Credential
/// observers write only Keychain, while non-secret configuration uses App Group defaults.
public final class ProviderConfig: ObservableObject, @unchecked Sendable {
public static let shared = ProviderConfig()
@@ -32,6 +35,8 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
persistConfiguration()
}
}
/// Its observer updates only the provider-scoped Keychain item; the value
/// must never enter `configuration` or App Group UserDefaults.
@Published public var apiKey: String {
didSet {
guard oldValue != apiKey, !isSyncingProviderAPIKey else { return }
@@ -42,6 +47,9 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
useICloudSync: configuration.settingsICloudSyncEnabled
)
} catch {
isSyncingProviderAPIKey = true
apiKey = oldValue
isSyncingProviderAPIKey = false
OSGLog.config.warning("Keychain write failed: \(error.localizedDescription, privacy: .public)")
}
}
@@ -80,6 +88,9 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
useICloudSync: configuration.settingsICloudSyncEnabled
)
} catch {
isSyncingASRProviderAPIKey = true
asrApiKey = oldValue
isSyncingASRProviderAPIKey = false
OSGLog.config.warning("ASR Keychain write failed: \(error.localizedDescription, privacy: .public)")
}
}
@@ -167,7 +178,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
persistConfiguration()
}
}
/// v0.2.1: whether to translate the transcript into
/// Whether to translate the transcript into
/// `translationTargetLocaleId` before insertion. **Derived**
/// translation is on iff the user has selected a target locale
/// (i.e. the persisted id is anything other than
@@ -175,7 +186,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
public var translationEnabled: Bool {
configuration.translationEnabled
}
/// v0.2.1: BCP-47-ish target language id (e.g. `en`, `ja`, `ko`) the
/// BCP-47-ish target language id (e.g. `en`, `ja`, `ko`) the
/// translate-and-polish prompt should produce. Default `"off"`
/// translation is opt-in. Persisted in the App Group so the keyboard
/// extension can honour it (and so the chip on the keyboard reflects
@@ -349,6 +360,8 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
private let defaults: UserDefaults
private var configuration: AppGroupConfiguration
/// Suppresses `@Published` observer persistence while a complete snapshot
/// or preset is applied, preventing reentrant writes of partial state.
private var isApplyingConfiguration = false
private var isSyncingProviderAPIKey = false
private var isSyncingASRProviderAPIKey = false