- Implement focus trapping in Popup component for keyboard navigation
- Add tabindex and ARIA attributes to ActionButton for screen readers
- Ensure tab navigation cycles through modal elements properly
- Enhance keyboard-only user experience
- Fetch random movie taglines from TMDB API
- Display quotes with elegant serif font styling
- Add error handling for failed API calls
- Enhance user experience with contextual content
- Implement keyboard shortcut (Cmd/Ctrl+K) to open command palette
- Add smart ranking algorithm (70% frequency + 30% recency)
- Track both route navigation and content (movies/shows) usage
- Support parameter input for dynamic routes (e.g., /movie/:id)
- Add query parameter support for search routes
- Integrate ElasticSearch fallback for content search
- Include rate limiting and error handling for API calls
- Store usage data in localStorage (commandPalette_stats)
- Auto-scroll selected items into view with keyboard navigation
Previous fix still rendered all 4 columns in DOM (just hidden with CSS),
causing horizontal overflow. Now actually renders only 2 columns on mobile.
Implementation:
1. Added reactive window width tracking with resize listener
2. Computed isMobile property (windowWidth <= 768px)
3. Computed visibleColumns: ['name', 'add'] on mobile, all 4 on desktop
4. Conditional v-if rendering for seed/size columns
5. Conditional v-if for metadata display in torrent-info cell
Template changes:
- Header: v-for="column in visibleColumns" (not all columns)
- Seed column: v-if="!isMobile" (not rendered on mobile)
- Size column: v-if="!isMobile" (not rendered on mobile)
- Metadata: v-if="isMobile" (only shown on mobile)
CSS cleanup:
- Removed .desktop-only class rules (no longer needed)
- Removed display: none media queries (handled by v-if)
- Removed header nth-child hiding (handled by visibleColumns)
Result:
Mobile (≤768px):
- Only 2 <td> elements rendered: name + add
- No horizontal scroll required
- Metadata shown inline under title
- Colspan correctly set to 2 for expanded rows
Desktop (>768px):
- All 4 <td> elements rendered: name + seed + size + add
- Full table layout
- Colspan correctly set to 4 for expanded rows
This is the correct solution - don't render unnecessary DOM elements.
The mobile torrent table changes were not working correctly due to CSS
specificity and display logic issues.
Fixes:
1. Changed .torrent-meta display logic:
- Before: display: none by default, then display: flex on mobile
- After: display: flex by default, display: none !important on desktop
- This ensures the metadata shows on mobile and is properly hidden on desktop
2. Fixed expanded row colspan:
- Dynamically calculate colspan based on screen width
- Mobile (≤768px): colspan = 2 (name + add columns)
- Desktop (>768px): colspan = 4 (name + seed + size + add columns)
- Prevents layout issues when expanding torrent names
Why the original didn't work:
- CSS specificity: 'display: none' as default was overriding mobile styles
- The @include mobile wasn't applying correctly due to cascade order
- Using @include desktop with !important ensures proper hiding
Result:
- Mobile: Shows torrent title with size/seeders on second line
- Desktop: Shows full 4-column table with separate columns
- Expanded rows now span correct number of columns on both layouts
When displaying recently added TV content, use the show's poster and
metadata instead of the individual episode's thumbnail and info.
Changes to processLibraryItem():
- Poster logic: For TV shows, prioritize grandparentThumb (show poster)
over thumb (episode thumbnail)
- Title: Use grandparentTitle (show name) instead of title (episode name)
- Year: Use grandparentYear (show year) instead of episode year
- Also applied same logic to music (use album/artist artwork)
Before:
- Shows displayed with episode-specific thumbnails
- Episode titles shown instead of show titles
- Inconsistent visual presentation
After:
- Shows display with proper show posters
- Show titles and years displayed correctly
- Consistent, professional library presentation
- Better visual recognition of TV series
This matches user expectations when browsing recently added TV content,
showing the series artwork rather than individual episode stills.
Change library item links to use the official Plex Web App URL format
instead of direct server URLs. This ensures items open correctly in
the Plex web interface.
Changes:
- usePlexApi.fetchPlexServers() now returns machineIdentifier (clientIdentifier)
- PlexSettings stores and passes machineId through the library loading flow
- usePlexLibraries.loadLibraries() accepts machineIdentifier parameter
- processLibrarySection() passes machineIdentifier to processLibraryItem()
- plexHelpers.processLibraryItem() updated signature and URL generation
New URL format:
https://app.plex.tv/desktop/#!/server/{machineId}/details?key=%2Flibrary%2Fmetadata%2F{ratingKey}
Example:
fe85f47ef9/details
Benefits:
- Links work universally (not dependent on local server URL)
- Opens in official Plex Web App with full functionality
- Consistent with Plex's own linking conventions
- Works from any network location
Modernize the Plex library UI by replacing emoji icons with proper SVG
icons and making library items clickable to open in Plex.
New icons:
- Created IconMusic.vue for music/album libraries
- Created IconClock.vue for watch time display
PlexLibraryStats updates:
- Replace emoji icons (🎬, 📺, 🎵, ⏱️) with IconMovie, IconShow, IconMusic, IconClock
- Icons use highlight color with hover effects
- Proper sizing: 2.5rem desktop, 2rem mobile
PlexLibraryModal updates:
- Replace emoji in header with dynamic icon component
- Icon sized at 48px with highlight color
- Better visual consistency
PlexLibraryItem updates:
- Add support for clickable links to Plex web interface
- Items render as <a> tags when plexUrl is available
- Fallback icons now use SVG components instead of emojis
- Non-linkable items have disabled hover state
plexHelpers updates:
- processLibraryItem now includes ratingKey and plexUrl
- plexUrl format: {serverUrl}/web/index.html#!/server/library/metadata/{ratingKey}
- Added getLibraryIconComponent helper function
Benefits:
- Professional SVG icons instead of emojis (consistent cross-platform)
- Clickable library items open directly in Plex
- Better accessibility with proper link semantics
- Scalable icons that look sharp at any size
- Consistent color theming with site palette
Improve password generator by using dynamic word sources instead of
static hardcoded lists.
Changes:
- Created useRandomWords composable:
- Primary: Random Word API (https://random-word-api.herokuapp.com)
- Fallback: EFF Diceware word list (576 memorable words)
- Automatic fallback if API fails or is unavailable
- Updated PasswordGenerator component:
- Remove 80+ lines of hardcoded word lists (adjectives, nouns, verbs, objects)
- Use async getRandomWords() for passphrase generation
- Better word variety and unpredictability
- Maintains same UX (no visible changes to users)
Benefits:
- More secure: Larger word pool (thousands vs 80 words)
- Always fresh: API provides truly random words
- Reliable: Built-in fallback ensures it always works
- Maintainable: No need to curate word lists
- Smaller bundle: Removed ~80 hardcoded words from component
Major performance improvement: Replace manual history aggregation with
Tautulli's built-in stats APIs. This eliminates the need to fetch and
process thousands of history records on every page load.
Changes:
- useTautulliStats composable completely rewritten:
- Use get_home_stats for overall watch statistics (pre-aggregated)
- Use get_plays_by_date for daily activity (already grouped by day)
- Use get_plays_by_dayofweek for weekly patterns (pre-calculated)
- Use get_plays_by_hourofday for hourly distribution (pre-calculated)
- Remove fetchUserHistory() and manual aggregation functions
- ActivityPage updates:
- Fetch all data in parallel with Promise.all for faster loading
- Use user_id instead of username for better API performance
- Simplified data processing since API returns pre-aggregated data
Benefits:
- 10-100x faster data loading (no need to fetch/process full history)
- Reduced network bandwidth (smaller API responses)
- Less client-side computation (no manual aggregation)
- Better scalability for large time ranges (365+ days)
- Consistent with Tautulli's internal calculations
- TorrentTable: Condense to 2 columns on mobile (title+meta, actions)
- Title shown on first line, size/seeders on second line
- Hide separate seed/size columns on mobile (desktop only)
- Improved spacing and readability for mobile screens
- Standardize page layouts to match ActivityPage:
- TorrentsPage: Update header style, padding, and container structure
- GenPasswordPage: Align header and content layout with other pages
- Consistent 3rem desktop padding, 0.75rem mobile padding
- Unified h1 styling: 2rem desktop, 1.5rem mobile, font-weight 300
- Minor improvements:
- Remove console.log statements from usePlexApi
- Fix duration unit handling in useTautulliStats
- Adjust AdminStats label font sizing
- Reduce Graph.vue point radius for cleaner charts
Issue: ActivityPage and route guards showed "not authenticated"
even when Plex was linked via Settings page.
Root cause: Plex user data stored in localStorage but route guards
and ActivityPage only checked Vuex store (state.settings.plexUserId).
Changes:
- Update routes.ts hasPlexAccount() to check both:
1. Vuex store (user/plexUserId)
2. localStorage (plex_user_data) as fallback
- Update ActivityPage plexUserId computed to check both:
1. Vuex store first
2. localStorage plex_user_data.id as fallback
Why two sources?
- Vuex store: Set from JWT token (backend user settings)
- localStorage: Set immediately when linking Plex account
- localStorage persists across page reloads
- Provides seamless experience without backend round-trip
Now Activity page correctly shows data when Plex is linked ✓
Create useTautulliStats composable (247 lines):
- fetchUserHistory() - Get watch history from Tautulli API
- calculateWatchStats() - Total hours, plays by media type
- groupByDay() - Daily activity (plays & duration)
- groupByDayOfWeek() - Weekly patterns by media type
- getTopContent() - Most watched content ranking
- getHourlyDistribution() - Watch patterns by hour of day
Update ActivityPage.vue with new visualizations:
- Stats overview cards (4 metrics: plays, hours, movies, episodes)
- Activity per day line chart (plays or duration)
- Activity by media type stacked bar chart (movies/shows/music)
- NEW: Hourly distribution chart
- NEW: Top 10 most watched content list
Features:
- Direct Tautulli API integration (no backend needed)
- Real-time data from Plex watch history
- Configurable time range (days filter)
- Toggle between plays count and watch duration
- Responsive grid layout for stats cards
- Styled top content ranking with hover effects
Benefits:
- Rich visualization of actual watch patterns
- See viewing habits by time of day
- Identify most rewatched content
- Compare movie vs TV viewing
- All data from authoritative source (Tautulli)
ActivityPage now provides comprehensive watch analytics! 📊
- Create new PlexLibraryItem.vue component
- Displays poster with fallback icon
- Shows title, year, and rating
- Optional extras (artist, episodes, tracks)
- Hover effect with translateY animation
- Responsive font sizes for mobile
- Update PlexLibraryModal to use grid layout
- Replace vertical list with CSS Grid
- Grid: repeat(auto-fill, minmax(140px, 1fr))
- Mobile: minmax(110px, 1fr) with reduced gap
- Much better space utilization
- Items flow horizontally then vertically
- Remove duplicate styles from modal
- Removed 69 lines of item styling
- All item display logic in PlexLibraryItem
- Cleaner separation of concerns
Benefits:
- Better visual presentation (grid vs vertical list)
- More items visible at once
- Reusable component for future Plex features
- Reduced modal complexity (284 → 215 lines)
- Update fetchLibrarySections to accept serverUrl parameter
- Was using internal plexServerUrl.value ref
- Now accepts explicit serverUrl parameter
- Prevents URL doubling when called from PlexSettings
- Update fetchLibraryDetails to accept serverUrl parameter
- Changed signature: (authToken, serverUrl, sectionKey)
- Was: (authToken, sectionKey) using internal ref
- Now matches how it's called from loadLibraries composable
- Fixes 404 errors from malformed URLs like:
http://server.com/library/sectionshttp://server.com/library/sections
Library API calls now use correct single URLs ✓
- Fix usePlexLibraries composable to return stats and details
- Updated loadLibraries signature to match PlexSettings usage
- Now accepts: sections, authToken, serverUrl, username, fetchFn
- Returns: { stats, details } object instead of updating refs
- Added watchtime calculation from Tautulli API
- Update processLibrarySection to work with passed parameters
- Accept stats and details objects instead of using refs
- Accept serverUrl and fetchLibraryDetailsFn as parameters
- No longer depends on composable internal state
- Remove all debug console.log statements
- Clean up usePlexAuth composable (removed 13 debug logs)
- Clean up PlexSettings component (removed 9 debug logs)
- Keep only error logging for troubleshooting
Library stats now display correctly after authentication ✓
Build size reduced by removing debug code
- Add detailed console logs throughout auth process
- PIN generation with CLIENT_IDENTIFIER
- PIN polling status checks
- Auth token received confirmation
- Cookie setting and verification
- User data fetch and account linking
- Helps diagnose authentication and cookie issues
- Logs show exact point of failure in auth flow
- Can be removed once issue is identified and fixed
- Export CLIENT_IDENTIFIER and APP_NAME as module-level constants
- Ensures same identifier used across all composables and API calls
- Prevents auth failures from mismatched client identifiers
- Refactor PlexSettings.vue to use composable auth flow
- Remove duplicate authentication logic (138 lines removed)
- Use openAuthPopup() from usePlexAuth composable
- Use cleanup() function in onUnmounted hook
- Reduced from 498 lines to 360 lines (28% further reduction)
- Fix usePlexAuth to import constants directly
- Previously tried to get constants from usePlexApi() instance
- Now imports as shared module exports
- Ensures consistent CLIENT_IDENTIFIER across auth flow
Total PlexSettings.vue reduction: 2094 → 360 lines (83% reduction)
Authentication flow now properly sets cookies and completes polling ✓
Extract more reusable components and utilities:
Components:
- PlexLibraryStats.vue: 4-card stats grid with loading states
- PlexServerInfo.vue: Server details and sync/unlink actions
Composables:
- usePlexLibraries.ts: Library data loading and processing logic
Utilities:
- plexHelpers.ts: Pure functions for formatting and calculations
- getLibraryIcon/Title: Type to display mapping
- formatDate/MemberSince: Date formatting
- processLibraryItem: Parse API response to display format
- calculateGenreStats: Top 5 genres from metadata
- calculateDuration: Total hours, episodes, tracks
Benefits:
- Cleaner separation: UI vs logic vs utilities
- Testable pure functions
- Reusable across components
- Reduces PlexSettings.vue complexity
Major improvements to Plex integration:
- Replace Vuex store dependency with localStorage-based connection detection
- Fetch and display real Plex user data (username, email, subscription, 2FA status)
- Add user badges: Plex Pass, member years, 2FA, experimental features
- Properly format Unix timestamp joined dates
- Remove success message box, add elegant checkmark icon next to username
- Add Plex connection badge to main user profile
Real-time Plex API integration:
- Fetch actual library counts from Plex server (movies, shows, music)
- Display real server name from user's Plex account
- Load recently added items with actual titles, years, and ratings
- Calculate real genre statistics from library metadata
- Compute actual duration totals from item metadata
- Count actual episodes (TV shows) and tracks (music)
- Sync library on demand with fresh data from Plex API
Interactive library modal:
- Replace toast messages with rich modal showing library details
- Display recently added items with poster images
- Show genre distribution with animated bar charts
- Add loading states with animated dots
- Disable empty library cards
- Modal appears above header with proper z-index
- Blur backdrop for better focus
- Fully responsive mobile design
Store Plex data in localStorage:
- Cache user profile data including subscription info
- Store auth token in secure cookie (30 day expiration)
- Load from cache for instant display on page load
- Refresh data on authentication and manual sync
Add Plex connection indicator to user profile:
- Orange Plex badge in settings profile header
- Shows 'Connected as [username]' below member info
- Loads username from localStorage on mount
- Remove unused imports and auto-refresh functionality
- Reduce padding and spacing for more compact admin layout
- Simplify stats generation and remove unused variables
- Adjust font sizes and icon sizes for better consistency
- Improve line-height on admin page title
- Minor performance optimizations
- Remove margin-right from SeasonedButton for better layout control
- Remove max-width constraint from SeasonedInput for full-width forms
- Simplify Toast component layout and remove unused icon section
- Improve toast text spacing and structure
- Load saved theme preference from localStorage
- Support for 'auto' theme that follows system preference
- Listen for system theme changes and update accordingly
- Apply theme before app mounts to prevent flash
- PasswordGenerator: Generate secure random passwords with options
- UserProfile: User information display (deprecated, moved to SettingsPage hero card)
- Supporting components for settings functionality
- Remove section title (now in parent SettingsPage)
- Add password generator integration
- Info box with password requirements
- Compact form layout with consistent spacing
- Match settings page typography and spacing
- Export user data in JSON or CSV format
- Display request statistics with mini stat cards
- View full request history button
- Account deletion with confirmation modal
- Warning for permanent actions with DELETE confirmation
- Compact styling consistent with settings page design
- Replace username/password with OAuth flow
- Generate PIN and open popup to app.plex.tv/auth
- Safari-compatible: open popup immediately, navigate after PIN generation
- Poll PIN status every second for authentication
- Custom loading screen in popup while generating PIN
- Plex orange button (#c87818) with icon
- Update API to accept authToken instead of credentials
- Cleanup on component unmount and popup close
- Handle popup blockers with user-friendly error messages
- Create single-page settings layout (removed sidebar navigation)
- Add large profile hero card with avatar, stats, and user info
- Display user stats: Requests and Magnets Added
- Compact spacing and improved typography hierarchy
- Section headers at 1.5rem for better hierarchy
- Reduced whitespace while maintaining readability
- Max-width: 800px for better content focus
- Add ThemePreferences component with current theme display
- Visual theme preview cards showing colors for each theme
- Add Seed theme to available themes list
- Theme icon with gradient and preview card styling
- Support for Auto, Light, Dark, Ocean, Nordic, Seed, and Halloween themes
- Add new 'seed' theme with green color palette
- Primary colors: #1c3a13 (seed green), #fcfcf7 (snow white), #e9f0ca (lemongrass)
- Dark green backgrounds with light green accents
- Complete theme definition with all CSS variables
tries to setup layout for success with safari iso 26 bottom navigation
bar and having content appear behind it instead of having a fat lip of
background color.
Also fixes where main content was not taking full width on mobile & text
alignment on torrent search results.
* On every route change, update local variables from query params
* ResultSection is keyed to query to force re-render
* Feat: vite & upgraded dependencies (#100)
* On every route change, update local variables from query params
* ResultSection is keyed to query to force re-render
* Resolved lint warnings
* replace webpack w/ vite
* update all imports with alias @ and scss
* vite environment variables, also typed
* upgraded eslint, defined new rules & added ignore comments
* resolved linting issues
* moved index.html to project root
* updated dockerfile w/ build stage before runtime image definition
* sign drone config
* dynamic colors from poster for popup bg & text colors
* more torrents nav button now link elem & better for darker bg
* make list item title clickable
* removed extra no-shadow eslint rule definitions
* fixed movie import
* adhere to eslint rules & package.json clean command
* remove debounce autocomplete search, track & hault on failure
* On every route change, update local variables from query params
* ResultSection is keyed to query to force re-render
* Resolved lint warnings
* replace webpack w/ vite
* update all imports with alias @ and scss
* vite environment variables, also typed
* upgraded eslint, defined new rules & added ignore comments
* resolved linting issues
* moved index.html to project root
* updated dockerfile w/ build stage before runtime image definition
* sign drone config