fix(ipad): ship iPad P0 layout/globe fixes, edit-last-input, drop clipboard commands

Adapt typing/voice surfaces for iPad width and height, add the system globe
key and last-input editing flow, harden host-only Rime deployment, and remove
clipboard voice commands. Bump build to 61.
This commit is contained in:
Rocky
2026-08-10 13:50:50 +08:00
parent 53abad2050
commit 2bc8c1b87d
85 changed files with 5703 additions and 3997 deletions
@@ -256,6 +256,35 @@ final class FlowSessionBridgeTests: XCTestCase {
XCTAssertNil(decoded.fieldContext)
}
func testLegacyClipboardCommandDecodesAsUnsupportedAndDropsRetiredPayload() throws {
let command = FlowCommand(
sessionId: UUID(),
utteranceId: UUID(),
commandSeq: 45,
action: .startRecording,
localeId: "en-US"
)
var object = try XCTUnwrap(
JSONSerialization.jsonObject(with: JSONEncoder().encode(command))
as? [String: Any]
)
object["utteranceMode"] = "clipboardCommand"
object["clipboardSnapshot"] = "retired material"
object["previousOutput"] = "retired output"
let legacyPayload = try JSONSerialization.data(withJSONObject: object)
let decoded = try JSONDecoder().decode(FlowCommand.self, from: legacyPayload)
XCTAssertEqual(decoded.resolvedUtteranceMode, .unsupportedLegacy)
let reencoded = try XCTUnwrap(
JSONSerialization.jsonObject(with: JSONEncoder().encode(decoded))
as? [String: Any]
)
XCTAssertEqual(reencoded["utteranceMode"] as? String, "unsupportedLegacy")
XCTAssertNil(reencoded["clipboardSnapshot"])
XCTAssertNil(reencoded["previousOutput"])
}
func testFlowResultRoundTripPreservesUtteranceIdentity() {
let defaults = makeDefaults()
let sessionId = UUID()
@@ -554,4 +583,76 @@ final class FlowSessionBridgeTests: XCTestCase {
XCTAssertNil(FlowSessionBridge.latestResult(defaults: defaults))
XCTAssertNil(FlowSessionBridge.readySnapshot(defaults: defaults))
}
func testEditCommandRoundTripIncludesDeadlinesAndSource() {
let defaults = makeDefaults()
let historyID = UUID()
let command = FlowCommand(
sessionId: UUID(),
utteranceId: UUID(),
commandSeq: 4,
action: .startRecording,
localeId: "zh-Hans",
utteranceMode: .editLastInput,
editSourceText: "原文",
sourceHistoryEntryID: historyID,
startDeadlineAt: 108,
processingDeadlineAt: 145
)
FlowSessionBridge.writeCommand(command, defaults: defaults)
XCTAssertEqual(FlowSessionBridge.latestCommand(defaults: defaults), command)
XCTAssertFalse(
FlowResult(
sessionId: command.sessionId,
utteranceId: command.utteranceId,
commandSeq: command.commandSeq,
status: .final,
text: "结果",
utteranceMode: .editLastInput
).allowsRawFallback
)
}
func testStartTransactionRoundTripAndClear() {
let defaults = makeDefaults()
let transaction = FlowStartTransaction(
sessionID: UUID(),
utteranceID: UUID(),
deadlineAt: 108,
phase: .starting
)
FlowSessionBridge.writeStartTransaction(transaction, defaults: defaults)
XCTAssertEqual(
FlowSessionBridge.startTransaction(defaults: defaults),
transaction
)
FlowSessionBridge.clearFlowState(defaults: defaults)
XCTAssertNil(FlowSessionBridge.startTransaction(defaults: defaults))
}
func testAudioPrimeActionsRoundTripOnSharedCommandWire() {
let defaults = makeDefaults()
let sessionID = UUID()
let primeID = UUID()
let prime = FlowCommand(
sessionId: sessionID,
utteranceId: primeID,
commandSeq: 10,
action: .primeAudio,
localeId: "auto"
)
let cancel = FlowCommand(
sessionId: sessionID,
utteranceId: primeID,
commandSeq: 11,
action: .cancelPrimeAudio,
localeId: "auto"
)
FlowSessionBridge.writeCommand(prime, defaults: defaults)
FlowSessionBridge.writeCommand(cancel, defaults: defaults)
XCTAssertEqual(
FlowSessionBridge.commands(after: 9, defaults: defaults),
[prime, cancel]
)
}
}