fix(nav): a navigation closes every overlay before it pushes
The overlay history stack let a navigation land ABOVE open overlays and "buried" their entries with a counter. Measured in Chrome: Shift+W over About navigated under the still-open dialog, the next Back both undid the navigation and closed About, and the Back after that did nothing; Shift+W on Home with the player open did nothing at all. The counter could also be spent by a different overlay closing first, and callers that could not report whether they pushed were assumed to have. setPage and openChannel now close every open overlay and push only once the history is back in sync (one entry per live overlay, no programmatic back() in flight, no reconcile queued), so nothing is ever buried and the whole mechanism goes: navigateOverOverlays, closeThenNavigate, buryUnder, setPage's boolean, and the three test files that pinned them. The palette closes only itself before running a row, so a theme picked over the player keeps the player. Shift+W on Home with the player open now closes it (owner decision 2026-09-09). The wait is on state, not on "a popstate": the old 250 ms fallback could run with back()s still in flight. An overlay that refuses to close within 1.5 s drops the command instead of being navigated out from under.
This commit is contained in:
+9
-10
@@ -1,21 +1,20 @@
|
||||
import { navigateOverOverlays } from "./history";
|
||||
import type { Page } from "./urlState";
|
||||
|
||||
// Tiny decoupled navigator so deep components (a feed card's download toast, the Download
|
||||
// Center) can switch pages without threading setPage through the whole tree. App registers
|
||||
// its setPage on mount; navigateTo is a no-op until then.
|
||||
let _navigate: ((p: Page) => boolean | void) | null = null;
|
||||
// Center) can switch pages without threading setPage through the whole tree. NavigationProvider
|
||||
// registers its setPage on mount; navigateTo is a no-op until then.
|
||||
let _navigate: ((p: Page) => void) | null = null;
|
||||
|
||||
export function setNavigator(fn: (p: Page) => boolean | void): void {
|
||||
export function setNavigator(fn: (p: Page) => void): void {
|
||||
_navigate = fn;
|
||||
}
|
||||
|
||||
/** Over whatever overlay is open: the "View" on a "Download added" toast is clicked INSIDE the player
|
||||
* (its toast host), and the page change unmounts the page-owned player, whose Back-to-close cleanup
|
||||
* then popped the entry the navigation had just pushed — the user landed back on the feed with the
|
||||
* player gone (deep-review round 4). Same trap, same fix as Shift+W (see `navigateOverOverlays`). */
|
||||
/** The "View" on a "Download added" toast is clicked INSIDE the player (its toast host). That is
|
||||
* safe because `setPage` closes every open overlay before it pushes (see
|
||||
* `lib/history.closeOverlaysThenNavigate`) — without it the player's Back-to-close cleanup popped the
|
||||
* entry the navigation had just pushed (deep-review round 4). */
|
||||
export function navigateTo(p: Page): void {
|
||||
navigateOverOverlays(() => _navigate?.(p));
|
||||
_navigate?.(p);
|
||||
}
|
||||
|
||||
/** Should this click be handled IN-APP, or left to the browser?
|
||||
|
||||
Reference in New Issue
Block a user