feat(keyboard): harden clipboard command and add What's New sheet

Stabilize clipboard long-press prepare/resume across paste alerts and cold start, add an in-app release-notes sheet with remote bilingual HTML, localize typing input settings, and bump build to 55.
This commit is contained in:
Rocky
2026-08-08 00:20:42 +08:00
parent f197b68573
commit 9da32b81e9
45 changed files with 3670 additions and 422 deletions
@@ -67,6 +67,53 @@ public enum FlowHandoffPolicy {
)
}
/// Proactive keyboard-open PiP arm. Returns false when the host is already
/// ready, warming, busy, or a recent startflow is still in cooldown
/// so Voice-tab / appear noise cannot re-jump to the app.
///
/// `heartbeatStaleness`: seconds since last host heartbeat (`nil` = never).
/// A lingering `sessionActive` after force-quit must still allow arm once the
/// heartbeat is clearly gone without treating brief switcher flaps as death.
public static let proactiveUnreachableArmGrace: TimeInterval = 8
public static func shouldProactivePiPArm(
hostReady: Bool,
snapshotReason: FlowReadySnapshot.Reason?,
sessionActive: Bool,
hostReachable: Bool,
hostStale: Bool,
withinReadyGrace: Bool,
inCooldown: Bool,
heartbeatStaleness: TimeInterval? = nil
) -> Bool {
guard !hostReady, !inCooldown else { return false }
if let snapshotReason {
switch snapshotReason {
case .recording, .processing, .awaitingDelivery, .starting,
.waitingForAudioProof, .audioEngineNotLive:
return false
case .ready, .noSession, .permissionMissing, .appGroupUnavailable,
.hostLost, .error:
break
}
}
// Force-quit leaves sessionActive=true until the 60s zombie window.
// If heartbeat has been gone long enough, arm once (cooldown still applies).
if sessionActive,
!hostReachable,
!hostStale,
let staleness = heartbeatStaleness,
staleness >= proactiveUnreachableArmGrace {
return true
}
return shouldOpenHostColdStart(
sessionActive: sessionActive,
hostReachable: hostReachable,
hostStale: hostStale,
withinReadyGrace: withinReadyGrace
)
}
/// Mic-press routing shared by the keyboard coordinator and unit tests.
public static func micPressAction(
availability: MicVoiceAvailability,
@@ -22,6 +22,7 @@ public enum TypingInputSchema: String, CaseIterable, Identifiable, Codable, Send
}
}
/// Stable Rime schema `name:` (not UI copy). Keep Chinese so redeploy fingerprints stay stable.
public var displayName: String {
switch self {
case .fullPinyin: return "全拼"
@@ -29,6 +30,15 @@ public enum TypingInputSchema: String, CaseIterable, Identifiable, Codable, Send
case .sogouDoublePinyin: return "搜狗双拼"
}
}
/// Localizable UI label key (`AppL10n` / `SharedL10n`).
public var labelKey: String {
switch self {
case .fullPinyin: return "typing.schema.fullPinyin"
case .microsoftDoublePinyin: return "typing.schema.microsoftDoublePinyin"
case .sogouDoublePinyin: return "typing.schema.sogouDoublePinyin"
}
}
}
public enum PinyinFuzzyPair: String, CaseIterable, Identifiable, Codable, Sendable {