- storage.ts: Centralize ALL_LIBRARIES = "__all__" sentinel constant.
setSelectedLibrary() now writes both localStorage and a cookie
(selectedLibrary, Path=/, SameSite=Lax, max-age=365d). The
sentinel "__all__" is used in both storage mediums — empty strings
are never stored. getSelectedLibrary() maps __all__ back to "".
Cookie enables server-side rendering to read the stored library
selection without access to localStorage.
- library-switcher.ts: Import ALL_LIBRARIES and setSelectedLibrary/
getSelectedLibrary from storage.ts instead of managing localStorage
directly. Remove local constants.
- dashboard.ts: Remove duplicate localStorage.setItem call that was
overwriting the __all__ sentinel with raw empty string. Fix
reloadPage() and scan-complete handler to work with empty libraryId.
openDashboardSettings/saveDashboardSettings show clear messages for
All Libraries mode.
- collections.ts: Remove library switcher initialization from the
collections list page — the list page no longer has a switcher.
- series.ts: Rewrite to use initLibrarySwitcher from library-switcher
module and switchWithTransition for navigation. Series card links
no longer include library_id in their URLs.
- bookshelf.ts: Autocomplete fetch calls handle empty libraryId
correctly for All Libraries mode.
- search.ts, collection-rules.ts: Use setSelectedLibrary() and
getSelectedLibrary() from storage.ts instead of direct localStorage
access.
Wire up the shared library switcher module on dashboard, collections
list, collection detail, and collection rules pages. All pages use SSR
for initial load and AJAX with fade transitions on library switch.
web/src/collections.ts:
- Add initCollectionsPage() that auto-detects list vs detail page
by checking for #collection-data element
- Collections list: onSwitch fetches /api/collections?library_id=X and
re-renders the grid with per-library book counts
- Collection detail: onSwitch fetches /api/collections/:id?library_id=X
and re-renders the books grid
- Add renderCollectionsGrid() and renderCollectionBooks() with
Alpine.initTree() calls for dynamic content
- Collection cards now link with ?library_id= from selected library
- Update hidden #collection-data data-library-id on switch
web/src/dashboard.ts:
- Replace standalone switchLibrary() with initLibrarySwitcher() +
switchWithTransition() from shared module
- Extract fetchAndRenderSections() helper shared by onSwitch callback,
reloadPage(), and saveDashboardSettings()
- Remove inline #library-select change listener and switch-library
data-action handler (now handled by shared module)
- Scan-complete event handler unchanged (independent incremental logic)
web/src/collection-rules.ts:
- Update backToCollection() to preserve library context by appending
?library_id= from localStorage selectedLibrary key
- Wrap all Alpine.data() object literals in arrow functions (() => ({}))
- This fixes "n.bind is not a function" errors when Alpine initializes components
- Alpine.data() requires a factory function, not a plain object
- Ensures each component instance gets its own closure and proper this binding
Fixed 24 TypeScript files:
- admin.ts, analytics.ts, api.ts, api-explorer-docs.ts
- bookshelf.ts, collection-rules.ts, collections.ts, conflicts.ts
- device-management.ts, docs.ts, header.ts, index.ts
- library.ts, linking.ts, login.ts, password_validation.ts
- profile-modal.ts, profile.ts, queue.ts, register.ts
- search.ts, theme.ts, toast-error.ts, toast.ts, unlinked_books.ts
Before: Alpine.data("name", { method1, method2 })
After: Alpine.data("name", () => ({ method1, method2 }))
This is a critical fix for Alpine.js v3+ where components must be
registered as factory functions to ensure proper reactivity and
prevent binding errors during initialization.