diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index ffeb297..3d7c2e7 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -41,8 +41,15 @@ export default defineConfig( // no-useless-assignment joined eslint:recommended in ESLint 10, and its flow analysis // does not model runes: it reads `let { ref = $bindable(null) } = $props()` as a value // that is never read. Deleting the default, as it suggests, breaks the binding. + // + // no-unused-expressions is off for the same reason: a bare `book;` inside an $effect is + // how a reactive dependency is declared when the read would otherwise be untracked. + // Removing the statement stops the effect re-running. files: ['**/*.svelte'], - rules: { 'no-useless-assignment': 'off' } + rules: { + 'no-useless-assignment': 'off', + '@typescript-eslint/no-unused-expressions': 'off' + } }, { files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], diff --git a/frontend/src/lib/components/ui/tags-input/tags-input.svelte b/frontend/src/lib/components/ui/tags-input/tags-input.svelte index a69f265..e5ce48c 100644 --- a/frontend/src/lib/components/ui/tags-input/tags-input.svelte +++ b/frontend/src/lib/components/ui/tags-input/tags-input.svelte @@ -36,7 +36,6 @@ $effect(() => { // whenever input value changes reset invalid - // eslint-disable-next-line @typescript-eslint/no-unused-expressions inputValue; untrack(() => { diff --git a/frontend/src/lib/state/bookCollection.svelte.ts b/frontend/src/lib/state/bookCollection.svelte.ts index fb05cc3..886a33b 100644 --- a/frontend/src/lib/state/bookCollection.svelte.ts +++ b/frontend/src/lib/state/bookCollection.svelte.ts @@ -105,6 +105,7 @@ export class BookCollectionState { this.view = next; + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const url = new URL(page.url); url.searchParams.set('view', next); // Not a route to resolve — this is the current URL with one query param @@ -138,6 +139,7 @@ export class BookCollectionState { updateSearchParams() { // Update URL + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const url = new URL(window.location.href); Object.entries(this.filters).forEach(([filter, values]) => { @@ -215,6 +217,7 @@ export class BookCollectionState { // and skips every page in between. this.currentBookPage = 1; + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const urlParams = new URLSearchParams(window.location.search); // Re-read sort and filter state from URL diff --git a/frontend/src/lib/state/bookOperations.svelte.ts b/frontend/src/lib/state/bookOperations.svelte.ts index b042d74..05976e7 100644 --- a/frontend/src/lib/state/bookOperations.svelte.ts +++ b/frontend/src/lib/state/bookOperations.svelte.ts @@ -131,6 +131,7 @@ export class BookOperationsState { async downloadBooks(bookIds: string[] | number[], filename?: string) { // Construct the download URL + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const downloadUrl = new URL(`/api/books/download`, page.url.origin); downloadUrl.searchParams.set('library_id', this.libraryId); bookIds.forEach((id) => { @@ -142,6 +143,7 @@ export class BookOperationsState { async downloadBookFile(bookId: number, fileId: number, filename: string) { // Construct the download URL + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const downloadUrl = new URL(`/api/books/download/${bookId}/${fileId}`, page.url.origin); this.download(downloadUrl, filename); } diff --git a/frontend/src/lib/state/bookshelf.svelte.ts b/frontend/src/lib/state/bookshelf.svelte.ts index d051569..907a584 100644 --- a/frontend/src/lib/state/bookshelf.svelte.ts +++ b/frontend/src/lib/state/bookshelf.svelte.ts @@ -116,6 +116,7 @@ export class BookshelfState { if (!bookshelves) return; // Create a Set of shelf IDs that need updating for efficient lookup + // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state const shelfIdsToUpdate = new Set(); books.forEach((book) => { book.lists.forEach((shelf) => { diff --git a/frontend/src/routes/(root)/settings/devices/+page.svelte b/frontend/src/routes/(root)/settings/devices/+page.svelte index 893ef71..fb29e89 100644 --- a/frontend/src/routes/(root)/settings/devices/+page.svelte +++ b/frontend/src/routes/(root)/settings/devices/+page.svelte @@ -21,11 +21,12 @@ import { toast } from 'svelte-sonner'; import { invalidateAll } from '$app/navigation'; import type { Device } from '$lib/schema/device'; + import { SvelteSet } from 'svelte/reactivity'; let { data } = $props(); let createDialogOpen = $state(false); - let visibleApiKeys = $state>(new Set()); + const visibleApiKeys = new SvelteSet(); let deleteConfirmDevice = $state(null); let regenerateConfirmDevice = $state(null); @@ -35,7 +36,6 @@ } else { visibleApiKeys.add(deviceId); } - visibleApiKeys = new Set(visibleApiKeys); } function maskApiKey(apiKey: string): string {