Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02134799cc | ||
|
|
c8bc843517 | ||
|
|
759a7536b9 |
@@ -7,6 +7,12 @@ edit the release notes and re-run the generator.
|
||||
|
||||
Upgrading, pinning a version and rolling back: [docs/self-hosting.md](docs/self-hosting.md#upgrading).
|
||||
|
||||
## 0.106.1 — 2026-10-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Plex player: clicking outside the Settings panel closes the panel again instead of closing the player.
|
||||
|
||||
## 0.106.0 — 2026-10-13
|
||||
|
||||
### New
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "0.106.1",
|
||||
"date": "2026-10-14",
|
||||
"new": [],
|
||||
"fixed": [
|
||||
"Plex player: clicking outside the Settings panel closes the panel again instead of closing the player."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.106.0",
|
||||
"date": "2026-10-13",
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "0.106.1",
|
||||
"date": "2026-10-14",
|
||||
"new": [],
|
||||
"fixed": [
|
||||
"Plex lejátszó: a Beállítások panelen kívül kattintva ismét csak a panel záródik be, nem a lejátszó."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.106.0",
|
||||
"date": "2026-10-13",
|
||||
|
||||
@@ -236,6 +236,53 @@ describe("closeOverlaysThenNavigate", () => {
|
||||
}
|
||||
});
|
||||
|
||||
// The Plex player's ⋯ → Settings: the menu closes (its entry goes back) and the panel opens a task
|
||||
// later, before that back() has landed. A push made then is the entry the landing back() takes, so
|
||||
// the books said one entry stood that did not — and the panel's own close stepped back over the
|
||||
// player's entry, closing the player on a click outside the panel.
|
||||
it("an overlay opened while a back() is in flight pushes its entry only once that back() lands", async () => {
|
||||
const landing: (() => void)[] = [];
|
||||
const back = vi.spyOn(window.history, "back").mockImplementation(() => {
|
||||
landing.push(() =>
|
||||
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state })),
|
||||
);
|
||||
});
|
||||
const land = () => act(() => landing.splice(0).forEach((l) => l()));
|
||||
const push = vi.spyOn(window.history, "pushState");
|
||||
let panel: { unmount: () => void } | undefined;
|
||||
try {
|
||||
const menu = mountLayer(() => {});
|
||||
await settle();
|
||||
menu.unmount();
|
||||
await settle();
|
||||
expect(back).toHaveBeenCalledTimes(1);
|
||||
|
||||
push.mockClear();
|
||||
panel = mountLayer(() => {});
|
||||
await tick();
|
||||
expect(push).not.toHaveBeenCalled(); // it would be undone by the landing back()
|
||||
|
||||
land();
|
||||
await settle();
|
||||
expect(push).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The books are square: closing the panel reclaims its own entry, one back() and no more.
|
||||
back.mockClear();
|
||||
panel.unmount();
|
||||
panel = undefined;
|
||||
await settle();
|
||||
expect(back).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
// Square the module-global books for the next test, however far this one got.
|
||||
panel?.unmount();
|
||||
await settle();
|
||||
land();
|
||||
await settle();
|
||||
back.mockRestore();
|
||||
push.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
// No such overlay exists today, but a future "discard your changes?" guard must not have the page
|
||||
// switched out from under it: dropping the command is the honest outcome.
|
||||
it("drops the command when an overlay does not actually close", async () => {
|
||||
|
||||
@@ -261,10 +261,14 @@ let listenerInstalled = false;
|
||||
const syncWaiters = new Set<() => void>();
|
||||
|
||||
// Push/pop history entries until their count matches the live overlay count. Runs once per tick.
|
||||
// A push waits while a back() of ours is still in flight: it would land on the entry just pushed and
|
||||
// take it, leaving the books one entry above the history — and the next close then stepped back over
|
||||
// the entry BELOW, the player's own (the Plex player's ⋯ → Settings, then a click outside the panel,
|
||||
// closed the player). The popstate that lands the last back() reconciles again.
|
||||
function reconcileHistory(): void {
|
||||
reconcileScheduled = false;
|
||||
const want = overlayStack.length;
|
||||
while (historyDepth < want) {
|
||||
while (historyDepth < want && pendingProgrammatic === 0) {
|
||||
historyDepth++;
|
||||
window.history.pushState({ ...window.history.state, _ov: nextOverlayToken() }, "");
|
||||
}
|
||||
@@ -294,6 +298,8 @@ function ensureOverlayListener(): void {
|
||||
window.addEventListener("popstate", () => {
|
||||
if (pendingProgrammatic > 0) {
|
||||
pendingProgrammatic--; // our own back(): consume and ignore
|
||||
// The last one landed: an overlay that opened while it was in flight still owes its entry.
|
||||
if (pendingProgrammatic === 0 && historyDepth < overlayStack.length) scheduleReconcile();
|
||||
return;
|
||||
}
|
||||
// Genuine user Back: the browser already dropped one overlay entry, so close the topmost
|
||||
|
||||
Reference in New Issue
Block a user