diff --git a/fe/src/__tests__/AddNewView.spec.ts b/fe/src/__tests__/AddNewView.spec.ts index d0a82fd3b..551141435 100644 --- a/fe/src/__tests__/AddNewView.spec.ts +++ b/fe/src/__tests__/AddNewView.spec.ts @@ -768,4 +768,21 @@ describe('AddNewView pagination', () => { expect(addBtn.text()).toContain('Added') expect(addBtn.attributes('disabled')).toBeDefined() }) + + // Regression: cold-add "root folder not configured" false negative. + // The addToLibrary() guard reads rootFoldersStore.folders synchronously, so + // the store must be populated on mount; otherwise the first add after a fresh + // page load wrongly redirects to /settings. + it('loads root folders on mount so the cold first add is not falsely rejected', async () => { + const apiModule = await import('@/services/api') + const getRootFolders = apiModule.apiService.getRootFolders as unknown as Mock + getRootFolders.mockClear() + getRootFolders.mockResolvedValue([{ id: 1, name: 'Books', path: '/books', isDefault: true }]) + + const router = createTestRouter() + mount(AddNewView, { global: { plugins: [createPinia(), router] } }) + await flushPromises() + + expect(getRootFolders).toHaveBeenCalled() + }) }) diff --git a/fe/src/stores/rootFolders.ts b/fe/src/stores/rootFolders.ts index ecab77a49..4f2f69069 100644 --- a/fe/src/stores/rootFolders.ts +++ b/fe/src/stores/rootFolders.ts @@ -38,7 +38,9 @@ export const useRootFoldersStore = defineStore('rootFolders', () => { } } catch (err) { logger.debug('Failed to load root folders:', err) - folders.value = [] + // Preserve any previously-loaded folders on transient failure: a failed + // reload must not masquerade as "no root folders configured" (folders + // starts as [], so a first-ever failure still yields an empty list). } finally { loading.value = false } diff --git a/fe/src/views/content/AddNewView.vue b/fe/src/views/content/AddNewView.vue index 40f6c77ce..c0fbd7e6f 100644 --- a/fe/src/views/content/AddNewView.vue +++ b/fe/src/views/content/AddNewView.vue @@ -2998,6 +2998,11 @@ onMounted(async () => { await configStore.loadApplicationSettings() await configStore.loadApiConfigurations() + // Ensure root folders are loaded before addToLibrary()'s guard reads them. + // Without this, the first add after a cold page load sees an empty store and + // falsely reports "Root folder not configured" until a redirect populates it. + if (rootFoldersStore.folders.length === 0) await rootFoldersStore.load() + const defaultRegion = normalizeSearchRegion(configStore.applicationSettings?.defaultSearchRegion) const defaultLanguage = normalizePreferredSearchLanguage( configStore.applicationSettings?.defaultSearchLanguage, diff --git a/fe/src/views/library/AudiobookDetailView.vue b/fe/src/views/library/AudiobookDetailView.vue index 14dbfc297..c00deac61 100644 --- a/fe/src/views/library/AudiobookDetailView.vue +++ b/fe/src/views/library/AudiobookDetailView.vue @@ -1171,6 +1171,10 @@ onMounted(async () => { syncActiveTabFromRoute() document.addEventListener('click', handleClickOutside) + // The path preview falls back to rootFoldersStore.defaultFolder; load it so a + // cold page load shows the correct destination rather than the legacy outputPath. + if (rootFoldersStore.folders.length === 0) await rootFoldersStore.load() + await loadAudiobook() // subscribe to scan job updates diff --git a/fe/src/views/library/AudiobooksView.vue b/fe/src/views/library/AudiobooksView.vue index f7b7fa4ec..ddbf663d6 100644 --- a/fe/src/views/library/AudiobooksView.vue +++ b/fe/src/views/library/AudiobooksView.vue @@ -1911,6 +1911,9 @@ onMounted(async () => { libraryStore.fetchLibrary(), configStore.loadApplicationSettings(), loadQualityProfiles(), + // hasRootFolderConfigured gates the empty-state CTA on this store; load it + // so an empty library doesn't falsely show "Root Folder Not Configured". + rootFoldersStore.folders.length === 0 ? rootFoldersStore.load() : Promise.resolve(), ]) // Load persisted view mode (if available) before layout calc