fix: guard AddToCollectionModal against out-of-order load results
Retargeting the modal mid-load could resolve responses out of order, overwriting the newer target's membership with the older one's (audit FE-2). Route load() through the tested CancellableLoader so a superseded result is dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.128.14"
|
version = "0.128.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.128.14"
|
version = "0.128.15"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.128.14",
|
"version": "0.128.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AdaptiveDialog from './AdaptiveDialog.svelte';
|
import AdaptiveDialog from './AdaptiveDialog.svelte';
|
||||||
|
import { CancellableLoader } from '$lib/util/cancellable';
|
||||||
import {
|
import {
|
||||||
addMangaToCollection,
|
addMangaToCollection,
|
||||||
createCollection,
|
createCollection,
|
||||||
@@ -74,19 +75,30 @@
|
|||||||
: removePageFromCollection(collectionId, t.id);
|
: removePageFromCollection(collectionId, t.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reopening for a different target (or an in-flight retarget) fires load()
|
||||||
|
// again; without this guard the two responses can resolve out of order and
|
||||||
|
// the first target's membership overwrites the second's (audit FE-2). The
|
||||||
|
// CancellableLoader drops a superseded call's result — the newer load then
|
||||||
|
// owns both the state and the spinner.
|
||||||
|
const loader = new CancellableLoader();
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading = true;
|
loading = true;
|
||||||
error = null;
|
error = null;
|
||||||
try {
|
try {
|
||||||
const [page, ids] = await Promise.all([
|
const result = await loader.run(async () => {
|
||||||
listMyCollections({ limit: 200 }),
|
const [page, ids] = await Promise.all([
|
||||||
loadContaining(target)
|
listMyCollections({ limit: 200 }),
|
||||||
]);
|
loadContaining(target)
|
||||||
collections = page.items;
|
]);
|
||||||
containingIds = new Set(ids);
|
return { items: page.items, ids };
|
||||||
|
});
|
||||||
|
if (result === null) return; // superseded — a newer load owns the state
|
||||||
|
collections = result.items;
|
||||||
|
containingIds = new Set(result.ids);
|
||||||
|
loading = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error).message;
|
error = (e as Error).message;
|
||||||
} finally {
|
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user