fix: keyboard stability, host-only analytics, and auto-reply dedupe
- record analytics/usage only in the host app; the extension no longer opens the SQLite queues, so hiding the keyboard cannot hold WAL locks (0xdead10cc) - sync clipboard history writes across processes so deletes made on one side are not reverted by the other - guard memory-relief reentrancy on controller and telemetry sides to break the recursive crash loop - resolve credentials off the main thread and migrate legacy keys once per install, so Keychain reads cannot be caught in the suspend window - gate Volcengine ASR ping completion to once (SIGTRAP on double resume) - treat same-text captures as the same logical copy and dedupe auto actions by content fingerprint within 30s, stopping Universal Clipboard storms - realign version line to 2.1.0 (build 110)
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
//
|
||||
// Host-only analytics lifecycle. The shared module owns the wire contract,
|
||||
// SQLite queue and uploader; this type only connects iOS lifecycle signals.
|
||||
// The keyboard extension must never open these databases — an open WAL
|
||||
// connection survives hide and is killed as RunningBoard `0xdead10cc`.
|
||||
|
||||
import BackgroundTasks
|
||||
import Foundation
|
||||
|
||||
@@ -65,6 +65,9 @@ struct AssistantKeyboardUITestHarness: View {
|
||||
}
|
||||
.onAppear {
|
||||
configure(width: proxy.size.width)
|
||||
#if DEBUG
|
||||
startVoiceMemorySampling()
|
||||
#endif
|
||||
}
|
||||
.onChange(of: proxy.size.width) { _, width in
|
||||
configureLayout(width: width)
|
||||
@@ -83,6 +86,22 @@ struct AssistantKeyboardUITestHarness: View {
|
||||
colorScheme == .dark ? Palette.dark.background : Palette.light.background
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// DEBUG-only: samples the host process footprint while the voice surface
|
||||
/// renders, so the Liquid Glass cost can be read off the simulator's App
|
||||
/// Group container directly (no device log capture needed).
|
||||
private func startVoiceMemorySampling() {
|
||||
MemoryDeviceProbe.log("harness.voice.beforeRender")
|
||||
Task { @MainActor in
|
||||
for i in 0..<25 {
|
||||
try? await Task.sleep(for: .milliseconds(200))
|
||||
MemoryDeviceProbe.log("harness.voice.t\(i)")
|
||||
}
|
||||
MemoryDeviceProbe.log("harness.voice.done")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private func configure(width: CGFloat) {
|
||||
configureLayout(width: width)
|
||||
guard !configured else { return }
|
||||
|
||||
@@ -391,6 +391,13 @@ struct MainAppRoot: View {
|
||||
/// Starts foreground-only services once per active transition. Rime yields
|
||||
/// the first frame; Flow and CLM keep their existing lazy-heavy-work rules.
|
||||
private func activateForegroundServices(reason: String) {
|
||||
// Clipboard history is a whole-array blob shared with the keyboard, and
|
||||
// a suspended app receives no Darwin notifications — so everything the
|
||||
// keyboard captured or deleted while we were backgrounded is visible
|
||||
// only after this reload. Skipping it leaves every host view holding a
|
||||
// stale array that silently reverts those writes on its next delete.
|
||||
ClipboardHistoryStore.shared.startObservingCrossProcessChanges()
|
||||
ClipboardHistoryStore.shared.reload()
|
||||
scheduleRimeDeployment(reason: reason)
|
||||
// Automatically arm the low-profile PiP on every host open.
|
||||
// Capture/ASR remain lazy and start only on an actual mic press.
|
||||
|
||||
Reference in New Issue
Block a user