Infinite loading guard: heartbeats + stall watchdogs on the model runs
A user got stuck on the wizard's loading screen: a hung model meant silence, and silence was indistinguishable from work - the server's own hard deadline is 2x OLLAMA_TIMEOUT_MS (ten minutes by default), and the client waited on a dead stream indefinitely. Both generate routes (deck, homework) now send an NDJSON heartbeat every 15s, and the client guards the two quiet phases: no response at all for 45s, and no bytes for 90s mid-stream, both surfacing the regular error state with a Retry. The caller's abort still maps to the friendly "cancelled" state. Verified with a stalled route and Playwright's fake clock in the wizard suite (93 checks), plus contrast 0 findings, mobile 124/124, chat 101/101, regression 44/44, i18n 13/13, native 33/33. Version 0.4.0, SW -> freezmo-v69.
This commit is contained in:
parent
2b78dcf0d1
commit
20bb08b0e7
8 changed files with 125 additions and 19 deletions
|
|
@ -199,6 +199,32 @@ for (const viewport of [{ width: 390, height: 844 }, { width: 360, height: 740 }
|
|||
await context.close();
|
||||
}
|
||||
|
||||
/* 4b. a stalled run gives up on its own (no infinite loading) -------- */
|
||||
{
|
||||
const { context, page } = await makePage(browser);
|
||||
await seed(page, { decks: [] });
|
||||
await page.clock.install();
|
||||
await page.route("**/api/generate", () => new Promise(() => {}));
|
||||
|
||||
await page.goto(`${BASE}/create`, { waitUntil: "networkidle" });
|
||||
await page.waitForTimeout(300);
|
||||
await page.getByRole("button", { name: "Paste text instead" }).click();
|
||||
await page.locator("textarea").first().fill("Photosynthesis needs light and water.");
|
||||
await page.getByRole("button", { name: "Next" }).click();
|
||||
await page.getByRole("button", { name: "Next" }).click();
|
||||
await page.getByRole("button", { name: "Freeze my deck" }).click();
|
||||
await page.waitForTimeout(400);
|
||||
check("stall: the loading screen shows", await page.getByRole("button", { name: "Cancel" }).isVisible());
|
||||
await page.clock.fastForward(60_000);
|
||||
await page.waitForTimeout(600);
|
||||
check(
|
||||
"stall: the run gives up with a retry instead of loading forever",
|
||||
await page.getByRole("button", { name: "Retry" }).isVisible(),
|
||||
);
|
||||
check("stall: the wizard is usable again", await page.getByRole("heading", { name: "Ready to freeze" }).isVisible());
|
||||
await context.close();
|
||||
}
|
||||
|
||||
/* 5. leaving mid-run aborts: no surprise deck, no navigation --------- */
|
||||
{
|
||||
const { context, page } = await makePage(browser);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue