From 60aad12cd47f0512755eeedb369067e89bc7769e Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Fri, 22 May 2026 14:14:09 +0300 Subject: [PATCH 1/3] fix(settings): show excluding state on library index exclude action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flush UI before the async unbind, disable repeat clicks, cancel an active sync when needed, and label the button "Excluding…" / localized equivalent. --- .../settings/LibraryIndexSection.tsx | 39 ++++++++++++++++--- .../settings/LibraryIndexServerRow.tsx | 9 ++++- src/locales/de/settings.ts | 1 + src/locales/en/settings.ts | 1 + src/locales/es/settings.ts | 1 + src/locales/fr/settings.ts | 1 + src/locales/nb/settings.ts | 1 + src/locales/nl/settings.ts | 1 + src/locales/ro/settings.ts | 1 + src/locales/ru/settings.ts | 1 + src/locales/zh/settings.ts | 1 + src/utils/library/librarySyncQueue.ts | 19 +++++++++ 12 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/components/settings/LibraryIndexSection.tsx b/src/components/settings/LibraryIndexSection.tsx index b3d578bb..ceda5e13 100644 --- a/src/components/settings/LibraryIndexSection.tsx +++ b/src/components/settings/LibraryIndexSection.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { flushSync } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { DatabaseZap } from 'lucide-react'; import { useAuthStore } from '../../store/authStore'; @@ -18,7 +19,7 @@ import { bootstrapIndexedServer, type BindServerResult, } from '../../utils/library/librarySession'; -import { enqueueLibrarySync } from '../../utils/library/librarySyncQueue'; +import { enqueueLibrarySync, waitForLibrarySyncIdle } from '../../utils/library/librarySyncQueue'; import { syncIngestDisplayCount } from '../../utils/library/libraryReady'; import { serverListDisplayLabel } from '../../utils/server/serverDisplayName'; import LibraryIndexServerRow, { type LibraryServerConnection } from './LibraryIndexServerRow'; @@ -58,6 +59,7 @@ export default function LibraryIndexSection() { const [connectionByServer, setConnectionByServer] = useState>({}); const [progressByServer, setProgressByServer] = useState>({}); const [busyServerId, setBusyServerId] = useState(null); + const [excludingServerId, setExcludingServerId] = useState(null); const [bootstrapping, setBootstrapping] = useState(false); const pollTimer = useRef | null>(null); @@ -130,6 +132,7 @@ export default function LibraryIndexSection() { setConnectionByServer({}); setProgressByServer({}); setBusyServerId(null); + setExcludingServerId(null); return; } void runBootstrap(); @@ -263,8 +266,21 @@ export default function LibraryIndexSection() { }; const handleExcludeServer = async (serverId: string) => { - setBootstrapping(true); + if (excludingServerId) return; + flushSync(() => setExcludingServerId(serverId)); try { + const syncing = + busyServerId === serverId || + statusByServer[serverId]?.syncPhase === 'initial_sync' || + statusByServer[serverId]?.syncPhase === 'probing'; + if (syncing) { + try { + await librarySyncCancel(); + await waitForLibrarySyncIdle(serverId); + } catch { + /* best-effort — proceed with unbind */ + } + } await librarySyncClearSession(serverId); setServerSyncExcluded(serverId, true); setStatusByServer(prev => { @@ -277,10 +293,18 @@ export default function LibraryIndexSection() { delete next[serverId]; return next; }); + setProgressByServer(prev => { + const next = { ...prev }; + delete next[serverId]; + return next; + }); + if (busyServerId === serverId) { + setBusyServerId(null); + } } catch (e) { showToast(t('settings.libraryIndexBindError', { error: String(e) }), 5000, 'error'); } finally { - setBootstrapping(false); + setExcludingServerId(null); } }; @@ -292,7 +316,7 @@ export default function LibraryIndexSection() { } }; - const globalBusy = bootstrapping || busyServerId != null; + const globalBusy = bootstrapping || busyServerId != null || excludingServerId != null; return ( void runServerAction(srv.id, 'full')} onDeltaSync={() => void runServerAction(srv.id, 'delta')} onVerify={() => void runServerAction(srv.id, 'verify')} @@ -376,7 +403,7 @@ export default function LibraryIndexSection() { type="button" className="btn btn-surface" style={{ fontSize: 12, padding: '4px 10px' }} - disabled={bootstrapping} + disabled={bootstrapping || excludingServerId != null} onClick={() => void handleIncludeServer(srv.id)} > {t('settings.libraryIndexIncludeServer')} diff --git a/src/components/settings/LibraryIndexServerRow.tsx b/src/components/settings/LibraryIndexServerRow.tsx index 4156b9a5..38995336 100644 --- a/src/components/settings/LibraryIndexServerRow.tsx +++ b/src/components/settings/LibraryIndexServerRow.tsx @@ -18,6 +18,7 @@ interface LibraryIndexServerRowProps { connection: LibraryServerConnection; progressLabel: string | null; busy: boolean; + excluding: boolean; actionsDisabled: boolean; onFullSync: () => void; onDeltaSync: () => void; @@ -33,6 +34,7 @@ export default function LibraryIndexServerRow({ connection, progressLabel, busy, + excluding, actionsDisabled, onFullSync, onDeltaSync, @@ -133,11 +135,14 @@ export default function LibraryIndexServerRow({ type="button" className="btn btn-ghost" style={{ fontSize: 12, padding: '4px 10px', color: 'var(--text-muted)' }} - disabled={actionsDisabled} + disabled={actionsDisabled || excluding} + aria-busy={excluding} onClick={onExclude} > - {t('settings.libraryIndexExcludeServer')} + {excluding + ? t('settings.libraryIndexExcludingServer') + : t('settings.libraryIndexExcludeServer')} diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index 50343613..d01b26ab 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -261,6 +261,7 @@ export const settings = { libraryIndexFullResync: 'Vollständige Neusynchronisation', libraryIndexDeltaSync: 'Delta-Synchronisation', libraryIndexExcludeServer: 'Von Synchronisation ausschließen', + libraryIndexExcludingServer: 'Wird ausgeschlossen…', libraryIndexExcludedTitle: 'Von Synchronisation ausgeschlossen', libraryIndexIncludeServer: 'Wieder einschließen', libraryIndexStatus: 'Status', diff --git a/src/locales/en/settings.ts b/src/locales/en/settings.ts index 9d5910ba..49ba9cad 100644 --- a/src/locales/en/settings.ts +++ b/src/locales/en/settings.ts @@ -264,6 +264,7 @@ export const settings = { libraryIndexFullResync: 'Full resync', libraryIndexDeltaSync: 'Delta sync', libraryIndexExcludeServer: 'Exclude from sync', + libraryIndexExcludingServer: 'Excluding…', libraryIndexExcludedTitle: 'Excluded from sync', libraryIndexIncludeServer: 'Include again', libraryIndexStatus: 'Status', diff --git a/src/locales/es/settings.ts b/src/locales/es/settings.ts index f101ba95..955e08fe 100644 --- a/src/locales/es/settings.ts +++ b/src/locales/es/settings.ts @@ -259,6 +259,7 @@ export const settings = { libraryIndexFullResync: 'Resincronización completa', libraryIndexDeltaSync: 'Sincronización delta', libraryIndexExcludeServer: 'Excluir de la sincronización', + libraryIndexExcludingServer: 'Excluyendo…', libraryIndexExcludedTitle: 'Excluidos de la sincronización', libraryIndexIncludeServer: 'Incluir de nuevo', libraryIndexStatus: 'Estado', diff --git a/src/locales/fr/settings.ts b/src/locales/fr/settings.ts index 575f3348..1c3e546a 100644 --- a/src/locales/fr/settings.ts +++ b/src/locales/fr/settings.ts @@ -257,6 +257,7 @@ export const settings = { libraryIndexFullResync: 'Resynchronisation complète', libraryIndexDeltaSync: 'Synchronisation delta', libraryIndexExcludeServer: 'Exclure de la synchronisation', + libraryIndexExcludingServer: 'Exclusion…', libraryIndexExcludedTitle: 'Exclus de la synchronisation', libraryIndexIncludeServer: 'Réinclure', libraryIndexStatus: 'État', diff --git a/src/locales/nb/settings.ts b/src/locales/nb/settings.ts index 39f94da1..63e8dd91 100644 --- a/src/locales/nb/settings.ts +++ b/src/locales/nb/settings.ts @@ -256,6 +256,7 @@ export const settings = { libraryIndexFullResync: 'Full resynkronisering', libraryIndexDeltaSync: 'Delta-synkronisering', libraryIndexExcludeServer: 'Ekskluder fra synkronisering', + libraryIndexExcludingServer: 'Ekskluderer…', libraryIndexExcludedTitle: 'Ekskludert fra synkronisering', libraryIndexIncludeServer: 'Inkluder igjen', libraryIndexStatus: 'Status', diff --git a/src/locales/nl/settings.ts b/src/locales/nl/settings.ts index a1816bc5..dcbf7c78 100644 --- a/src/locales/nl/settings.ts +++ b/src/locales/nl/settings.ts @@ -257,6 +257,7 @@ export const settings = { libraryIndexFullResync: 'Volledige resync', libraryIndexDeltaSync: 'Delta-sync', libraryIndexExcludeServer: 'Uitsluiten van synchronisatie', + libraryIndexExcludingServer: 'Uitsluiten…', libraryIndexExcludedTitle: 'Uitgesloten van synchronisatie', libraryIndexIncludeServer: 'Weer opnemen', libraryIndexStatus: 'Status', diff --git a/src/locales/ro/settings.ts b/src/locales/ro/settings.ts index ed051bb0..a8d2f356 100644 --- a/src/locales/ro/settings.ts +++ b/src/locales/ro/settings.ts @@ -263,6 +263,7 @@ export const settings = { libraryIndexFullResync: 'Resincronizare completă', libraryIndexDeltaSync: 'Sincronizare delta', libraryIndexExcludeServer: 'Exclude din sincronizare', + libraryIndexExcludingServer: 'Se exclude…', libraryIndexExcludedTitle: 'Excluse din sincronizare', libraryIndexIncludeServer: 'Include din nou', libraryIndexStatus: 'Stare', diff --git a/src/locales/ru/settings.ts b/src/locales/ru/settings.ts index 872b170f..bc51b323 100644 --- a/src/locales/ru/settings.ts +++ b/src/locales/ru/settings.ts @@ -269,6 +269,7 @@ export const settings = { libraryIndexFullResync: 'Полная пересинхронизация', libraryIndexDeltaSync: 'Быстрая дельта', libraryIndexExcludeServer: 'Исключить из синхронизации', + libraryIndexExcludingServer: 'Отключается…', libraryIndexExcludedTitle: 'Исключены из синхронизации', libraryIndexIncludeServer: 'Включить снова', libraryIndexStatus: 'Статус', diff --git a/src/locales/zh/settings.ts b/src/locales/zh/settings.ts index 712eb6d6..e08d7fc6 100644 --- a/src/locales/zh/settings.ts +++ b/src/locales/zh/settings.ts @@ -256,6 +256,7 @@ export const settings = { libraryIndexFullResync: '完全重新同步', libraryIndexDeltaSync: '增量同步', libraryIndexExcludeServer: '排除同步', + libraryIndexExcludingServer: '正在排除…', libraryIndexExcludedTitle: '已排除同步', libraryIndexIncludeServer: '重新纳入', libraryIndexStatus: '状态', diff --git a/src/utils/library/librarySyncQueue.ts b/src/utils/library/librarySyncQueue.ts index c45cb469..6aede145 100644 --- a/src/utils/library/librarySyncQueue.ts +++ b/src/utils/library/librarySyncQueue.ts @@ -62,6 +62,25 @@ function waitForServerIdle(serverId: string): Promise { }); } +/** Wait until a server emits `library:sync-idle`, or time out (best-effort). */ +export function waitForLibrarySyncIdle(serverId: string, timeoutMs = 15_000): Promise { + return new Promise(resolve => { + let unlisten: (() => void) | undefined; + const timer = setTimeout(() => { + unlisten?.(); + resolve(); + }, timeoutMs); + void subscribeLibrarySyncIdle(p => { + if (p.serverId !== serverId) return; + clearTimeout(timer); + unlisten?.(); + resolve(); + }).then(fn => { + unlisten = fn; + }); + }); +} + async function invokeSync(serverId: string, kind: LibrarySyncQueueKind): Promise { if (kind === 'verify') { await librarySyncVerifyIntegrity({ serverId }); From 376edbfe1808ced758651ef06a6363e67c548360 Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Fri, 22 May 2026 14:16:19 +0300 Subject: [PATCH 2/3] fix(settings): show including state on library index include action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the exclude UX: flushSync before bootstrap, block repeat clicks, show "Including…" on the row, and roll back exclusion if bind fails. --- .../settings/LibraryIndexSection.tsx | 57 ++++++++++++++----- .../settings/LibraryIndexServerRow.tsx | 5 ++ src/locales/de/settings.ts | 1 + src/locales/en/settings.ts | 1 + src/locales/es/settings.ts | 1 + src/locales/fr/settings.ts | 1 + src/locales/nb/settings.ts | 1 + src/locales/nl/settings.ts | 1 + src/locales/ro/settings.ts | 1 + src/locales/ru/settings.ts | 1 + src/locales/zh/settings.ts | 1 + 11 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/components/settings/LibraryIndexSection.tsx b/src/components/settings/LibraryIndexSection.tsx index ceda5e13..01ba34e7 100644 --- a/src/components/settings/LibraryIndexSection.tsx +++ b/src/components/settings/LibraryIndexSection.tsx @@ -60,6 +60,7 @@ export default function LibraryIndexSection() { const [progressByServer, setProgressByServer] = useState>({}); const [busyServerId, setBusyServerId] = useState(null); const [excludingServerId, setExcludingServerId] = useState(null); + const [includingServerId, setIncludingServerId] = useState(null); const [bootstrapping, setBootstrapping] = useState(false); const pollTimer = useRef | null>(null); @@ -133,6 +134,7 @@ export default function LibraryIndexSection() { setProgressByServer({}); setBusyServerId(null); setExcludingServerId(null); + setIncludingServerId(null); return; } void runBootstrap(); @@ -251,22 +253,38 @@ export default function LibraryIndexSection() { }; const handleIncludeServer = async (serverId: string) => { - setServerSyncExcluded(serverId, false); + if (includingServerId || excludingServerId) return; const srv = servers.find(s => s.id === serverId); - if (srv) { - setBootstrapping(true); - try { - const result = await bootstrapIndexedServer(srv); - applyConnectionResults({ [serverId]: result }); - await refreshAllStatuses(); - } finally { - setBootstrapping(false); + if (!srv) return; + flushSync(() => { + setIncludingServerId(serverId); + setServerSyncExcluded(serverId, false); + }); + try { + const result = await bootstrapIndexedServer(srv); + applyConnectionResults({ [serverId]: result }); + if (result === 'error') { + setServerSyncExcluded(serverId, true); + showToast(t('settings.libraryIndexBindError', { error: t('settings.libraryIndexStatusError') }), 5000, 'error'); + return; } + try { + const fresh = await libraryGetStatus(serverId); + syncPhaseRef.current[serverId] = fresh.syncPhase; + setStatusByServer(prev => ({ ...prev, [serverId]: fresh })); + } catch { + /* status poll is best-effort */ + } + } catch (e) { + setServerSyncExcluded(serverId, true); + showToast(t('settings.libraryIndexBindError', { error: String(e) }), 5000, 'error'); + } finally { + setIncludingServerId(null); } }; const handleExcludeServer = async (serverId: string) => { - if (excludingServerId) return; + if (excludingServerId || includingServerId) return; flushSync(() => setExcludingServerId(serverId)); try { const syncing = @@ -316,7 +334,8 @@ export default function LibraryIndexSection() { } }; - const globalBusy = bootstrapping || busyServerId != null || excludingServerId != null; + const globalBusy = + bootstrapping || busyServerId != null || excludingServerId != null || includingServerId != null; return ( void handleMasterToggle(e.target.checked)} /> @@ -373,9 +392,12 @@ export default function LibraryIndexSection() { connection={connectionByServer[srv.id] ?? 'unknown'} progressLabel={progressByServer[srv.id] ?? null} busy={busyServerId === srv.id} + including={includingServerId === srv.id} excluding={excludingServerId === srv.id} actionsDisabled={ - (globalBusy && busyServerId !== srv.id) || excludingServerId != null + (globalBusy && busyServerId !== srv.id) + || excludingServerId != null + || includingServerId != null } onFullSync={() => void runServerAction(srv.id, 'full')} onDeltaSync={() => void runServerAction(srv.id, 'delta')} @@ -403,10 +425,15 @@ export default function LibraryIndexSection() { type="button" className="btn btn-surface" style={{ fontSize: 12, padding: '4px 10px' }} - disabled={bootstrapping || excludingServerId != null} + disabled={ + includingServerId != null || excludingServerId != null + } + aria-busy={includingServerId === srv.id} onClick={() => void handleIncludeServer(srv.id)} > - {t('settings.libraryIndexIncludeServer')} + {includingServerId === srv.id + ? t('settings.libraryIndexIncludingServer') + : t('settings.libraryIndexIncludeServer')} ))} diff --git a/src/components/settings/LibraryIndexServerRow.tsx b/src/components/settings/LibraryIndexServerRow.tsx index 38995336..0bf613c4 100644 --- a/src/components/settings/LibraryIndexServerRow.tsx +++ b/src/components/settings/LibraryIndexServerRow.tsx @@ -18,6 +18,7 @@ interface LibraryIndexServerRowProps { connection: LibraryServerConnection; progressLabel: string | null; busy: boolean; + including: boolean; excluding: boolean; actionsDisabled: boolean; onFullSync: () => void; @@ -34,6 +35,7 @@ export default function LibraryIndexServerRow({ connection, progressLabel, busy, + including, excluding, actionsDisabled, onFullSync, @@ -93,6 +95,9 @@ export default function LibraryIndexServerRow({ {busy && ( {t('settings.libraryIndexServerSyncing')} )} + {including && !busy && ( + {t('settings.libraryIndexIncludingServer')} + )}
{phaseLabel} diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index d01b26ab..ecfa21d6 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -264,6 +264,7 @@ export const settings = { libraryIndexExcludingServer: 'Wird ausgeschlossen…', libraryIndexExcludedTitle: 'Von Synchronisation ausgeschlossen', libraryIndexIncludeServer: 'Wieder einschließen', + libraryIndexIncludingServer: 'Wird eingeschlossen…', libraryIndexStatus: 'Status', libraryIndexStatusIdle: 'Bereit', libraryIndexStatusProbing: 'Server wird geprüft…', diff --git a/src/locales/en/settings.ts b/src/locales/en/settings.ts index 49ba9cad..1bede627 100644 --- a/src/locales/en/settings.ts +++ b/src/locales/en/settings.ts @@ -267,6 +267,7 @@ export const settings = { libraryIndexExcludingServer: 'Excluding…', libraryIndexExcludedTitle: 'Excluded from sync', libraryIndexIncludeServer: 'Include again', + libraryIndexIncludingServer: 'Including…', libraryIndexStatus: 'Status', libraryIndexStatusIdle: 'Idle', libraryIndexStatusProbing: 'Checking server…', diff --git a/src/locales/es/settings.ts b/src/locales/es/settings.ts index 955e08fe..7d64187c 100644 --- a/src/locales/es/settings.ts +++ b/src/locales/es/settings.ts @@ -262,6 +262,7 @@ export const settings = { libraryIndexExcludingServer: 'Excluyendo…', libraryIndexExcludedTitle: 'Excluidos de la sincronización', libraryIndexIncludeServer: 'Incluir de nuevo', + libraryIndexIncludingServer: 'Incluyendo…', libraryIndexStatus: 'Estado', libraryIndexStatusIdle: 'Inactivo', libraryIndexStatusProbing: 'Comprobando servidor…', diff --git a/src/locales/fr/settings.ts b/src/locales/fr/settings.ts index 1c3e546a..56488e97 100644 --- a/src/locales/fr/settings.ts +++ b/src/locales/fr/settings.ts @@ -260,6 +260,7 @@ export const settings = { libraryIndexExcludingServer: 'Exclusion…', libraryIndexExcludedTitle: 'Exclus de la synchronisation', libraryIndexIncludeServer: 'Réinclure', + libraryIndexIncludingServer: 'Réinclusion…', libraryIndexStatus: 'État', libraryIndexStatusIdle: 'Inactif', libraryIndexStatusProbing: 'Vérification du serveur…', diff --git a/src/locales/nb/settings.ts b/src/locales/nb/settings.ts index 63e8dd91..ac836c86 100644 --- a/src/locales/nb/settings.ts +++ b/src/locales/nb/settings.ts @@ -259,6 +259,7 @@ export const settings = { libraryIndexExcludingServer: 'Ekskluderer…', libraryIndexExcludedTitle: 'Ekskludert fra synkronisering', libraryIndexIncludeServer: 'Inkluder igjen', + libraryIndexIncludingServer: 'Inkluderer…', libraryIndexStatus: 'Status', libraryIndexStatusIdle: 'Inaktiv', libraryIndexStatusProbing: 'Sjekker server…', diff --git a/src/locales/nl/settings.ts b/src/locales/nl/settings.ts index dcbf7c78..82564ebc 100644 --- a/src/locales/nl/settings.ts +++ b/src/locales/nl/settings.ts @@ -260,6 +260,7 @@ export const settings = { libraryIndexExcludingServer: 'Uitsluiten…', libraryIndexExcludedTitle: 'Uitgesloten van synchronisatie', libraryIndexIncludeServer: 'Weer opnemen', + libraryIndexIncludingServer: 'Opnemen…', libraryIndexStatus: 'Status', libraryIndexStatusIdle: 'Inactief', libraryIndexStatusProbing: 'Server controleren…', diff --git a/src/locales/ro/settings.ts b/src/locales/ro/settings.ts index a8d2f356..2be0cf8a 100644 --- a/src/locales/ro/settings.ts +++ b/src/locales/ro/settings.ts @@ -266,6 +266,7 @@ export const settings = { libraryIndexExcludingServer: 'Se exclude…', libraryIndexExcludedTitle: 'Excluse din sincronizare', libraryIndexIncludeServer: 'Include din nou', + libraryIndexIncludingServer: 'Se include…', libraryIndexStatus: 'Stare', libraryIndexStatusIdle: 'Inactiv', libraryIndexStatusProbing: 'Se verifică serverul…', diff --git a/src/locales/ru/settings.ts b/src/locales/ru/settings.ts index bc51b323..3be265ca 100644 --- a/src/locales/ru/settings.ts +++ b/src/locales/ru/settings.ts @@ -272,6 +272,7 @@ export const settings = { libraryIndexExcludingServer: 'Отключается…', libraryIndexExcludedTitle: 'Исключены из синхронизации', libraryIndexIncludeServer: 'Включить снова', + libraryIndexIncludingServer: 'Подключается…', libraryIndexStatus: 'Статус', libraryIndexStatusIdle: 'Ожидание', libraryIndexStatusProbing: 'Проверка сервера…', diff --git a/src/locales/zh/settings.ts b/src/locales/zh/settings.ts index e08d7fc6..49b1ca55 100644 --- a/src/locales/zh/settings.ts +++ b/src/locales/zh/settings.ts @@ -259,6 +259,7 @@ export const settings = { libraryIndexExcludingServer: '正在排除…', libraryIndexExcludedTitle: '已排除同步', libraryIndexIncludeServer: '重新纳入', + libraryIndexIncludingServer: '正在纳入…', libraryIndexStatus: '状态', libraryIndexStatusIdle: '空闲', libraryIndexStatusProbing: '正在检查服务器…', From 3825b0176906c2f8253e143d66e4fce63fbd504e Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Fri, 22 May 2026 14:19:32 +0300 Subject: [PATCH 3/3] docs(release): CHANGELOG and credits for library index busy UI (PR #850) --- CHANGELOG.md | 8 ++++++++ src/config/settingsCredits.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4266ee..791494d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +### Settings — local library index exclude/include feedback + +**By [@cucadmuh](https://github.com/cucadmuh), PR [#850](https://github.com/Psychotoxical/psysonic/pull/850)** + +* **Settings → Library:** **Exclude from sync** and **Include again** show immediate busy labels and block repeat clicks while bind/unbind runs; exclude cancels an in-flight sync first. + + + ## [1.46.0] - 2026-05-18 > **🙏 Special thanks to [@zz5zz](https://github.com/zz5zz)** for his tireless quirk-spotting and bug reports on the [Psysonic Discord](https://discord.gg/AMnDRErm4u) — several of the polish fixes in this release landed directly off the back of his messages. diff --git a/src/config/settingsCredits.ts b/src/config/settingsCredits.ts index aa3d1855..72c22859 100644 --- a/src/config/settingsCredits.ts +++ b/src/config/settingsCredits.ts @@ -125,6 +125,7 @@ const CONTRIBUTOR_ENTRIES = [ 'Local library index: multi-server settings UI, serial sync queue, music-library-scoped local search, parallel initial ingest, i18n across 9 locales (PR #846)', 'Library browse: local-vs-network text search race, All Albums/Artists catalog from index, DevTools browse-race logging (PR #847)', 'Player stats: local listening history tab with heatmap, year summary, recent days, and day drill-down (PR #849)', + 'Settings → Library: exclude/include index buttons show busy state and block repeat clicks (PR #850)', ], }, {