checkpoint before checking out main

This commit is contained in:
Rocky
2026-08-25 13:42:00 +08:00
parent 57844ce615
commit dccafe6f9e
105 changed files with 4302 additions and 1323 deletions
@@ -105,6 +105,7 @@ public struct AppGroupConfiguration: Sendable, Equatable {
public var uiLanguage: AppUILanguage
public var translationTargetLocaleId: String
public var handednessPreference: HandednessPreference
/// Decode-only compatibility field for cursor drag pads removed in 2.0.0.
public var cursorDragNavigationEnabled: Bool
/// Typing-grid haptic strength (off / light / strong).
public var keyboardHapticIntensity: KeyboardHapticIntensity
@@ -300,7 +301,7 @@ public struct AppGroupConfiguration: Sendable, Equatable {
),
cursorDragNavigationEnabled: {
if defaults.object(forKey: Keys.cursorDragNavigationEnabled) == nil {
return true
return false
}
return defaults.bool(forKey: Keys.cursorDragNavigationEnabled)
}(),
@@ -594,17 +595,7 @@ public struct AppGroupConfiguration: Sendable, Equatable {
return .empty
}
do {
var dictionary = try JSONDecoder().decode(PersonalDictionary.self, from: data)
if dictionary.entries.contains(where: { $0.source == .history }) {
for index in dictionary.entries.indices where dictionary.entries[index].source == .history {
dictionary.entries[index].source = .manual
}
dictionary.version += 1
if let migrated = try? JSONEncoder().encode(dictionary) {
defaults.set(migrated, forKey: Keys.personalDictionary)
}
}
return dictionary
return try JSONDecoder().decode(PersonalDictionary.self, from: data)
} catch {
OSGLog.config.warning("personalDictionary decode failed: \(error.localizedDescription, privacy: .public)")
return .empty
@@ -39,12 +39,11 @@ public enum KeyboardChromeLayout {
public static let horizontalInset: CGFloat = 8
/// Voice-surface content column cap.
///
/// The voice surface is a sparse cluster two cursor-drag pads flanking a
/// fixed 121 pt mic over a transparent background, so filling an iPad's
/// width buys no visual width; it only parks delete/return at the screen
/// edges and turns each drag pad into a ~450 pt runway. The typing surface
/// has the opposite need (a key grid must fill the width to match the
/// system keyboard), which is why it no longer shares this constant.
/// The voice surface is a sparse control cluster over a transparent
/// background, so filling an iPad's width buys no visual width and only
/// parks delete/return at the screen edges. The typing surface has the
/// opposite need (a key grid must fill the width to match the system
/// keyboard), which is why it no longer shares this constant.
public static let voiceContentMaxWidth: CGFloat = 700
/// Width at or above which the typing surface switches to wide-iPad
@@ -8,7 +8,7 @@
//
// Sources (mutually exclusive per entry):
// - `.manual` user typed it in by hand
// - `.history` legacy auto-learned entries (migrated to `.manual`)
// - `.history` user-confirmed recommendations from typing history
// - `.contacts` imported from the iOS Contacts framework
// - `.recentEdit` extracted from edits the user made to a
// polished transcript before sending
@@ -274,12 +274,28 @@ extension PersonalDictionary {
return entries.first { $0.term.lowercased() == key }
}
/// Insert or update a manual entry. Returns the saved entry.
/// Insert or update a manual entry. Kept as a convenience for existing callers.
@discardableResult
public mutating func upsertManual(
term: String,
existingID: UUID? = nil,
regenerateAliases: Bool = false
) -> Entry? {
upsert(
term: term,
existingID: existingID,
source: .manual,
regenerateAliases: regenerateAliases
)
}
/// Insert or update an entry while preserving its user-visible origin.
@discardableResult
public mutating func upsert(
term: String,
existingID: UUID? = nil,
source: Entry.Source,
regenerateAliases: Bool = false
) -> Entry? {
let trimmed = term.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
@@ -292,7 +308,7 @@ extension PersonalDictionary {
let termChanged = entry.term.caseInsensitiveCompare(trimmed) != .orderedSame
entry.term = trimmed
entry.category = category
entry.source = .manual
entry.source = source
if termChanged || regenerateAliases {
entry.aliases = []
}
@@ -307,7 +323,7 @@ extension PersonalDictionary {
var entry = entries[idx]
entry.term = trimmed
entry.category = category
entry.source = .manual
entry.source = source
entry.updatedAt = Date()
entries[idx] = entry
return entry
@@ -318,7 +334,7 @@ extension PersonalDictionary {
term: trimmed,
aliases: [],
category: category,
source: .manual,
source: source,
createdAt: now,
updatedAt: now
)
+1 -13
View File
@@ -113,7 +113,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
didSet {
guard !isApplyingConfiguration, localeId != configuration.localeId else { return }
configuration.localeId = localeId
persistConfiguration()
persistConfiguration(postConfigChanged: true)
}
}
/// "local" on-device ASR + user's LLM polish (requires user API key).
@@ -220,16 +220,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
}
}
/// Press-and-drag pads beside the mic for four-way caret movement.
@Published public var cursorDragNavigationEnabled: Bool {
didSet {
guard !isApplyingConfiguration,
cursorDragNavigationEnabled != configuration.cursorDragNavigationEnabled else { return }
configuration.cursorDragNavigationEnabled = cursorDragNavigationEnabled
persistConfiguration(postConfigChanged: true)
}
}
/// Typing-grid haptic strength (off / light / strong). Default is light.
@Published public var keyboardHapticIntensity: KeyboardHapticIntensity {
didSet {
@@ -445,7 +435,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
uiLanguage = configuration.uiLanguage
translationTargetLocaleId = configuration.translationTargetLocaleId
handednessPreference = configuration.handednessPreference
cursorDragNavigationEnabled = configuration.cursorDragNavigationEnabled
keyboardHapticIntensity = configuration.keyboardHapticIntensity
polishIntensity = configuration.polishIntensity
aiResponseLength = configuration.aiResponseLength
@@ -551,7 +540,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
uiLanguage = fresh.uiLanguage
translationTargetLocaleId = fresh.translationTargetLocaleId
handednessPreference = fresh.handednessPreference
cursorDragNavigationEnabled = fresh.cursorDragNavigationEnabled
keyboardHapticIntensity = fresh.keyboardHapticIntensity
polishIntensity = fresh.polishIntensity
aiResponseLength = fresh.aiResponseLength