Search results navigated to /bookshelf with no filters instead of the
selected book's page. Results now link to /media/:id and display cover
thumbnails, with cover URLs resolved server-side via ResolveMediaURL.
Removes the dead selectedBook localStorage plumbing.
On viewports below 970px the header now collapses to a compact bar with
only the Bookhoard logo and a hamburger button. Clicking the hamburger
reveals a slide-down panel containing:
- Full-text search input (wired to the existing debounced search API)
- Navigation links (Library, All Books, Series, Collections, Progress, Devices)
- Collapsible theme switcher with all 7 themes and 4 bookshelf backgrounds
- User section: profile/admin/logout when logged in, inline login form when logged out
Changes:
- templates/header.templ: add hamburger button, mobile panel with all controls,
hide desktop search/theme/user controls below nav breakpoint
- web/src/header.ts: add mobileMenuOpen state to Alpine header component
- web/src/search.ts: refactor initializeSearch to wire both desktop and mobile
search inputs, track active input for results container placement
- tailwind.config.ts: add custom 'nav' screen breakpoint at 970px so the
mobile menu activates before the search bar becomes unusable
- web/static/style.css: rebuilt with new nav breakpoint utility classes
- 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.
Since main.js has 'defer' attribute, the DOM is guaranteed to be
ready when modules execute. These wrappers are unnecessary.
dashboard.ts:
- Removed DOMContentLoaded wrapper, code runs directly
- Event delegation setup runs immediately
custom-section-builder.ts:
- Removed DOMContentLoaded wrapper
- initCustomSectionBuilder() called directly
toast.ts:
- Removed DOMContentLoaded wrapper
- initializeToastSystem() called directly at top level
- Removed dead Alpine.data registration (unused)
search.ts:
- Removed DOMContentLoaded wrapper
- initializeSearch exported for use in header
theme.ts:
- Removed DOMContentLoaded wrapper
- Functions now exported for use in header Alpine component
- 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.
Added Alpine.global() registration to enable template access to functions:
- admin.ts: Added Alpine for scan, stats, and settings functions
- api-explorer.ts: Already had Alpine (kept as is)
- bookshelf.ts: Added Alpine for library/bookshelf interactions
- collections.ts: Added Alpine for collection management
- conflicts.ts: Added Alpine for conflict resolution
- device-management.ts: Added Alpine with event delegation for dynamic content
- header.ts: Added Alpine for theme dropdown and user menu
- library.ts: Added Alpine registrations
- linking.ts: Added Alpine registrations
- queue.ts: Added Alpine for queue operations
- search.ts: Added Alpine registrations
- themeDropdown.ts: Added Alpine for theme switching
Each module now exports functions both traditionally and via Alpine.global() for template access.
Features:
- Complete dashboard redesign with improved UI components and layout
- Implement custom section builder for personalized book organization
- Add new events tracking system for user interactions
- Enhance search functionality with better static search.js
- Update TypeScript type definitions for API responses
Backend:
- Update Go dependencies in go.mod
- Add new frontend routes in router
Templates:
- Update admin and dashboard templates with new components
Frontend:
- Refactor analytics, collections, conflicts, and queue modules
- Add new documentation features in docs.ts
- Implement linking between books and collections
- Add toast notifications for user feedback
- Include placeholder book SVG asset
This commit consolidates multiple feature additions and improvements
across the entire stack including backend, templates, and frontend.