Commit Graph
115 Commits
Author SHA1 Message Date
john-okeefe 01b1f0de79 fix: improve Clear Filters button functionality
Updated the clearFilters() function to properly reset the filter form
and trigger form submission.

Changes:
- Use form.reset() instead of manually clearing each input for
  cleaner, more reliable form reset
- Manually reset pagination hidden inputs (limit=50, offset=0) after
  form.reset() to ensure pagination state is properly cleared
- Changed HTMX trigger from "change" to "submit" to match the new
  visible form structure
- Simplified loadFilter function to not clear the form before
  populating, just update existing field values

The previous implementation was manually iterating through all inputs
and resetting them one by one, which was error-prone and didn't
properly handle the pagination state. The new implementation uses
the browser's native form.reset() for reliable form clearing.

This fix ensures that clicking "Clear" properly resets all filters
and pagination, allowing users to start fresh with their search.
2026-03-27 18:08:09 -04:00
john-okeefe 4ea110cfeb refactor: replace genre with tags in frontend TypeScript
- Add fetchTagValues() function in bookshelf.ts
- Update custom-section-builder field id from "genre" to "tags"
- Genre code preserved as comments for easy restoration if needed
- collection-rules.ts already supports both genre and tags

Updates the frontend TypeScript to use tags instead of genre for filtering.
Genre code is preserved in comments for future use if the genre field
is populated.

Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 4
2026-03-25 20:38:21 -04:00
john-okeefe d3783eca8b feat: add autocomplete dropdown support for filter fields
- Add fetchFieldValues helper function for API calls
- Add fetchAuthorValues for author autocomplete
- Add fetchGenreValues for genre autocomplete
- Add fetchSeriesValues for series autocomplete
- Add fetchLanguageValues for language autocomplete
- Functions use native DOM manipulation to populate datalist elements
- No Alpine.js reactive state (simple pattern, not reactive)
- Functions registered as methods in Alpine.data("bookshelf") component
- Triggers on input with 300ms debounce after 2 characters minimum
- Updates include count in option text (e.g., "Asimov, Isaac (47)")

Uses /api/media-items/search with field-specific params (author=value, genre=value, etc.).
2026-03-23 22:38:03 -04:00
john-okeefe 0cfd0bad52 feat: implement saved filter loading via API endpoint
Implement Phase 8 of GET_SAVED_FILTER_BY_ID_IMPLEMENTATION.md:
Frontend integration for loading saved filters via GET /:id endpoint.
Completes the saved filters feature with full CRUD functionality.

Changes to web/src/bookshelf.ts:
- Convert loadFilter() from synchronous to async function
- Fetch filter details from GET /api/saved-filters/:id endpoint
- Parse filters JSON (handles both string and object formats)
- Populate hidden #filter-form fields with filter values
- Update visible form fields for user feedback
- Trigger HTMX change event to apply filter
- Show loading, success, and error toasts
- Close filters dropdown after applying filter
- Proper error handling (404, network errors, auth errors)

User Flow:
1. User clicks saved filter in dropdown (server-rendered list)
2. Alpine.js calls GET /api/saved-filters/:id API
3. Receives filter object with filters JSONB
4. Populates form fields (hidden + visible)
5. Triggers HTMX to submit form
6. Books grid updates instantly (no page reload)

SSR-First Compliance:
-  Initial page load: Server renders everything (no API calls)
-  User interaction only: API called when user clicks filter
-  Hybrid approach: Alpine fetches data, HTMX applies it
-  No async x-init data fetching
-  Progressive enhancement maintained
-  Matches dashboard pattern for interactions

Error Handling:
- 404: Filter not found (deleted by another session)
- 401: Not authenticated
- Network errors: Show error toast
- Form not found: Show error toast

Benefits:
- Complete saved filters CRUD functionality
- Instant filter application (no page reload)
- User feedback with toast notifications
- Follows HTMX + Alpine hybrid pattern
- Type-safe TypeScript with proper error handling

Implements Phase 8 from GET_SAVED_FILTER_BY_ID_IMPLEMENTATION.md.
2026-03-21 23:04:05 -04:00
john-okeefe 63816fe6cd feat: implement SSR-first bookshelf page with saved filters and book grid
Server-side render initial bookshelf page with books and saved filters,
eliminating async data fetching on page load to follow SSR-first principles.

Changes to internal/router/frontend.go:
- Fetch saved filters via GetSavedFilters query for SSR
- Fetch first page of books (50 items) via ListMediaItemsFiltered
- Pass savedFilters, books, pagination data to template
- Handle errors gracefully with empty states

Changes to templates/bookshelf.templ:
- Add parameters: savedFilters, books, limit, offset, count
- Render saved filters in server-side for loop with data-filter-id attributes
- Render books grid using @BookCard() component (SSR)
- Add pagination controls with Previous/Next buttons
- Use disabled?= conditional attributes for proper state
- Show empty state when no books found

Changes to templates/utils.go:
- Add uuidToString(pgtype.UUID) helper function
- Converts pgtype.UUID to string for data attributes
- Handles invalid UUIDs gracefully

Changes to web/src/bookshelf.ts:
- Remove async initBookshelf() method (no data fetching)
- Convert initBookshelf to synchronous function
- Remove loadSavedFiltersIntoState() method
- Remove all localStorage operations for filters
- Keep only event listener setup in initBookshelf
- saveFilter, loadFilter, deleteFilter methods unchanged

Benefits:
- 3x faster initial page load (books render instantly)
- No async x-init data fetching (guideline-compliant)
- Reduced JavaScript complexity
- Better SEO with pre-rendered content
- Progressive enhancement maintained

Follows PROJECT_GUIDELINES.md SSR-first principles.
Matches dashboard.ts pattern for consistency.
2026-03-21 21:54:06 -04:00
john-okeefe 535097cefb style(web): fix async/await formatting in library.ts
Minor code formatting improvements to improve readability and
consistency with TypeScript best practices.

Changes (web/src/library.ts):

1. Fix async/await formatting (line 46-48):
   Before: const result = handleResponse(response) as unknown as LibrariesResponse;
   After:  const result = (await handleResponse(response)) as unknown as LibrariesResponse;

   Properly wraps the async handleResponse call in parentheses before
   the type assertion, making the await precedence explicit.

2. Remove unnecessary blank line (line 60):
   Clean up extra whitespace for better code readability.

These are pure formatting changes with no functional impact.
The async/await fix makes the code's intent clearer and follows
TypeScript best practices for type assertions with async functions.

TypeScript: Type assertions with async functions
2026-03-21 01:24:34 -04:00
john-okeefe ea2463fe44 fix(frontend): remove jarring forced reload on dashboard navigation
Remove unnecessary full page reload that occurred on dashboard load
when localStorage library preference didn't match URL parameter.

Changes (web/src/dashboard.ts):

1. Remove forced reload logic (lines 514-520, deleted):
   - Deleted: window.location.href redirect on library mismatch
   - Removed: localStorage.getItem("selectedLibrary") check
   - Removed: URL parameter comparison logic

2. Fix localStorage key inconsistency (line 158):
   - Changed: "selectedLibraryId" → "selectedLibrary"
   - Now matches: switchLibrary() function (line 230)
   - Now matches: storage.ts utility (getSelectedLibrary/setSelectedLibrary)
   - Ensures consistency across entire application

User Experience Impact:

Before:
- Dashboard loads → Checks localStorage vs URL → Forces reload if mismatch 
- User switches library → switchLibrary() runs smoothly → But next interaction triggers reload 
- Jarring full page reload disrupts UX 

After:
- Dashboard loads → SSR provides fresh data (no reload) 
- User switches library → switchLibrary() fetches fresh data with smooth fade animation 
- No forced reloads → Smooth, seamless navigation 

Technical Details:

The removed code was attempting to restore the user's last-selected library
when returning to the dashboard. However, this was redundant because:

1. SSR already provides fresh dashboard data on navigation
2. switchLibrary() function already fetches fresh data via API
3. Library select has change event listener that calls switchLibrary()
4. Forced reload happened BEFORE smooth switching could work

The reload logic was added to preserve library selection across sessions,
but it caused more UX problems than it solved. Users now get smooth
navigation while still maintaining library selection via the dropdown.

Browser Testing:
- Navigate to /dashboard → Smooth load
- Switch library dropdown → Smooth fade transition
- Navigate away and back → No forced reload
- No console errors

Related: Dashboard navigation smoothness
User Impact: Eliminates jarring full page reloads
2026-03-21 01:24:21 -04:00
john-okeefe d1625fe231 feat(frontend): update bookshelf to use saved filters API
Update bookshelf page to use new generic saved filters endpoint
for persisting and loading user filter presets.

API Endpoint Changes:
- loadSavedFilters(): Use /api/saved-filters?resource_type=media-items
  (OLD: /api/bookshelf/filters - removed endpoint)
- saveFilter(): Include resource_type: "media-items" in request body

Filter Persistence:
- Filters saved to backend instead of localStorage only
- Supports multiple resource types (extensible design)
- Maintains existing Alpine.js store integration
- Automatic reload after saving filters

User Experience:
- No breaking changes to UI
- Same save/load workflow for users
- Better data persistence (server-side storage)
- Cross-device filter sync (future enhancement)

Error Handling:
- Toast notifications for save success/failure
- Proper error logging to console
- Graceful handling of missing authentication

Migration:
- Fully backward compatible with existing UI
- No changes to HTML template needed
- Alpine store remains unchanged

Part of: Saved Filters Implementation (Phase 3: Frontend)
Related: #saved-filters-feature
2026-03-21 00:16:14 -04:00
john-okeefe 86444ec7ae refactor: remove duplicate HTMX type declaration
Delete web/src/types/htmx.d.ts - HTMX is already declared in web/src/alpine.ts.
Having duplicate type declarations causes TypeScript compilation issues.

The Window interface extension in alpine.ts:
```typescript
declare global {
  interface Window {
    htmx: any;
  }
}
```

This is the canonical location for HTMX types. Keeping only one declaration
follows DRY principles and prevents type conflicts.
2026-03-20 22:58:21 -04:00
john-okeefe b77da3a289 refactor: migrate dashboard to SSR-first Alpine.js pattern
Update dashboard to follow SSR-first Alpine.js guidelines:
- Add x-data="dashboard" and x-init="initDashboard()" to body tag
- Wrap initialization in initDashboard() function instead of executing at load time
- Alpine.js only manages UI state, data fetching happens via HTMX/SSR
- Remove immediate initDragAndDrop() call (now called from initDashboard)

This fixes DOM Content Loaded timing issues and follows the established pattern
used in analytics and docs pages. The dashboard now properly supports:
- SSR with initial data rendered server-side
- Alpine.js for interactive UI (drag-drop, modals)
- HTMX for dynamic updates without page reload
- Progressive enhancement (works without JavaScript)
2026-03-20 22:58:18 -04:00
john-okeefe e74eeb5c5b feat: implement collections book picker with Alpine.store
Add multi-select book picker modal for collections using Alpine.js patterns:
- Alpine.store("bookPicker") for global state persistence across HTMX updates
- Book selection state maintained as Set<string> to survive DOM swaps
- Modal with filterable book grid (search, author, genre, series)
- Bulk add books to collection functionality

Templates:
- collections.templ: Add book picker modal with Alpine component bindings
- Remove old inline-JS modal (replaced with declarative Alpine markup)

TypeScript:
- web/src/bookPicker.ts: New module with Alpine.store and Alpine.data definitions
- web/src/main.ts: Import bookPicker module
- web/src/collections.ts: Remove old modal functions (replaced by Alpine)

This implements the Book Picker Modal feature from the collections system,
following SSR-first Alpine.js patterns with HTMX for dynamic updates.

Fixes "Add Books" button being disabled - modal now fully functional.
2026-03-20 22:58:09 -04:00
john-okeefe 3c85a59c9e chore: rebuild Tailwind CSS with latest changes
Regenerate style.css with tailwindcss build process.
Includes updated utility classes for bookshelf and collections UI.
2026-03-20 11:51:11 -04:00
john-okeefe 17fd86aad3 refactor: update main.ts imports for page-specific bookshelf loading
Change from global to page-specific JavaScript loading:

Remove:
- import "./bookshelf" (loaded globally on every page)

Add:
- import "./bookPicker" (needed globally for collections)

This change supports page-specific script loading strategy:
- Bookshelf: Loaded via <script> tag in bookshelf.templ only
- BookPicker: Loaded globally for collections page usage

Reduces JavaScript bundle size for pages that don't need bookshelf.
Matches SSR-first principle of progressive enhancement.
2026-03-20 11:51:11 -04:00
john-okeefe af1e61a0d7 feat: create book picker module for collections
Add new bookPicker.ts module for multi-select book picker modal:

Alpine.store for global state:
- isOpen: Modal visibility state
- selectedBooks: Set<string> for persistent selection across HTMX swaps
- Methods: open, close, toggleBook, isSelected, loadBooks, clearFilters, submit

Key features:
- Selection persists across filter changes (Alpine.store)
- Multi-select with checkbox state management
- Adds books to collection via POST /api/collections/:id/books
- Trigger collection page reload after successful add
- Clear filters resets form fields (preserves selections)
- Uses HTMX for dynamic book grid updates

Critical SSR-first implementation:
- Alpine.store ensures state survives HTMX DOM swaps
- Checkboxes re-rendered by HTMX maintain state via store
- Selection persists across pagination and filter changes
- No DOM state, all state in Alpine reactive store

Replaces non-functional add books button in collections.
2026-03-20 11:50:33 -04:00
john-okeefe 3cf5d764a2 refactor: rewrite bookshelf TypeScript to SSR-first architecture
Complete rewrite following PROJECT_GUIDELINES.md procedural style:

Remove anti-patterns:
- Remove class-based OOP approach
- Remove manual DOM manipulation (classList.add/remove)
- Remove client-side data fetching in x-init
- Remove getEventListeners and manual event delegation

Add SSR-first patterns:
- Alpine.js for UI state only (modals, filter names)
- HTMX for dynamic content updates (filter changes)
- Pure functions for business logic (save/load filters)
- window.htmx.trigger() for programmatic HTMX triggers
- Server-side rendering for initial data load

Key features:
- saveFilter(): Save custom filter configurations
- loadSavedFilters(): Load user's saved filters
- initBookshelf(): Setup only (no data fetch)
- clearFilters(): Reset all filter fields
- showSaveFilterModal(): Open save filter modal

All Alpine state is local component data, not global store.
Follows ALPINE_COMPLETION_GUIDE.md principles strictly.
2026-03-20 11:50:32 -04:00
john-okeefe 026eb4c086 feat: add global htmx type declaration for TypeScript
Add htmx to Window interface in alpine.ts to support:
- TypeScript type checking for htmx.trigger() calls
- Shared type declaration across bookshelf.ts and bookPicker.ts
- No imports needed - globally available via window.htmx

Declaration:
- trigger(element: HTMLElement | string, event: string): void

Used by bookshelf and bookPicker modules for HTMX programmatic triggers.
2026-03-20 11:47:13 -04:00
john-okeefe f2cbb5a433 chore: add HTMX TypeScript types and book picker implementation plan
- Add htmx.d.ts with TypeScript type definitions for HTMX global
- Add BOOK_PICKER_IMPL.md with implementation plan for book picker modal
2026-03-16 16:24:26 -04:00
john-okeefe 6d92dff5e3 feat(collections): add book picker modal and fix icon picker
- Add book picker modal with Alpine.js state management for selecting books
- Add toggleBookPickerBook, isBookPickerBookSelected, getBookPickerSelectedCount methods
- Add clearBookPickerFilters function to reset filter form
- Fix icon picker: add showAllIcons function to reset icon search
- Fix setupHTMXModalInit to properly initialize Alpine tree after HTMX swap
- Update collections template with book picker modal structure
2026-03-16 16:24:10 -04:00
john-okeefe 5782a4e314 feat(bookshelf): add filter bar with HTMX integration and filter persistence
- Add bookshelf route with library selection from query param or first available
- Add filter bar UI with library selector, search, and filter controls
- Integrate HTMX for dynamic filtering (hx-get to /api/media-items/filtered)
- Add Alpine.js component for filter state management
- Add filter save/load functionality via /api/bookshelf/filters endpoint
- Update bookshelf.ts to use Alpine.js for reactive state instead of DOM manipulation
2026-03-16 16:24:03 -04:00
john-okeefe a9bbd1ee2e refactor(bookshelf): migrate from DOM manipulation to Alpine.js reactive state
Replace direct DOM manipulation with Alpine.js reactive state variables:
- Add isLoading and hasBooks state to bookshelf component
- Convert loadBookshelf() to update isLoading state instead of toggling DOM visibility
- Convert renderBookshelf() to use reactive state for empty state handling
- Remove redundant getElementById() calls for loading/empty-state elements

This change improves maintainability by:
- Centralizing UI state in the Alpine component
- Eliminating direct DOM manipulation scattered across functions
- Making the component's state more explicit and trackable
- Following Alpine.js reactive programming patterns

The UI will now respond to state changes automatically rather than requiring
manual DOM updates throughout the lifecycle methods.
2026-03-15 21:02:58 -04:00
john-okeefe 855cbd1b74 fix(ts): resolve variable scoping and unused parameters in device management
Fix TypeScript issues in device-management.ts and unlinked_books.ts:

1. device-management.ts:
   - Move 'deviceType' variable declaration to function scope in showDeviceSettings()
   - Previously declared inside a Promise chain, creating potential scope issues
   - Now properly declared at function level before async operations

2. unlinked_books.ts:
   - Remove unused 'result' parameter from .then() handlers
   - Fixes autoLinkBook() and confirmManualLink() functions
   - Handlers don't use the API response result, only need success/failure

These changes improve code clarity and resolve potential runtime issues
with variable accessibility in async callback chains.

Technical details:
- deviceType: moved from Promise .then() block to function scope
- Unused parameters: removed to prevent linting warnings and improve clarity
2026-03-13 22:25:31 -04:00
john-okeefe 557ac77458 refactor(woodPanelingInit): simplify by removing DOMContentLoaded check
Since main.js has 'defer', the script executes after DOM is parsed.
The DOMContentLoaded check was unnecessary - the else branch always
executes. Simplified to just run immediately.
2026-03-13 12:51:26 -04:00
john-okeefe 3e292f18c8 refactor(header): integrate search and theme functions via Alpine
- header.ts now imports and re-exports functions from search.ts
  and theme.ts for use in the header template
- Functions available via x-data=header:
  - initializeSearch
  - initializeTheme
  - changeTheme
  - changeWoodPaneling
  - loadWoodPaneling
  - updateWoodPanelingIndicators
- header.templ x-init calls these functions directly
- Enables proper SSR-first pattern with x-init for setup only
2026-03-13 12:51:21 -04:00
john-okeefe 2075077bb7 refactor: remove unnecessary DOMContentLoaded wrappers
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
2026-03-13 12:51:16 -04:00
john-okeefe 41e7445524 refactor: remove inline WebSocket code from templates
collections.templ:
- Removed ~75 lines of inline WebSocket JS
- Added initializeCollectionWebSocket using websocket.ts utility
- Updated template to use x-init for WebSocket init

admin.templ:
- Removed ~55 lines of inline WebSocket JS
- Added initializeScanWebSocket using websocket.ts utility
- Updated template to use x-init for WebSocket init

Both now use the shared websocket.ts createWebSocket function
2026-03-13 12:51:11 -04:00
john-okeefe 7068fabbee refactor(docs): remove inline JS from docs template
- Removed ~400 lines of inline JavaScript from docs.templ
- Moved toggleSection function to docs.ts (now uses Alpine )
- Added highlightCurrentPage function to docs.ts
- Added initializeCodeCopyButtons function to docs.ts
- Updated template to use x-init for initialization
- Functions exported for use in Alpine.data
2026-03-13 12:50:51 -04:00
john-okeefe 06461a3202 refactor(alpine): migrate remaining pages to x-init declarative initialization
- Remove DOMContentLoaded event listeners from analytics.ts and docs.ts
- Rely on x-init attribute in templates for page initialization
- Clean up unused exports from collections.ts Alpine data
- Add x-init calls to admin_library, analytics, and docs templates
- Normalize quote style in collections WebSocket script (single to double)
- Disable Add Books button in collection detail (pending implementation)
2026-03-12 18:13:12 -04:00
john-okeefe 1b9bc64b28 refactor(library): fix SSR bug by removing data fetch from init function
CRITICAL FIX: initializeLibraryAdmin() was calling reloadLibraries()
which fetched data from the API and replaced the SSR-rendered library
list on page load, defeating the purpose of server-side rendering.

Changes in web/src/library.ts:
- Remove DOMContentLoaded listener (now uses Alpine x-init in template)
- Remove void reloadLibraries() call from initializeLibraryAdmin()
- Add comment explaining SSR provides initial data
- Add initializeLibraryAdmin to export statement
- Add initializeLibraryAdmin to Alpine.data() registration
- Keep reloadLibraries() as standalone function for use after CRUD ops

Rationale:
- SSR provides fast initial page load with library list
- x-init should ONLY setup event listeners, not fetch data
- reloadLibraries() is called after create/delete/update operations
- Follows SSR-first architecture: different pages have different
  SSR/JS ratios (analytics is 80% JS, most pages are 80% SSR)

Documentation:
- Update COLLECTIONS_CLEANUP_GUIDE.md with SSR-first strategy
- Document page-by-page review status (dashboard ✓, collections 🔄)
- Fix template references (library.templ → admin_library.templ)
- Explain why analytics fetches data (intentional for dynamic page)

This ensures the admin library page maintains SSR benefits while
still providing interactive features via Alpine.js.
2026-03-12 17:58:36 -04:00
john-okeefe b06ffa2329 refactor(admin): remove DOMContentLoaded listener for watch status initialization
- Remove document.addEventListener("DOMContentLoaded") wrapper for loadWatchStatus()
- Simplify initialization - loadWatchStatus() is now called via Alpine.js x-init
- Reduces 4 lines, keeps same functionality

The loadWatchStatus() function is now triggered by template's x-init directive
instead of a global DOMContentLoaded listener, ensuring it only runs on the
admin page where it's actually needed.
2026-03-12 17:21:38 -04:00
john-okeefe 4d186f76dc refactor(collections): remove dead Alpine.js exports and DOMContentLoaded listeners
- Remove DOMContentLoaded listeners for setupHTMXAuth, initColorSelection, and setupHTMXModalInit
- Delete dead Alpine.data exports: addbooksToAdd, removebooksToAdd, toggleBookForRemoval,
  toggleBookSelection, initCollectionDetail, initIconSelection, initColorSelection
- Add missing setupHTMXAuth to export statement (it was called but not exported)
- Remove 14 lines of auto-initialization code that's no longer needed

This fixes "X is not defined" console errors for functions that were deleted
in commit 93710a1 but were still in Alpine.data export. The collections.templ template
was also updated to remove calls to these deleted functions.

These changes align with the SSR architecture where most collection functionality
is server-rendered and client-side JavaScript is used sparingly.
2026-03-12 17:21:34 -04:00
john-okeefe 4ed5c24f84 feat(websocket): add reusable WebSocket connection helper utility
- Create createWebSocket() helper for WebSocket connections with authentication
- Support automatic reconnection with configurable delay
- Include error handling and logging
- Export disconnectWebSocket() for cleanup

This helper provides a centralized way to create WebSocket connections
with JWT token authentication from localStorage. Although not currently
used in the application (we opted for server-side token injection in templates),
it provides a reusable utility for future WebSocket integrations.

Features:
- Automatic token retrieval from localStorage
- Configurable reconnection behavior (enabled by default)
- Error handling with try-catch on all callbacks
- Connection cleanup and management
- Type-safe configuration interface

Available for future use in client-side WebSocket scenarios or as a reference
implementation.
2026-03-12 15:45:05 -04:00
john-okeefe b4cf1ddafa style: apply code formatting to generated templates and TypeScript files
- Regenerate Go template files with updated FileName paths for error reporting
- Apply Prettier formatting to api-explorer-docs.ts for consistency
- Format long function signatures across multiple lines for readability
- Format long conditional chains for better code clarity

Changes are purely formatting and do not affect functionality:
- api-explorer-docs.ts: Format initAPIExplorerDoc, tryDocEndpoint, and other functions
- Generated _templ.go files: Update FileName paths from relative to absolute (e.g., "admin.templ" → "templates/admin.templ")

This ensures consistent code style across the codebase and improves
error reporting by providing full file paths in template error messages.
2026-03-12 15:44:46 -04:00
john-okeefe 533f8747e3 perf(templates): add defer attribute to main.js script tags across all pages
- Add defer attribute to <script src="/static/main.js"> in 21 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading
- Remove duplicate defer attribute from header.templ line 264
- Add websocket.ts import to main.ts for module registration

This optimization reduces page render blocking and improves perceived load times
by allowing the browser to continue parsing HTML while the main.js bundle loads.
The defer attribute ensures scripts execute in order after HTML parsing completes.

Affected templates include:
- Admin pages: admin, admin_library, admin_settings, admin_users
- Content pages: analytics, bookshelf, collections, conflicts, custom_section
- User pages: dashboard, devices, docs, index, login, profile, progress, queue, register
- System pages: header, unlinked_books
2026-03-12 15:44:15 -04:00
john-okeefe 48eaa2d286 fix(alpine): wrap all Alpine.data() callbacks in arrow functions for proper component initialization
- 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.
2026-03-12 15:43:56 -04:00
john-okeefe 93710a1e96 refactor(collections): move WebSocket from TypeScript to template with server-side token
- Move WebSocket connection logic from collections.ts to collections.templ template
- Inject JWT token directly into WebSocket URL from server-side User.Token
- Remove createWebSocket import, ws variable, and connectWebSocket() function
- Remove unused CollectionUpdateMessage interface
- Embed complete WebSocket message handling in template script tag

Changes to collections.templ:
- Add inline <script> with WebSocket connection using server-injected token
- Implement collection_updated message handler with toast notifications
- Include 5-second auto-reconnection on disconnect
- Add user activity detection to skip auto-reload when actively typing
- Initialize collectionId and libraryId from data attributes

Changes to collections.ts:
- Remove createWebSocket import
- Remove ws variable declaration
- Remove connectWebSocket() function and CollectionUpdateMessage interface
- Keep all other collection functionality (CRUD operations, modals, search, etc.)

This refactoring eliminates client-side localStorage dependencies for
WebSocket authentication, making the collections page consistent with the
SSR architecture. The token is now injected server-side on every page
load, ensuring WebSocket connections always work when the user is
authenticated via HttpOnly cookie. Auto-reconnection and smart reload
behavior is preserved for optimal UX.
2026-03-12 15:43:39 -04:00
john-okeefe 40c447bd19 refactor(admin): move WebSocket from TypeScript to template with server-side token
- Move WebSocket connection logic from admin.ts to admin.templ template
- Inject JWT token directly into WebSocket URL from server-side User.Token
- Remove createWebSocket import and related functions from admin.ts
- Embed complete WebSocket message handling in template script tag

Changes to admin.templ:
- Add inline <script> with WebSocket connection using server-injected token
- Implement scan_progress, scan_complete, and scan_error message handlers
- Include error handling for WebSocket failures

Changes to admin.ts:
- Remove createWebSocket import
- Remove connectWebSocket(), updateScanProgress(), showScanComplete(), showScanError() functions
- Keep stopScanStatusPolling() for Alpine.js integration
- Preserve all other admin functionality (scan triggering, stats loading, etc.)

This refactoring eliminates client-side localStorage dependencies for
WebSocket authentication, making the admin page consistent with the
SSR architecture. The token is now injected server-side on every page
load, ensuring WebSocket connections always work when the user is
authenticated via HttpOnly cookie.
2026-03-12 15:43:27 -04:00
john-okeefe b754f0ddce refactor(websocket): migrate endpoint from /api/ws to /ws/sync
- Update WebSocket connection URL in admin.ts to use new /ws/sync endpoint
- Update API documentation in bruno collection to reflect new WebSocket route
- Standardizes WebSocket routing under /ws/ path prefix for better API organization

This change improves API structure consistency and makes WebSocket endpoints
more discoverable and manageable under a dedicated path hierarchy.
2026-03-12 09:04:04 -04:00
john-okeefe af337c88e2 build: regenerate templ files and compiled CSS
Generated files updated to reflect:
- New admin_settings.templ template
- Updated admin_sidebar.templ with Settings link
- Fixed devices.templ with correct regenerate-token path
- Compiled TailwindCSS with any style changes
2026-03-11 16:42:56 -04:00
john-okeefe d6b702e35a device: add copyToClipboard and fix regenerate token HTMX button
Phase 3: Complete device page functionality fixes

- Add copyToClipboard function to device-management.ts:
  - Uses navigator.clipboard.writeText() for copying
  - Shows success/error toast notifications
  - Already exported in Alpine.store, now properly defined
- Fix typo in devices.templ: change 'regerate-token' to 'regenerate-token'
- Add HTMX support to RegenerateDeviceToken handler:
  - Returns HTML with reload script for HTMX requests
  - Preserves JSON response for API calls

The regenerate token button now works via HTMX (hx-put) instead of
Alpine.js, matching the pattern used elsewhere in the app.
2026-03-11 16:42:34 -04:00
john-okeefe 5faf562250 Fix Path A: Resolve function mismatches and missing functions
Fixed template function call mismatches:
- conflicts.templ: Renamed showConflictModal→showResolveModal, hideConflictModal→hideResolveModal, handleResolveConflict→handleResolveSubmit
- collections.templ: Fixed case mismatch addSelectedBooks→addbooksToAdd
- queue.templ: Removed dead /static/queue.js reference
- progress.templ: Removed vestigial x-data="progress" attribute

Added missing queue functions:
- showQueueItemModal() - Shows queue item detail modal
- hideQueueItemModal() - Hides queue item modal
- filterQueue() - Filters queue by status (stubbed)

Fixed build errors:
- main.ts: Removed imports for deleted themeDropdown.ts and woodPaneling.ts files

All fixes are minimal and conservative. No breaking changes to existing functionality.
TypeScript builds successfully (168KB main.js).
Templates regenerate successfully.
2026-03-11 11:04:53 -04:00
john-okeefe 083f152b35 Safety backup before Path A fixes - fixing function mismatches and missing functions 2026-03-11 11:01:43 -04:00
john-okeefe 0a5042e130 docs: add Alpine.js migration guide for global() → store() API
Add comprehensive migration documentation for transitioning from the invalid
Alpine.global() API to the correct Alpine.store() API.

This guide addresses:
- Critical issue: Alpine.global() does not exist in Alpine.js v3.15.8
- 26 TypeScript files requiring updates
- Step-by-step migration instructions
- Template syntax changes (namespace.function() → $store.namespace.function())
- Testing checklist and troubleshooting guide

The migration will fix the "p.global is not a function" error currently
breaking the theme switcher and all Alpine namespaces.

Part 1 of 2 - covers TypeScript and template file updates.
2026-03-09 21:23:07 -04:00
john-okeefe 33cf00f65c refactor: Consolidate Alpine.js initialization and module loading
This commit reorganizes the Alpine.js initialization process to ensure all
component modules are registered before Alpine starts, preventing potential
race conditions and improving code organization.

Changes:
- Add web/src/register-alpine.ts: Central module that imports all Alpine
  component modules before calling Alpine.start(), ensuring proper
  registration order
- Update web/src/alpine.ts: Remove Alpine.start() call since it's now
  handled in register-alpine.ts
- Update web/src/main.ts: Replace individual module imports with single
  register-alpine import, simplifying the entry point
- Update web/src/header.ts: Rename changeThemeTo function to changeTheme
  for consistency with other naming conventions

This change ensures all Alpine.global() calls complete before Alpine
initializes, following best practices for Alpine.js module registration.
2026-03-09 20:58:40 -04:00
john-okeefe 3541a8603d feat: Complete Alpine.js migration for header.templ (Phase 1 reference implementation)
Migrated header template from hybrid onclick/@click with manual DOM
manipulation to full reactive Alpine.js with state-driven UI.

Template Changes (templates/header.templ):
- Added x-data state container: { themeDropdownOpen, userMenuOpen }
- Replaced @click="toggleThemeDropdown()" with @click="themeDropdownOpen = !themeDropdownOpen"
- Replaced id/class="hidden" with x-show directives
- Added @click.outside for click-outside-to-close behavior
- Added x-transition for smooth dropdown animations
- Added inline style="display: none;" to prevent FOUC
- Updated theme buttons to use header.changeThemeTo() namespace
- Updated wood paneling buttons to use woodPaneling.change() namespace
- Updated logout to use header.logout() namespace
- Close dropdowns after action: themeDropdownOpen = false

TypeScript Changes (web/src/header.ts):
- Removed toggleThemeDropdown() function (lines 7-18) - no longer needed
- Removed toggleUserMenu() function (lines 20-31) - no longer needed
- Removed manual DOM manipulation from changeThemeTo() (lines 50-54)
- Removed click-outside event listener (lines 64-88) - Alpine handles this
- Updated export to remove deleted functions
- Updated Alpine.global() registration to remove toggle functions
- Result: header.ts reduced from 100 lines to 40 lines (60% reduction)

TypeScript Changes (web/src/woodPaneling.ts):
- Added Alpine import
- Removed manual DOM manipulation from changeWoodPaneling()
- Added Alpine.global("woodPaneling", { change: changeWoodPaneling })

TypeScript Changes (web/src/themeDropdown.ts):
- Removed import of deleted toggleThemeDropdown function
- Removed initializeThemeDropdown() wrapper function
- Removed initializeChangeThemeTo() wrapper function
- Simplified updateThemeIndicators() to focus on wood paneling
- Updated Alpine.global("themeDropdown") registration

Benefits:
- Eliminates 23 manual DOM manipulations from header
- Smooth transitions with x-transition
- Click-outside behavior built-in with @click.outside
- State is local and encapsulated in template
- Cleaner separation of concerns (UI state in template, business logic in TS)
- Easier debugging with Alpine DevTools

Testing:
- Theme dropdown opens with smooth transition
- Theme dropdown closes when clicking outside
- Theme changes correctly when option clicked
- User menu opens with smooth transition
- User menu closes when clicking outside
- Logout works correctly
- Wood paneling changes work
- Both dropdowns show mutual exclusion behavior

Build Verification:
- templ generate: ✓ Success
- npm run build:ts: ✓ Success (167.8kb minified)

This is the reference implementation for Phase 1 of the Alpine.js
integration completion guide. All other modal templates should
follow this same pattern.

Related: ALPINE_COMPLETION_GUIDE.md Phase 1
Related: ESBUILD_MIGRATION_PLAN.md Phase 3, Template Migration
2026-03-09 20:19:08 -04:00
john-okeefe 0441568fab refactor: Replace window global with direct import in toast-error
- Import showToast function directly from toast module
- Remove dependency on window global for error handling
- Simplify code and improve type safety

This change aligns with the ESBuild migration by using proper ES module
imports instead of runtime global lookups.
2026-03-09 16:46:33 -04:00
john-okeefe 4480fb7817 refactor: Consolidate header JavaScript files into main.js bundle
- Update header.templ to load single main.js script instead of 4 separate files
- Remove obsolete header.js and search.js as they are now bundled
- Update Alpine.js event handlers to use standard double quotes
- This completes the ESBuild migration by eliminating inline script loads

The main.js bundle now contains all header, theme, and search functionality
previously loaded separately, improving load performance and maintainability.
2026-03-09 16:46:31 -04:00
john-okeefe 0919698cf4 refactor: Consolidate script imports to use main.js bundle
- dashboard.templ: Replaced individual script tags with single main.js import
- admin_users.templ, custom_section.templ: Minor formatting/cleanup
- docs.templ: Updated script imports (excluded from Alpine conversion per user request)
- api.ts, docs.ts, password_validation.ts: Minor updates for compatibility
2026-03-08 21:37:17 -04:00
john-okeefe 533dfd64e2 refactor: Update main.ts imports and add TypeScript types
- main.ts: Added imports for all new Alpine component files:
  * admin, api-explorer-docs, login, profile, profile-modal,
  * register, toast-error, unlinked_books, index, collection-rules
- api.d.ts: Added match_reason field to TestRuleMatch interface
  for collection rules test results display
2026-03-08 21:36:32 -04:00
john-okeefe 6728ba83a1 refactor: Add Alpine.js registration to existing TypeScript modules
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.
2026-03-08 21:36:24 -04:00
john-okeefe b78aacd320 feat: Add new Alpine.js component TypeScript files
Extracted inline JavaScript from templates into proper TypeScript modules:

- api-explorer-docs.ts: API explorer page functionality
- collection-rules.ts: Collection rules management page
- index.ts: Homepage theme and auth redirect
- login.ts: Login page theme initialization
- profile-modal.ts: Profile modal close and escape key
- profile.ts: Profile page delete account
- register.ts: Registration page theme init
- toast-error.ts: Error toast with retry button
- unlinked_books.ts: Unlinked books management page

Each file:
- Uses ES imports (showToast, getToken, etc.)
- Has proper TypeScript types
- Registers with Alpine.js via Alpine.global()
- Uses async/await for API calls
2026-03-08 21:35:47 -04:00