feat(keyboard): improve typing, voice flow, and polish reliability

Reduce extension memory pressure and delivery races while adding richer candidates, tactile feedback, and safer two-level creative polishing.
This commit is contained in:
Rocky
2026-08-05 21:39:31 +08:00
parent 38e5ad570d
commit 31f5937a7f
177 changed files with 8343 additions and 3904 deletions
@@ -61,6 +61,11 @@ final class FlowSessionBridgeTests: XCTestCase {
func testSessionInactiveWhenExpired() {
let defaults = makeDefaults()
// Expiry only applies on the Live Activity keep-alive path (PiP is persistent).
defaults.set(
FlowKeepAliveMode.liveActivity.rawValue,
forKey: AppGroupConfiguration.Keys.flowKeepAliveMode
)
FlowSessionBridge.markSessionActive(duration: 1, defaults: defaults)
let expired = Date().timeIntervalSince1970 - 5
defaults.set(expired, forKey: FlowSessionKeys.flowSessionExpires)
@@ -110,6 +115,10 @@ final class FlowSessionBridgeTests: XCTestCase {
func testRemainingSessionDurationNilWhenExpired() {
let defaults = makeDefaults()
defaults.set(
FlowKeepAliveMode.liveActivity.rawValue,
forKey: AppGroupConfiguration.Keys.flowKeepAliveMode
)
FlowSessionBridge.markSessionActive(duration: 1, defaults: defaults)
XCTAssertNotNil(FlowSessionBridge.remainingSessionDuration(defaults: defaults))
@@ -453,6 +462,44 @@ final class FlowSessionBridgeTests: XCTestCase {
XCTAssertTrue(FlowSessionBridge.isHostReady(defaults: defaults))
}
func testHostReadyRejectedWhenReadyAtSkewsFromHeartbeat() {
let defaults = makeDefaults()
let sessionId = UUID()
let now = Date().timeIntervalSince1970
FlowSessionBridge.markSessionActive(duration: 60, sessionId: sessionId, defaults: defaults)
let skewed = FlowReadySnapshot(
sessionId: sessionId,
ready: true,
reason: .ready,
heartbeatAt: now,
readyAt: now - FlowSessionKeys.hostReadyMaxHeartbeatSkew - 1,
audioProofAt: now,
engineMode: "local",
localeId: "zh-Hans",
sessionExpiresAt: now + 60
)
FlowSessionBridge.writeReadySnapshot(skewed, defaults: defaults)
// Keep heartbeat fresh so reachability alone would pass.
defaults.set(now, forKey: FlowSessionKeys.flowHeartbeat)
XCTAssertFalse(FlowSessionBridge.isHostReady(defaults: defaults))
}
func testFlowAckRoundTripAndClearedByClearFlowState() {
let defaults = makeDefaults()
let sessionId = UUID()
let utteranceId = UUID()
let ack = FlowAck(
sessionId: sessionId,
utteranceId: utteranceId,
commandSeq: 3
)
FlowSessionBridge.writeAck(ack, defaults: defaults)
XCTAssertEqual(FlowSessionBridge.latestAck(defaults: defaults), ack)
FlowSessionBridge.clearFlowState(defaults: defaults)
XCTAssertNil(FlowSessionBridge.latestAck(defaults: defaults))
}
func testClearFlowStateRemovesProtocolPayloads() {
let defaults = makeDefaults()
let sessionId = UUID()