fix: recover mobile and sync state after resume

Reconnect sync stream when native mobile app resumes
Materialize incomplete sessions with explicit recovery reasons
Add low-noise debug breadcrumb for scoped recovery
This commit is contained in:
Bohdan Triapitsyn
2026-07-01 18:32:24 +03:00
parent 0a69c4ebab
commit 4b1e05160f
6 changed files with 123 additions and 65 deletions
+17 -4
View File
@@ -151,10 +151,23 @@ export type GlobalEventResult = {
project: Project
} | null
export type SessionMaterializationReason =
| "missing-owning-message"
| "orphan-delta"
| "missing-delta-part"
| "empty-assistant-message"
| "child-session-idle"
| "child-session-discovered"
| "ensure-session-messages"
| "stream-reconnect"
| "transport-switch"
| "stale-status-resync"
export type DirectoryEventResult = boolean | {
changed: boolean
materialization: {
type: "incomplete-session-snapshot"
reason: SessionMaterializationReason
sessionID?: string
messageID: string
partID?: string
@@ -356,7 +369,7 @@ export function applyDirectoryEvent(
return missingOwningMessage
? {
changed: true,
materialization: { type: "incomplete-session-snapshot", sessionID, messageID, partID: part.id },
materialization: { type: "incomplete-session-snapshot", reason: "missing-owning-message", sessionID, messageID, partID: part.id },
}
: true
}
@@ -390,7 +403,7 @@ export function applyDirectoryEvent(
return missingOwningMessage
? {
changed: true,
materialization: { type: "incomplete-session-snapshot", sessionID, messageID, partID: part.id },
materialization: { type: "incomplete-session-snapshot", reason: "missing-owning-message", sessionID, messageID, partID: part.id },
}
: true
}
@@ -426,7 +439,7 @@ export function applyDirectoryEvent(
syncDebug.reducer.partDeltaNoParts(props.messageID, props.partID)
return {
changed: false,
materialization: { type: "incomplete-session-snapshot", sessionID: props.sessionID, messageID: props.messageID, partID: props.partID },
materialization: { type: "incomplete-session-snapshot", reason: "orphan-delta", sessionID: props.sessionID, messageID: props.messageID, partID: props.partID },
}
}
const result = Binary.search(parts, props.partID, (p) => p.id)
@@ -434,7 +447,7 @@ export function applyDirectoryEvent(
syncDebug.reducer.partDeltaNotFound(props.messageID, props.partID)
return {
changed: false,
materialization: { type: "incomplete-session-snapshot", sessionID: props.sessionID, messageID: props.messageID, partID: props.partID },
materialization: { type: "incomplete-session-snapshot", reason: "missing-delta-part", sessionID: props.sessionID, messageID: props.messageID, partID: props.partID },
}
}
const existing = parts[result.index] as Record<string, unknown>