Merge branch 'main' into feature/e39b-machine-access
This commit is contained in:
@@ -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).
|
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
|
## 0.106.0 — 2026-10-13
|
||||||
|
|
||||||
### New
|
### 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",
|
"version": "0.106.0",
|
||||||
"date": "2026-10-13",
|
"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",
|
"version": "0.106.0",
|
||||||
"date": "2026-10-13",
|
"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
|
// 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.
|
// switched out from under it: dropping the command is the honest outcome.
|
||||||
it("drops the command when an overlay does not actually close", async () => {
|
it("drops the command when an overlay does not actually close", async () => {
|
||||||
|
|||||||
@@ -261,10 +261,14 @@ let listenerInstalled = false;
|
|||||||
const syncWaiters = new Set<() => void>();
|
const syncWaiters = new Set<() => void>();
|
||||||
|
|
||||||
// Push/pop history entries until their count matches the live overlay count. Runs once per tick.
|
// 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 {
|
function reconcileHistory(): void {
|
||||||
reconcileScheduled = false;
|
reconcileScheduled = false;
|
||||||
const want = overlayStack.length;
|
const want = overlayStack.length;
|
||||||
while (historyDepth < want) {
|
while (historyDepth < want && pendingProgrammatic === 0) {
|
||||||
historyDepth++;
|
historyDepth++;
|
||||||
window.history.pushState({ ...window.history.state, _ov: nextOverlayToken() }, "");
|
window.history.pushState({ ...window.history.state, _ov: nextOverlayToken() }, "");
|
||||||
}
|
}
|
||||||
@@ -294,6 +298,8 @@ function ensureOverlayListener(): void {
|
|||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("popstate", () => {
|
||||||
if (pendingProgrammatic > 0) {
|
if (pendingProgrammatic > 0) {
|
||||||
pendingProgrammatic--; // our own back(): consume and ignore
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Genuine user Back: the browser already dropped one overlay entry, so close the topmost
|
// Genuine user Back: the browser already dropped one overlay entry, so close the topmost
|
||||||
|
|||||||
Reference in New Issue
Block a user