From 218986ea89de61a2f3985916391901950e44056c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 12 Nov 2026 15:09:22 +0100 Subject: [PATCH] fix(sheets): let Back answer the discard question with "keep editing" Back on a sheet with unsaved input asks whether to discard it. A second Back while that question was open put the sheet's entry back and asked again, so the Android button could never get past the question. It now closes the question and keeps the sheet and its entry, like Cancel. --- .../__tests__/sheet-history.browser.test.tsx | 39 +++++++++++++++++++ src/components/ui/responsive-sheet.tsx | 5 ++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/components/ui/__tests__/sheet-history.browser.test.tsx b/src/components/ui/__tests__/sheet-history.browser.test.tsx index 78ec10989..bd57bbdea 100644 --- a/src/components/ui/__tests__/sheet-history.browser.test.tsx +++ b/src/components/ui/__tests__/sheet-history.browser.test.tsx @@ -8,10 +8,13 @@ * entry's own state copied in (`HistoryUpdater`), and that write can land * after a sheet's close has asked for Back but before Back arrives. */ +import { useState } from "react"; import { describe, expect, it, vi } from "vitest"; import { DAY_HISTORY_KEY } from "@/components/day/day-url"; +import { renderWithApp } from "../../../../tests/browser/render"; +import { ResponsiveSheet } from "../responsive-sheet"; import { sheetHistory } from "../sheet-history"; type Navigation = { currentEntry: { index: number } }; @@ -97,4 +100,40 @@ describe("sheet history in the browser", () => { expect(state()[DAY_HISTORY_KEY]).toBe("2026-11-10"); expect(state()[MARK]).toBeUndefined(); }); + + it("answers Back during the discard question by keeping the sheet open", async () => { + window.history.pushState(null, "", "?step=form"); + const page = index(); + function EditedSheet() { + const [open, setOpen] = useState(true); + return ( + +

Typed but not saved

+
+ ); + } + const { screen } = await renderWithApp(); + await expect.element(screen.getByText("Typed but not saved")).toBeVisible(); + await vi.waitFor(() => expect(index()).toBe(page + 1)); + + // Back with unsaved input asks first and puts the sheet's entry back. + window.history.back(); + await settle(1); + await expect.element(screen.getByRole("alertdialog")).toBeVisible(); + expect(index()).toBe(page + 1); + + // Back again means "not now": the question goes, the sheet stays. + window.history.back(); + await settle(1); + await expect + .element(screen.getByRole("alertdialog")) + .not.toBeInTheDocument(); + await expect.element(screen.getByText("Typed but not saved")).toBeVisible(); + expect(index()).toBe(page + 1); + }); }); diff --git a/src/components/ui/responsive-sheet.tsx b/src/components/ui/responsive-sheet.tsx index 779bddc6d..ac634acf6 100644 --- a/src/components/ui/responsive-sheet.tsx +++ b/src/components/ui/responsive-sheet.tsx @@ -235,9 +235,10 @@ function useSheetDismissal({ }; const handleBack = React.useEffectEvent(() => { - if (hasUnsavedInput()) { + // Back while the question is open answers it with "keep editing". + if (asking || hasUnsavedInput()) { entryRef.current?.restore(); - setAsking(true); + setAsking(!asking); return; } countBack();