feat(keyboard): harden polish/clipboard guards and ship 1.6.6 (build 59)
Keep marketing version at 1.6.6 and bump build to 59. Strengthen never-answer polish safeguards, clipboard reply-intent continuity, voice undo UX, device UITests harness, eval fixtures, and What's New assets.
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
// OSGKeyboard · Keyboard Extension
|
||||
//
|
||||
// Inserts Flow transcripts from the host app and surfaces polish warnings
|
||||
// without re-running LLM polish in the extension.
|
||||
// without re-running LLM polish in the extension. Also tracks the last
|
||||
// voice insertion so the undo button can roll it back safely.
|
||||
|
||||
import OSGKeyboardShared
|
||||
|
||||
@@ -14,6 +15,14 @@ final class KeyboardTextInserter {
|
||||
private let contextBeforeInput: () -> String?
|
||||
private let scheduleAutoClearError: () -> Void
|
||||
|
||||
/// Exact string last inserted by voice (including any word-boundary
|
||||
/// separator). Cleared after a successful undo or when the caret no
|
||||
/// longer sits after that text.
|
||||
private var lastInsertedText: String?
|
||||
/// Suppresses availability refresh while we walk `deleteBackward`
|
||||
/// for undo, so intermediate contexts don't flicker the button.
|
||||
private var isUndoing = false
|
||||
|
||||
init(
|
||||
state: KeyboardState,
|
||||
insertText: @escaping (String) -> Void,
|
||||
@@ -55,7 +64,9 @@ final class KeyboardTextInserter {
|
||||
previousContext: contextBeforeInput(),
|
||||
insertion: trimmed
|
||||
)
|
||||
insertText(separator + trimmed)
|
||||
let inserted = separator + trimmed
|
||||
insertText(inserted)
|
||||
recordLastInsertion(inserted)
|
||||
state.lastTranscript = ""
|
||||
state.level = 0
|
||||
if let warning = delivery.polishWarning {
|
||||
@@ -66,4 +77,50 @@ final class KeyboardTextInserter {
|
||||
}
|
||||
OSGLog.keyboardExt.info("flow insert length=\(trimmed.count, privacy: .public)")
|
||||
}
|
||||
|
||||
/// Roll back the last voice insertion when it is still at the caret.
|
||||
func undoLastInsertion() {
|
||||
guard let text = lastInsertedText, !text.isEmpty else { return }
|
||||
guard let preceding = contextBeforeInput(), preceding.hasSuffix(text) else {
|
||||
clearLastInsertion()
|
||||
return
|
||||
}
|
||||
|
||||
isUndoing = true
|
||||
defer { isUndoing = false }
|
||||
|
||||
for _ in 0..<text.count {
|
||||
deleteBackward()
|
||||
}
|
||||
clearLastInsertion()
|
||||
OSGLog.keyboardExt.info("voice undo length=\(text.count, privacy: .public)")
|
||||
}
|
||||
|
||||
/// Re-evaluate whether the recorded insertion is still undoable.
|
||||
/// Call from `textDidChange` / `selectionDidChange`.
|
||||
func refreshUndoAvailability() {
|
||||
guard !isUndoing else { return }
|
||||
guard let text = lastInsertedText, !text.isEmpty else {
|
||||
if state.undoAvailable { state.undoAvailable = false }
|
||||
return
|
||||
}
|
||||
let available = contextBeforeInput()?.hasSuffix(text) == true
|
||||
if !available {
|
||||
// Caret moved or the user edited the insertion — drop the record.
|
||||
lastInsertedText = nil
|
||||
}
|
||||
if state.undoAvailable != available {
|
||||
state.undoAvailable = available
|
||||
}
|
||||
}
|
||||
|
||||
private func recordLastInsertion(_ text: String) {
|
||||
lastInsertedText = text
|
||||
state.undoAvailable = true
|
||||
}
|
||||
|
||||
private func clearLastInsertion() {
|
||||
lastInsertedText = nil
|
||||
state.undoAvailable = false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user