From e7af7a4b77c695ad122c4b92b8249c9a10eea041 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 27 Aug 2026 11:55:23 -0400 Subject: [PATCH] board: the FAB and the undo snackbar rode behind the keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by the Scaffold audit #2951 asked for. Three Scaffolds exist; the editor and the sync screen both consume the IME inset, and the board consumed nothing. `enableEdgeToEdge()` makes the manifest's `adjustResize` a no-op on API 30+, so nothing resizes for the keyboard unless the app asks — and `ScaffoldDefaults.contentWindowInsets` is systemBars, which the IME is not part of. The Scaffold positions the FAB and the snackbar host from that value, so with the search field focused both sat under the keyboard. Not theoretical, and newly load-bearing: `3f0eef1` put an UNDO on the trash snackbar, so the one control you could not reach was the one that takes back a note you did not mean to throw away — reachable by searching, long-pressing a hit and trashing it. `union` rather than `add`: the navigation bar and the IME are the same edge, not two stacked ones, and adding them would inset twice under a keyboard that already covers the nav bar. Set once on the Scaffold rather than per-slot, so the content column shrinks with it and the board's cards stay above the keyboard instead of scrolling under it. Co-Authored-By: Claude Opus 5 --- .../fabledsword/thoughtsync/ui/BoardScreen.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt index 7c52d62..62dfe60 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt @@ -6,11 +6,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.union import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells @@ -37,6 +40,7 @@ import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -138,6 +142,21 @@ fun BoardScreen( }, ) { Scaffold( + // The IME, added to what the Scaffold already insets for. `enableEdgeToEdge` + // makes the manifest's `adjustResize` a no-op on API 30+, so nothing resizes + // for the keyboard unless the app asks — and `ScaffoldDefaults.contentWindowInsets` + // is systemBars, which the IME is not part of. The Scaffold positions the FAB + // AND the snackbar host from this value, so without it both sit behind the + // keyboard whenever the search field has focus. That is not theoretical: the + // undo on a trashed search hit is exactly the control you cannot reach. + // + // `union` rather than `add` — the two are the same edge, not two stacked ones. + // Adding them would inset by the navigation bar a second time underneath a + // keyboard that already covers it. + // + // One owner for the edge, as with the search bar's missing statusBarsPadding: + // set here, the content Column gets it through `padding` and must not repeat it. + contentWindowInsets = ScaffoldDefaults.contentWindowInsets.union(WindowInsets.ime), snackbarHost = { SnackbarHost(snackbars) }, floatingActionButton = { // The + is the ONLY way in, by design: one obvious target rather