Skip to content

fix(sheets): make the sheet edge visible on Android - #757

Merged
wyne merged 1 commit into
mainfrom
fix/android-sheet-separation
Sep 10, 2026
Merged

fix(sheets): make the sheet edge visible on Android#757
wyne merged 1 commit into
mainfrom
fix/android-sheet-separation

Conversation

@wyne

@wyne wyne commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

The game sheet had no visible boundary on Android. In light mode it blended completely into the board behind it — the handle was the only thing that gave it away.

image image

Why the previous attempts couldn't work

Three things stack up, and fixing only one of them leaves the sheet invisible:

  1. The elevation was landing on a transparent view. <BottomSheet style={...}> isn't applied to the sheet surface — the library forwards it to BottomSheetBody, a bare Animated.View with no background. Android derives an elevation shadow from the view's background outline, so no background means an empty outline and elevation: 8 was a literal no-op. The iOS shadow* props on that same view did work, because iOS derives the shadow from layer contents instead.

  2. Moving elevation onto the surface would have broken the sheet. It reorders siblings on the Z axis, so the background would paint over the handle and content.

  3. There was no color to fall back on. sheetBackground and background are both #F2F2F7 in light mode, and TileBoard paints bottomSheetHeight + 2 of padding in exactly that color right behind the collapsed sheet. The shadow was doing 100% of the work of showing that a sheet existed.

Changes

  • New SheetBackground — a shared backgroundComponent that draws the surface itself, so the shadow has a real shape to come from.
  • boxShadow instead of elevation: paint-only, no Z reordering, supported on both platforms under the New Architecture.
  • A hairline border is what actually guarantees the fix. Outset boxShadow is gated at API 28 on Android and silently does nothing below it, and minSdk is Expo's default 24.
  • New sheetBorder / sheetShadow theme tokens; the dead sheetShadow StyleSheet blocks are gone from GameSheet and ChooseWinnersSheet. All four sheets now share the one surface.

Validation

Verified on a Pixel 10 emulator (API 37) through Fast Refresh, stashing and unstashing the change to compare the same frame region:

  • light mode collapsed — the reported bug, and the fix
  • expanded — content stacks correctly over the background, no Z-order regression
  • dark mode, collapsed and expanded

npx tsc --noEmit, npx eslint src/ (0 errors), npx jest (451 tests, 45 suites) all pass.

iOS is unverified — no simulator was booted and a build is slow, so this is reasoned rather than tested. The shadow moved from the transparent container onto the actual rounded surface, which should read near-identically or slightly crisper. Worst case if boxShadow disappoints there is a flatter shadow, not an invisible sheet, since the border still holds.

Follow-up worth considering separately

sheetBackground === background in light mode is the root cause the shadow was papering over. Nudging the light sheet to something like #EAEAEF would give real tonal separation on every platform. Left alone here because it changes the iOS look and the white sub-elements inside the sheet (the Edit button, the ChooseWinners player list) are tuned against the current value.

🤖 Generated with Claude Code

The game sheet had no visible boundary on Android, so in light mode it
blended into the board behind it and only the handle gave it away.

`<BottomSheet style={...}>` is forwarded to `BottomSheetBody`, a
transparent container rather than the sheet surface. Android derives an
`elevation` shadow from the view's background outline, so the
`elevation: 8` in the old `sheetShadow` style never drew anything; only
the iOS `shadow*` props on that same view took effect. Moving
`elevation` onto the surface is not an option either, since it reorders
siblings on the Z axis and would paint the background over the sheet's
own content.

Draw the surface in a shared `backgroundComponent` instead, and separate
it with `boxShadow` (paint-only, no Z reordering, supported on both
platforms under the New Architecture) plus a hairline border. The border
is what guarantees the fix: outset `boxShadow` is gated on API 28 and
silently no-ops below it, and light mode has no color contrast to fall
back on, since `sheetBackground` and `background` are both `#F2F2F7` and
`TileBoard` paints that color directly behind the collapsed sheet.

All four sheets now share the one surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage after merging fix/android-sheet-separation into main will be

65.71%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
redux
   GamesSlice.ts59.90%35.42%63.33%69.30%104–105, 105, 105, 107, 115–116, 116, 116, 118, 126, 148, 173–175, 190, 220, 227, 229, 232–234, 298, 311, 320–321, 321, 321, 327, 327, 327–328, 330, 332–333, 336, 336, 336, 336, 336–337, 340–341, 343, 345, 345, 345, 345, 345–346, 348, 348, 348, 350–351, 354, 354, 354, 356, 58, 69, 86, 91–92, 92, 92
   PlayersSlice.ts88.04%95%85.71%86.27%105, 107, 115, 35, 52–53, 70–71
   SettingsSlice.ts61.54%66.67%55%65.38%103, 106, 111, 117, 61, 64, 70, 73, 76, 88, 97
   backup.ts78.19%75.19%88.89%80.21%105, 105, 128–130, 138, 174–175, 177, 191–195, 197, 197, 197–198, 203, 206, 217, 227–228, 244, 58–63, 65, 68, 68–70, 70, 70–71, 71, 71, 73–74, 76–78, 80–81, 81–82, 88–89
   hooks.ts100%100%100%100%
   scoreUtils.ts93.33%87.50%100%100%5
   selectors.ts100%100%100%100%
   store.ts0%0%0%0%10, 12, 12, 12–13, 13, 13–15, 18, 21–23, 23, 23, 23, 23–24, 29, 33, 60, 67, 74, 81, 99
   testStore.ts0%100%0%0%11–15, 20–23, 25, 42–44, 5
src
   Analytics.ts100%100%100%100%
   AnalyticsEvents.ts100%100%100%100%
   ColorPalette.ts100%100%100%100%
   Logger.ts91.23%87.50%81.82%94.74%14–15, 22
   Navigation.tsx82.35%80%66.67%88.89%104–105, 49, 53, 95, 98
   constants.ts100%100%100%100%
   platform.ts100%100%100%100%
   theme.ts93.75%85.71%100%100%85
src/components
   EditGame.tsx79.63%75%66.67%84.85%35, 49, 62–63, 86, 86, 86–87
   FloatingActionButton.tsx95.24%100%92.31%96%47
   GameListItem.tsx94.19%85.29%100%100%122, 155, 25, 31, 58
   GameListItemPlayerName.tsx100%100%100%100%
   MenuOpenContext.tsx87.50%100%66.67%100%
   PlayerListItem.tsx0%0%0%0%123, 123, 134, 24, 32–34, 34, 34–38, 40, 40, 40, 40, 40–41, 41, 41–42, 42, 42, 44–45, 56, 63–64, 66, 72–73, 76, 79, 82–83, 90, 93, 93, 96, 96, 96–97, 99
   ScoreLogTable.tsx90.91%80%100%96.67%51, 53, 55, 55, 64, 70
   SectionLabel.tsx100%100%100%100%
   SplashOverlay.tsx0%0%0%0%22–24, 24, 24, 26–28, 30, 32–33, 35, 37, 40, 40, 40, 43, 49, 53, 58, 74
src/components/AppInfo
   RotatingIcon.tsx0%0%0%0%18–19, 21–24, 26, 28–30, 37–38, 40, 40, 40–41, 44, 44, 44–48, 51, 53–54, 59
   SeedData.ts0%100%0%0%17, 47, 49–50, 52, 54–56, 58–60, 62, 68, 71, 82–85
src/components/BigButtons
   BigButton.tsx0%0%0%0%18, 18–20, 22–23, 23, 25, 25–26, 26, 48
src/components/Boards
   ListBoard.tsx86.81%72.73%84.21%98.57%123, 180, 206–207, 220, 223, 262, 29, 33, 59–61, 84, 84, 88, 92
   PlayerTile.tsx0%0%0%0%29, 38–42, 42, 42–43, 43, 43, 43, 43–44, 44, 44–46, 46, 46, 48–50, 50, 50, 52, 55, 55, 59–60, 63–64, 66, 68, 68, 68, 68, 68–69, 69, 69, 69, 69, 71, 81, 81, 98
   TileBoard.tsx96.43%90.32%100%100%18, 77, 93
src/components/Buttons
   AppSettingsButton.tsx100%100%100%100%
   GameOptionsButton.tsx51.85%42.86%69.23%54.72%102, 122, 122, 122–123, 125–126, 130, 130, 130, 130, 130, 130, 130, 132–133, 135–136, 138–139, 141–144, 146–148, 150–152, 159, 165–166, 177, 179, 179, 185, 185, 185, 185, 40, 46, 48, 58, 65, 73, 94, 98
   HeaderButton.tsx81.82%60%100%100%19–20
src/components/ColorPalettes
   ColorSelector.tsx100%100%100%100%
   PalettePreview.tsx0%0%0%0%10–12, 14, 19, 19, 25
   PaletteSelector.tsx0%0%0%0%13, 15–18, 20, 22, 22, 22, 24–25, 31, 37, 40, 45, 56
src/components/Headers
   RoundHeaderTitle.tsx0%0%0%0%12–14, 16–17, 17, 17–18, 18, 18–19, 19, 19, 21, 21, 21, 23–24, 26–27, 27, 27, 27, 27, 29–30, 30, 33–34, 43–44, 44, 44, 46–48, 56, 58, 61, 61, 61, 61, 63, 63, 69, 69,

@wyne
wyne merged commit cee99ce into main Sep 10, 2026
8 checks passed
@wyne
wyne deleted the fix/android-sheet-separation branch September 10, 2026 02:38
@wyne wyne mentioned this pull request Sep 10, 2026
wyne added a commit that referenced this pull request Sep 10, 2026
Merging this PR bumps `package.json` to the version below, tags the
merge commit, and publishes the GitHub release. `app.config.js` reads
its `version` from `package.json`, so the shipped app version follows
automatically.
---


##
[3.1.2](v3.1.1...v3.1.2)
(2026-09-10)


### Bug Fixes

* **edit-player:** give the screen a top margin and a full-height scroll
area ([#747](#747))
([d916b92](d916b92))
* **fab:** restore SwiftUI floating action button
([#753](#753))
([698bfbe](698bfbe))
* **fab:** stop an outside tap on the open menu reaching the list
([#746](#746))
([4cc7a35](4cc7a35))
* **game-list:** tidy the row's hierarchy, spacing and dates
([#750](#750))
([d07ce4e](d07ce4e))
* **list:** refine game list hierarchy
([#752](#752))
([5417264](5417264))
* **list:** repair game list separators and revert the android options
button
([#756](#756))
([7d73a70](7d73a70))
* polish android interactions
([#755](#755))
([d061fe4](d061fe4))
* **settings:** label the analytics state in the version alert
([#745](#745))
([29a16c2](29a16c2))
* **sheets:** make the sheet edge visible on Android
([#757](#757))
([cee99ce](cee99ce))
* **ui:** one section label style across the app
([#748](#748))
([0e9a7b8](0e9a7b8))


### Infrastructure

* drop --what-to-test so the iOS submission can be scheduled
([#743](#743))
([a7a27bd](a7a27bd))


### Miscellaneous

* **deps:** bump expo to 57.0.21 and expo-glass-effect to 57.0.2
([#751](#751))
([bcaa6eb](bcaa6eb))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant