From 178fab2b7c8ea6f090ae693827170b71aa80c8e9 Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 29 Sep 2026 12:38:07 -0500 Subject: [PATCH] Fix(cloud): keep the settings conflict buttons on screen on a phone On a phone the "Vault settings differ from the cloud" dialog put its buttons where they could not be tapped, so the conflict could never be resolved and sync stayed in review (Android #88, from @uNyanda). Two things combined. The footer was one row that could not wrap: its note plus "Decide later" and "Keep this device's settings" were wider than the sheet at Android's usual 115 to 130 % text size, so the note shrank to a sliver and both buttons ran past the edge, where the panel clipped them. And the panel capped itself at 88vh while the body separately capped at 60vh, so a long description, 60vh of settings and the footer could still outgrow the panel. The panel is now a column capped at 88dvh (the screen that is actually visible) in which only the body scrolls, so the title and the buttons always stay on screen. The footer wraps: the note keeps a readable width and the buttons move under it, wrapping again if needed. Desktop looks as before, with more rows visible before the list scrolls. Reproduced in the web client at 393 x 851 (the reporter's phone) with a five-section conflict: at 115 and 130 % text both buttons fell outside the dialog. After the fix, at 393 x 851 and 360 x 640 and at 100, 115 and 130 % text, the title and both buttons stay inside the dialog and on screen and the body scrolls. A structural test pins the column, the scrolling body and the wrapping footer. The three core packages move to 2.58.1 for the phones' core-only release; the desktop app stays 2.58.0. --- package-lock.json | 6 +++--- packages/app-core/package.json | 2 +- .../CloudSettingsConflictDialog.test.ts | 15 +++++++++++++++ .../components/CloudSettingsConflictDialog.tsx | 14 ++++++++++---- packages/bridge-contract/package.json | 2 +- packages/shared-domain/package.json | 2 +- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21e45f6c..8a07c322 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16382,7 +16382,7 @@ }, "packages/app-core": { "name": "@zennotes/app-core", - "version": "2.58.0", + "version": "2.58.1", "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", @@ -16469,14 +16469,14 @@ }, "packages/bridge-contract": { "name": "@zennotes/bridge-contract", - "version": "2.58.0", + "version": "2.58.1", "devDependencies": { "typescript": "^5.7.2" } }, "packages/shared-domain": { "name": "@zennotes/shared-domain", - "version": "2.58.0", + "version": "2.58.1", "dependencies": { "@zennotes/bridge-contract": "*", "lz-string": "^1.5.0" diff --git a/packages/app-core/package.json b/packages/app-core/package.json index 4165a109..f3b4003a 100644 --- a/packages/app-core/package.json +++ b/packages/app-core/package.json @@ -1,7 +1,7 @@ { "name": "@zennotes/app-core", "private": true, - "version": "2.58.0", + "version": "2.58.1", "type": "module", "exports": { "./main": "./src/main.tsx", diff --git a/packages/app-core/src/components/CloudSettingsConflictDialog.test.ts b/packages/app-core/src/components/CloudSettingsConflictDialog.test.ts index c32bd033..7d37f75c 100644 --- a/packages/app-core/src/components/CloudSettingsConflictDialog.test.ts +++ b/packages/app-core/src/components/CloudSettingsConflictDialog.test.ts @@ -125,6 +125,21 @@ describe("CloudSettingsConflictDialog", () => { expect(applied.folderIcons).toEqual({ "inbox:Projects": "bolt", "inbox:Archive": "archive" }); }); + it("keeps the buttons on a phone: a column where only the body scrolls, and a footer that wraps (Android #88)", () => { + // jsdom has no layout, so this pins the structure the fix depends on; the + // measured check at 360 to 393 px and 100 to 130 % text is in the release notes. + const panel = document.body.querySelector('[data-cloud-settings-conflict-dialog] [role="dialog"]') as HTMLElement; + expect(panel.className).toMatch(/\bflex\b/); + expect(panel.className).toMatch(/\bflex-col\b/); + expect(panel.className).toMatch(/max-h-\[88dvh\]/); + const [, body, footer] = [...panel.children] as HTMLElement[]; + expect(body.className).toMatch(/\bmin-h-0\b/); + expect(body.className).toMatch(/\boverflow-y-auto\b/); + expect(body.className).not.toMatch(/max-h-\[60vh\]/); + expect(footer.className).toMatch(/\bflex-wrap\b/); + expect(footer.contains(button(host, "Decide later"))).toBe(true); + }); + it("applies nothing when postponed", async () => { await act(async () => sectionChoice("Favorites", "Cloud").click()); await act(async () => button(host, "Decide later").click()); diff --git a/packages/app-core/src/components/CloudSettingsConflictDialog.tsx b/packages/app-core/src/components/CloudSettingsConflictDialog.tsx index 3455adc3..03dad4b8 100644 --- a/packages/app-core/src/components/CloudSettingsConflictDialog.tsx +++ b/packages/app-core/src/components/CloudSettingsConflictDialog.tsx @@ -118,7 +118,10 @@ export function CloudSettingsConflictDialog({ onClose={onClose} labelledBy={TITLE_ID} initialFocus={primary} - className="max-h-[88vh]" + // A column capped to the visible screen: only the body scrolls, so the + // header and the buttons stay on screen however many settings differ. + // Phones (Android #88) put the buttons past the sheet's edge otherwise. + className="flex max-h-[88dvh] flex-col" // The existing guards (vim leader, editor focus, overlay detection) key // on the first marker; the second names this dialog for its own tests. data={{ @@ -131,7 +134,7 @@ export function CloudSettingsConflictDialog({ titleId={TITLE_ID} description={describeQuestion(vaultName, described, differences)} /> - + {differences.length > 0 && described !== null && ( <>
@@ -190,8 +193,11 @@ export function CloudSettingsConflictDialog({
)}
- -
+ {/* The note keeps a readable width and the buttons wrap under it on a + narrow screen, instead of the note shrinking to nothing while the + buttons run past the edge (Android #88). */} + +
This device’s settings stay in use until you decide.