fix(mobile): recover pending questions after cold start

This commit is contained in:
Bohdan Triapitsyn
2026-08-10 15:10:36 +03:00
parent 87f2d21054
commit dfa7b45dd0
7 changed files with 257 additions and 12 deletions
@@ -70,6 +70,7 @@ import { getRuntimeKey } from "@/lib/runtime-switch"
const {
createEventRoutingIndex,
handleEvent,
resyncBlockingRequestsForActiveDirectory,
resyncBlockingRequestsForDirectory,
setActiveSession,
} = await import("../sync-context")
@@ -130,6 +131,34 @@ describe("resyncBlockingRequestsForDirectory", () => {
expect(listPendingPermissionsCalls[0]).toEqual({ directories: ["/repo"] })
})
test("resume recovery refreshes blocking requests only for the active materialized directory", async () => {
const childStores = new ChildStoreManager()
childStores.ensureChild("/resume-active", { bootstrap: false }).setState({
session: [{ id: "ses_a", title: "ses_a", time: { created: 1, updated: 1 }, version: "1" } as State["session"][number]],
})
childStores.ensureChild("/resume-inactive", { bootstrap: false }).setState({
session: [{ id: "ses_b", title: "ses_b", time: { created: 1, updated: 1 }, version: "1" } as State["session"][number]],
})
pendingQuestionsResponse = [buildQuestion()]
await resyncBlockingRequestsForActiveDirectory("/resume-active", childStores)
expect(listPendingQuestionsCalls).toEqual([{ directories: ["/resume-active"] }])
expect(listPendingPermissionsCalls).toEqual([{ directories: ["/resume-active"] }])
expect(childStores.getChild("/resume-active")?.getState().question.ses_a?.[0]?.id).toBe("que_1")
expect(childStores.getChild("/resume-inactive")?.getState().question.ses_b).toBe(undefined)
})
test("resume recovery does not materialize or fetch an unopened directory", async () => {
const childStores = new ChildStoreManager()
await resyncBlockingRequestsForActiveDirectory("/unopened", childStores)
expect(childStores.getChild("/unopened")).toBe(undefined)
expect(listPendingQuestionsCalls).toHaveLength(0)
expect(listPendingPermissionsCalls).toHaveLength(0)
})
test("merges newly fetched questions/permissions into the directory store", async () => {
const store = createDirectoryStore({})
pendingQuestionsResponse = [buildQuestion()]
@@ -187,6 +216,36 @@ describe("resyncBlockingRequestsForDirectory", () => {
expect(listPendingPermissionsCalls).toHaveLength(0)
})
test("recovers an explicit session candidate before directory bootstrap materializes it", async () => {
const store = createDirectoryStore({ session: [] })
pendingQuestionsResponse = [buildQuestion()]
await resyncBlockingRequestsForDirectory("/repo", store, ["ses_a"], { includePermissions: false })
expect(listPendingQuestionsCalls).toEqual([{ directories: ["/repo"] }])
expect(listPendingPermissionsCalls).toHaveLength(0)
expect(store.getState().question.ses_a?.[0]?.id).toBe("que_1")
})
test("limits explicit question-only recovery to the requested session", async () => {
const store = createDirectoryStore({
session: [
{ id: "ses_a", title: "ses_a", time: { created: 1, updated: 1 }, version: "1" },
{ id: "ses_b", title: "ses_b", time: { created: 1, updated: 1 }, version: "1" },
] as State["session"],
})
pendingQuestionsResponse = [
buildQuestion(),
buildQuestion({ id: "que_b", sessionID: "ses_b" }),
]
await resyncBlockingRequestsForDirectory("/repo", store, ["ses_a"], { includePermissions: false })
expect(store.getState().question.ses_a?.[0]?.id).toBe("que_1")
expect(store.getState().question.ses_b).toBe(undefined)
expect(listPendingPermissionsCalls).toHaveLength(0)
})
// Regression: prior to the fix, listPendingQuestions silently returned [] on
// fetch failure, indistinguishable from a successful empty server response.
// The resync then walked the candidate set and deleted any question that