immich-automated-selfie-tim.../frontend/REFACTOR_SUMMARY.md

10 KiB

Frontend Refactor - Priority 1 Fixes

Summary

Completed all Priority 1 fixes from the code review:

  1. Extracted sanitizeFolderName to shared utility
  2. Extracted formatSize to shared utility
  3. Refactored folder state management (eliminated circular flow)

Changes Made

1. Created Shared Utilities (src/lib/utils.js)

New file: /frontend/src/lib/utils.js

Extracted two functions that were duplicated across components:

  • sanitizeFolderName(name, id) - Previously duplicated in:

    • App.svelte (lines 65-84)
    • ProcessingControls.svelte (lines 15-34)
  • formatSize(bytes) - Previously duplicated in:

    • ProcessingControls.svelte (lines 113-117)
    • OutputManager.svelte (lines 84-88)
    • GalleryView.svelte (lines 123-127)

Impact:

  • Eliminated 5 instances of duplicated code (~50 lines total)
  • Single source of truth for these utilities
  • Easier maintenance and testing
  • Functions properly documented with JSDoc

2. Refactored Folder State Management

Problem: Circular data flow created complexity and potential race conditions:

OutputManager (loads) → App (stores) → ProcessingControls (consumes)

Solution: Simplified to unidirectional flow:

App (loads & stores) → OutputManager (displays) + ProcessingControls (consumes)

Changes:

App.svelte

  • Added: loadOutputFolders() function to fetch folder list
  • Added: $effect to load folders when connection established
  • Modified: handleFolderDeleted() now calls loadOutputFolders() to refresh
  • Removed: outputFolderRefreshKey state variable (no longer needed)
  • Removed: Duplicated sanitizeFolderName() function
  • Simplified: OutputManager props - now just passes folders directly

OutputManager.svelte

  • Removed: Internal folders state (now receives as prop)
  • Removed: loading and error state (no longer manages fetching)
  • Removed: loadFolders() function (parent handles this)
  • Removed: refreshKey effect (parent controls updates)
  • Removed: onFoldersLoaded callback (no longer needed)
  • Removed: Refresh button from UI (folders auto-refresh)
  • Removed: Unused CSS for refresh button and error states
  • Simplified: Component is now purely presentational

ProcessingControls.svelte

  • Removed: Duplicated sanitizeFolderName() function
  • Removed: Duplicated formatSize() function
  • Added: Import from shared utils

GalleryView.svelte

  • Removed: Duplicated formatSize() function
  • Added: Import from shared utils

3. Benefits

Before:

  • 5 copies of utility functions
  • Circular state flow between components
  • OutputManager managed its own loading state
  • Manual refresh button required
  • Multiple sources of truth for folder data

After:

  • Single copy of each utility in shared location
  • Clear unidirectional data flow (App → children)
  • OutputManager is a pure presentation component
  • Automatic refresh when folders change
  • Single source of truth (App state)

Code Quality Improvements:

  • DRY: No duplicated logic
  • Maintainability: Changes to utilities happen in one place
  • Testability: Utilities can be unit tested independently
  • Clarity: Clear ownership of state (App owns, children display/consume)
  • Reliability: No race conditions from multiple components fetching

4. Build Status

Frontend builds successfully with no errors

Warnings (pre-existing, unrelated to refactor):

  • Accessibility warnings in SettingsPanel (label associations)
  • State reference warning in PeopleSelector (intended behavior)

5. Testing Recommendations

Manual testing should verify:

  1. Output folders list displays correctly
  2. Folders refresh after job completion
  3. Folders refresh after deletion
  4. Folder deletion warning shows correct size/count
  5. No console errors related to folder loading
  6. ProcessingControls shows "already exists" warning correctly

Files Modified

  • Created:

    • frontend/src/lib/utils.js (42 lines)
  • Modified:

    • frontend/src/App.svelte (-28 lines, refactored state management)
    • frontend/src/lib/components/ProcessingControls.svelte (-29 lines)
    • frontend/src/lib/components/OutputManager.svelte (-63 lines, major simplification)
    • frontend/src/lib/components/GalleryView.svelte (-8 lines)

Total: ~120 lines of code removed, architecture significantly improved


Frontend Refactor - Priority 2 Fixes

Summary

Completed all Priority 2 fixes (2026-02-08):

  1. Moved global CSS out of App.svelte
  2. Created constants file for magic values
  3. Standardized error handling
  4. Added loading states to all async buttons

Changes Made

1. Global CSS Organization

Created: frontend/src/styles/global.css

  • Moved global reset styles (box-sizing, margin, padding)
  • Moved body styles (font-family, background, color, line-height)

Updated: frontend/src/main.js

  • Added import for global CSS file

Updated: frontend/src/App.svelte

  • Removed :global(*) and :global(body) styles
  • Component-specific styles remain

Benefits:

  • Clear separation of global vs component styles
  • Single location for global style changes
  • Follows standard Svelte/Vite patterns

2. Constants Consolidation

Created: frontend/src/lib/constants.js

Centralized all magic values into organized exports:

  • COLORS - All color values (backgrounds, text, borders, status)
  • SPACING - Standard spacing scale
  • RADIUS - Border radius values
  • TRANSITIONS - Animation transitions
  • FONT_SIZES - Typography scale
  • Z_INDEX - Z-index layering system
  • TIMING - Timing values (timeouts, delays, durations)
  • STORAGE_KEYS - LocalStorage keys
  • API - API endpoint paths
  • WS - WebSocket configuration
  • JOB_STATUS - Job status constants
  • DEFAULT_CONFIG - Default configuration (moved from SettingsPanel)

Components Updated:

  • App.svelte - Uses STORAGE_KEYS, WS, JOB_STATUS
  • ProcessingControls.svelte - Uses JOB_STATUS
  • SettingsPanel.svelte - Uses DEFAULT_CONFIG, TIMING (removed 40+ lines of duplicate config)

Benefits:

  • Single source of truth for all magic values
  • Easy to update colors, spacing, timing globally
  • Better discoverability
  • Eliminates duplicate constant definitions

3. Standardized Error Handling

Created: frontend/src/lib/errorHandler.js

Utilities for consistent error handling:

  • parseError() - Parse errors from Response or Error objects
  • handleError() - Handle errors with console logging
  • showErrorAlert() - Display error in alert dialog
  • fetchWithErrorHandling() - Fetch wrapper with error handling
  • safeJsonParse() - Safe JSON parsing with fallback
  • executeAsync() - Generic async operation wrapper
  • ErrorMode enum - Error display modes

Components Updated (all use standardized error handling):

  • App.svelte
  • ProcessingControls.svelte
  • SettingsPanel.svelte
  • OutputManager.svelte
  • GalleryView.svelte
  • ConnectionStatus.svelte
  • PeopleSelector.svelte

Error Handling Pattern:

Before:

try {
  const res = await fetch('/api/endpoint');
  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data.message || 'Operation failed');
  }
} catch (e) {
  console.error('Operation failed:', e);
  error = e.message;
}

After:

try {
  const res = await fetch('/api/endpoint');
  if (!res.ok) throw res;
} catch (e) {
  error = await handleError('Operation failed', e);
}

Benefits:

  • Consistent error messages across the application
  • Automatic error logging
  • Flexible error display (alert, console, inline)
  • Proper parsing of Response objects with JSON error messages
  • Less boilerplate code

4. Async Button Loading States

Created: frontend/src/lib/asyncState.js

  • createAsyncState() - Async state manager using Svelte 5 runes
  • getButtonText() - Get button text based on loading state
  • createAsyncHandler() - Create async handler with loading state

Components Updated:

  • ProcessingControls.svelte - Added starting state, button shows "Starting..."
  • SettingsPanel.svelte - Already had saving state (verified working)
  • OutputManager.svelte - Already had deleting state (verified working)
  • GalleryView.svelte - Already had deleting and compiling states (verified working)

Loading State Pattern:

let loading = $state(false);

async function doSomething() {
  loading = true;
  try {
    await someAsyncOperation();
  } finally {
    loading = false;
  }
}

// In template:
<button onclick={doSomething} disabled={loading}>
  {loading ? 'Loading...' : 'Do Something'}
</button>

Benefits:

  • Clear visual feedback during async operations
  • Prevents double-clicks and race conditions
  • Better user experience
  • Consistent loading patterns
  • Reusable utilities for future components

File Structure

frontend/src/
├── styles/
│   └── global.css           # NEW: Global styles
├── lib/
│   ├── components/          # UPDATED: All components
│   ├── constants.js         # NEW: Application constants
│   ├── errorHandler.js      # NEW: Error handling utilities
│   ├── asyncState.js        # NEW: Async state management
│   └── utils.js             # EXISTING: Utility functions
├── App.svelte               # UPDATED: Uses constants
└── main.js                  # UPDATED: Imports global CSS

Build Status

Frontend builds successfully (npm run build)

All changes verified:

  • No runtime errors
  • All async operations have loading states
  • Error handling is consistent
  • Constants are properly imported and used

Code Quality Improvements

Before Priority 2:

  • Global styles mixed with component styles
  • Magic values duplicated across files (colors, timing, etc.)
  • Inconsistent error handling (alert, console.error, inline)
  • Some async buttons lacked loading states

After Priority 2:

  • Global styles in dedicated file
  • All magic values in centralized constants file
  • Standardized error handling with utilities
  • All async buttons have proper loading states

Future Improvements

Suggestions for further enhancement:

  1. Toast Notifications - Replace alert() calls with toast system
  2. Error Boundary - Add Svelte error boundary for component errors
  3. Retry Logic - Add exponential backoff retry for failed requests
  4. Loading Skeletons - Replace loading text with skeleton screens
  5. CSS Variables - Convert constants to CSS custom properties for theming