Decouple transcripts from note text; end-to-end watch transfer acks
Schema v3: transcription attempts accumulate on AudioInfo (text, locale, confidence, forced flag) — latest attempt is displayed, payload.text is purely user writing, v2 notes migrate at read time. Editor shows the transcript in its own live-updating section with insert-into-note; search matches transcripts weighted by confidence. Watch recordings are now deleted only on the phone's durable-ingest ack (transferUserInfo), closing every phone-side loss window; failed sidecar writes unstage instead of orphaning, and unreadable inbox recordings are surfaced in logs. Claude-Session: https://claude.ai/code/session_014esDWi42URLEC6Cj17hGQ3
This commit is contained in:
@@ -71,3 +71,41 @@ struct AudioNotePayload: Equatable, Sendable {
|
||||
self.timeZoneID = metadata[Key.timeZone] as? String
|
||||
}
|
||||
}
|
||||
|
||||
/// Phone → watch acknowledgment that a recording reached a terminal state on
|
||||
/// the phone (durably ingested into the iCloud container, or permanently
|
||||
/// vetoed). Sent over `transferUserInfo` — queued and durable, so an ack
|
||||
/// survives both apps being closed. **Only** this ack lets the watch delete
|
||||
/// its local copy of a recording; WatchConnectivity-level delivery success is
|
||||
/// deliberately not trusted, because the phone can still fail after `didReceive`
|
||||
/// returns (a staging or iCloud write failure would otherwise lose the audio
|
||||
/// forever — the watch's copy is the last line of defense).
|
||||
struct IngestAck: Equatable, Sendable {
|
||||
static let currentVersion = 1
|
||||
|
||||
/// ULID string of the acknowledged recording.
|
||||
let id: String
|
||||
|
||||
private enum Key {
|
||||
static let version = "ackV"
|
||||
static let id = "ackID"
|
||||
}
|
||||
|
||||
init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
/// A plist-serializable dictionary for `WCSession.transferUserInfo(_:)`.
|
||||
func userInfo() -> [String: Any] {
|
||||
[Key.version: Self.currentVersion, Key.id: id]
|
||||
}
|
||||
|
||||
/// Decode a received userInfo dictionary; nil when it isn't an ack this
|
||||
/// build understands (same forward-gate shape as `AudioNotePayload`).
|
||||
init?(userInfo: [String: Any]) {
|
||||
guard let version = userInfo[Key.version] as? Int,
|
||||
version >= 1, version <= Self.currentVersion,
|
||||
let id = userInfo[Key.id] as? String else { return nil }
|
||||
self.id = id
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user