Repository navigation
feat(web): make Back step out of what was opened instead of leaving the app - #739
Merged
Merged
Conversation
…he app The map page mirrored all state into the URL with replaceState only, so the history stack never grew and the system Back gesture closed the installed PWA (and the native app). Every surface now gets a history entry and Back closes the last one opened. - lib/backNavigation owns all history writes. Opening a panel, place, list item or map tool pushes an entry (navigationPath ignores camera, base layer and overlays, which keep rewriting the current entry). Closing rewrites the entry; sibling switches rewrite it too. - Back restores only the open surfaces: the map stays where the user left it, and stepping out of something closes it in place instead of rebuilding the rest from the URL. - App code never calls history.back(): to the Next.js router that popstate would cancel a page navigation in flight (a drawer link that also closes the drawer). Closed surfaces leave their entry; a Back landing on an entry that shows nothing new keeps going, and the next surface reuses such an entry. - useBackDismiss gives transient surfaces an entry: Dialog/Menu/Popover via Back-aware wrappers in components/ui (enforced by lint, exposed to integrations), the drawer menu, full-screen search, photo gallery, map-click card, an expanded bottom sheet (Back lowers it) and active guidance (Back ends the trip like the End button). - Text searches are encoded as `search=` so they survive Back and sharing. - The native shell sends Android's hardware Back into the WebView history.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
In the installed PWA (and the native app), the system Back gesture closed OpenMapX instead of closing what the user had just opened.
DeepLinkManagermirrored all state into the URL withreplaceStateonly, so the history stack never grew past one entry.Now every surface gets a history entry, and Back closes the most recent one:
How it works
lib/backNavigation.tsis the only code that writes history.navigationPath()(indeepLink.ts) lists the open surfaces and leaves out the camera, base layer and overlays, so panning or toggling a layer still rewrites the current entry.history.back(). Next.js treats any popstate as a traversal and cancels a route navigation in flight, so a drawer link that closes the drawer as it navigates would silently stay on the map. Instead, a surface closed by its own control leaves its entry behind. A Back that lands on an entry showing nothing new keeps going, so Back always visibly does something. The next surface to open reuses such an entry, so history doesn't pile up.useBackDismiss(open, onBack)gives a transient surface its own entry.components/ui/{Dialog,Menu,Popover}wrap MUI and close on Back the way Escape closes them. All ~60 importers now use them, a BiomenoRestrictedImportsrule keeps new code on them, and integrations get@/integration-api/components/Dialog.search=in the URL, so a results list survives Back (and sharing).useHardwareBacksends Android's hardware Back toWebView.goBack()while the page has history; otherwise Back goes to the system. iOS already uses the WebView's swipe-back gesture.Trade-offs
Verification
historyStep,navigationPath, push/rewrite/reuse rules, layers, step-over of leftover entries (including returning from another page), and the router-navigation case (apps/web/src/lib/backNavigation.test.ts). Also Back inDeepLinkManager(closes the card in place without re-searching the list), theDialogwrapper, the bottom sheet, and the mobileuseHardwareBack/Appwiring.pnpm exec vitest run --project web: 5723 passed. 8 failures inuseNavJunctions.test.tsxandCategoryResultMarkers.test.tsxalso fail on an unmodified checkout in this environment.pnpm -C apps/mobile run test: 1220 passed.tsc --noEmitpasses for web,tsconfig.integrations.json,tsconfig.sw.jsonand mobile.biome check . --diagnostic-level=error, Prettier on the changed docs andpnpm check-translationspass.pnpm check-dead-codereports only findings in files this PR doesn't touch.next dev, 390×844 touch viewport, no API backend. Back closed: the drawer; the Settings dialog opened from the drawer, then the drawer underneath; the directions panel; the full-screen search; the layer popover. Closing directions with its own button didn't leave a Back that reopens it. A drawer link to/settings/offlinenavigated normally, and Back from there returned to the map. The page didn't reload between steps.Release and upgrade impact
No migration or operator action. Contributors must import
Dialog,MenuandPopoverfrom@/components/ui/...(integrations:@/integration-api/components/Dialog); lint enforces this.