diff --git a/ROUTER_REFACTOR_PLAN.md b/ROUTER_REFACTOR_PLAN.md
deleted file mode 100644
index 5cc1fa0..0000000
--- a/ROUTER_REFACTOR_PLAN.md
+++ /dev/null
@@ -1,479 +0,0 @@
-# Router Refactoring Execution Plan
-
-## Objective
-Refactor 858-line `cmd/server/main.go` by migrating route definitions to `internal/router/` package while maintaining 100% API compatibility and passing all verification tests.
-
-## Current State
-- ✅ `internal/router/` package created with 7 files
-- ✅ Route stubs implemented for: auth, library, device, frontend, docs
-- ❌ Router package NOT integrated (never called from main.go)
-- ❌ All routes still defined in main.go (duplicates)
-- ⚠️ main.go: 858 lines (target: ~200 lines)
-
-## Success Criteria
-1. All 26 verification checks pass (`scripts/verify-guidelines.sh`)
-2. All Go tests pass (`go test ./...`)
-3. All Bruno/curl API tests pass
-4. No API behavior changes (routes, handlers, responses identical)
-5. main.go reduced to ~200 lines
-6. Code compiles without errors
-7. Application runs successfully (containers start, health check returns 200)
-
-## Migration Strategy: Incremental with Rollback Safety
-
-### Phase 1: Create Safety Branch ✅
-- [x] Create branch `continue-router-refactor`
-- [x] Router package structure exists
-
-### Phase 2: Integrate Router Package (DO THIS FIRST)
-
-#### Step 2.1: Add Router Import and Config
-**File:** `cmd/server/main.go`
-
-Add to imports:
-```go
-"bookhoard/internal/router"
-```
-
-Add after line 130 (after rateLimiter initialization):
-```go
-// ========================================================================
-// ROUTER REGISTRATION - Migrate routes to internal/router/ package
-// ========================================================================
-routerConfig := &router.Config{
- Echo: e,
- Queries: queries,
- Cfg: cfg,
- DBPool: dbPool,
- AuthHandler: authHandler,
- LibraryHandler: libraryHandler,
- DeviceHandler: deviceHandler,
- KOReaderHandler: koreaderHandler,
- WSHandler: wsHandler,
- ConflictHandler: conflictHandler,
- AnalyticsHandler: analyticsHandler,
- QueueHandler: queueHandler,
- CollectionHandler: collectionHandler,
- OPDSHandler: opdsHandler,
- ConnManager: connManager,
- QueueProcessor: queueProcessor,
- DeviceAuthMiddleware: deviceAuthMiddleware,
- LoginTracker: loginAttemptTracker,
-}
-```
-
-#### Step 2.2: Call Router.RegisterRoutes()
-Add immediately after routerConfig:
-```go
-router.RegisterRoutes(routerConfig)
-```
-
-**IMPORTANT:** Do NOT remove any routes from main.go yet!
-
-#### Step 2.3: Test Compilation
-```bash
-go build ./cmd/server
-```
-
-**Expected:** Should compile (routes will be duplicated but that's OK temporarily)
-
-#### Step 2.4: Test Application
-```bash
-# Stop containers if running
-podman-compose down
-
-# Rebuild and start
-podman-compose up -d --build
-
-# Wait for startup
-sleep 10
-
-# Test health endpoint
-curl -s http://localhost:8765/health | jq .
-
-# Test frontend
-curl -s http://localhost:8765/ | grep -o "
.*"
-
-# Run verification
-bash scripts/verify-guidelines.sh
-```
-
-**Expected:** All should pass (duplicate routes don't break Echo)
-
-**ROLLBACK IF:** Compilation fails or health check returns non-200
-- `git checkout -- cmd/server/main.go`
-
----
-
-### Phase 3: Remove Duplicate Routes from main.go
-
-⚠️ **CRITICAL:** Remove ONE route group at a time, test after each removal!
-
-#### Step 3.1: Remove Auth Routes (lines 132-191)
-**Lines to remove:** From `// Auth routes` to `// JWT middleware for protected routes` (before jwtMiddleware creation)
-
-**Actually:** Keep jwtMiddleware creation (it's used by other routes)
-Remove: auth POST endpoints and protected auth routes that are now in router/auth.go
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-podman-compose up -d --build
-sleep 10
-# Test auth endpoints
-curl -X POST http://localhost:8765/api/auth/register -H "Content-Type: application/json" -d '{"email":"test@test.com","username":"test","password":"Test123!"}'
-```
-
-#### Step 3.2: Remove Library Routes (lines 192-235)
-**Lines to remove:** From `// Library management routes` to visibility routes
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-podman-compose up -d --build
-# Test library endpoints
-curl -s http://localhost:8765/api/libraries/types | jq .
-```
-
-#### Step 3.3: Remove Device Registration Routes (lines 236-239)
-**Lines to remove:** Device register and status endpoints
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-# Device registration test
-```
-
-#### Step 3.4: Remove Frontend Routes (lines 627-823)
-**Lines to remove:** From `// FRONTEND ROUTES` to before `// HEALTH CHECK`
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-curl -s http://localhost:8765/ | grep -o ".*"
-```
-
-#### Step 3.5: Remove Health Check (lines 824-844)
-**Lines to remove:** From `// HEALTH CHECK` to before `// DOCUMENTATION ROUTES`
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-curl -s http://localhost:8765/health | jq .
-```
-
-#### Step 3.6: Remove Documentation Routes (lines 845-858)
-**Lines to remove:** From `// DOCUMENTATION ROUTES` to end
-
-**Test after removal:**
-```bash
-go build ./cmd/server
-curl -s http://localhost:8765/docs | grep -o ".*"
-```
-
----
-
-### Phase 4: Implement Remaining Router Stubs
-
-#### Step 4.1: Create `router/sync.go`
-```bash
-# Create file with sync routes (KOReader, Kobo, websocket)
-# Copy sync route definitions from main.go
-```
-
-**Routes to migrate:**
-- KOReader sync routes (device authentication required)
-- Kobo sync routes (device authentication required)
-- Book matching routes
-- WebSocket endpoint
-
-#### Step 4.2: Create `router/media.go`
-**Routes to migrate:**
-- Media item routes (download, shelf management)
-- Bulk book operations
-
-#### Step 4.3: Create `router/analytics.go`
-**Routes to migrate:**
-- Analytics routes (API + SSR)
-
-#### Step 4.4: Create `router/queue.go`
-**Routes to migrate:**
-- Sync queue management routes (API + SSR)
-
-#### Step 4.5: Create `router/opds.go`
-**Routes to migrate:**
-- OPDS routes (public - device authentication optional)
-
-#### Step 4.6: Update `router/collections.go`
-**Routes to migrate:**
-- Collection routes (API + SSR)
-
----
-
-### Phase 5: Complete Migration
-
-For each new route file created in Phase 4:
-1. Add `registerXYZRoutes(cfg *Config)` function
-2. Call it from `router.RegisterRoutes()` in router.go
-3. Remove corresponding routes from main.go
-4. Test with: `go build ./cmd/server`
-5. Test with: `podman-compose up -d --build`
-6. Test specific endpoints with curl
-7. Run: `bash scripts/verify-guidelines.sh`
-
----
-
-### Phase 6: Final Verification
-
-#### Step 6.1: Full Test Suite
-```bash
-# Compilation
-go build ./cmd/server
-go test ./...
-
-# Verification
-bash scripts/verify-guidelines.sh
-
-# Container test
-podman-compose down
-podman-compose up -d --build
-sleep 15
-
-# Critical endpoint tests
-curl -s http://localhost:8765/health | jq .
-curl -s http://localhost:8765/ | grep -o ".*"
-curl -s http://localhost:8765/api/libraries/types | jq .
-curl -s http://localhost:8765/docs | grep -o ".*"
-
-# Run Bruno tests (if available)
-# bruno test ...
-```
-
-#### Step 6.2: Verify main.go Size
-```bash
-wc -l cmd/server/main.go
-```
-**Expected:** ~200 lines (down from 858)
-
-#### Step 6.3: Code Review Checklist
-- [ ] No routes duplicated (each route defined once)
-- [ ] All route groups use JWT middleware correctly
-- [ ] Admin middleware applied where needed
-- [ ] Rate limiting applied to auth endpoints
-- [ ] No compilation errors
-- [ ] All imports used
-- [ ] Consistent code style with rest of codebase
-
----
-
-### Phase 7: Commit and Push
-
-#### Step 7.1: Review Changes
-```bash
-git diff cmd/server/main.go | head -100
-git diff internal/router/
-```
-
-#### Step 7.2: Run Verification
-```bash
-bash scripts/verify-guidelines.sh
-```
-
-#### Step 7.3: Commit Changes
-```bash
-git add cmd/server/main.go internal/router/
-git commit -m "refactor: complete router package migration
-
-- Migrate all routes from main.go to internal/router/ package
-- Reduce main.go from 858 lines to ~200 lines
-- Create separate files for route groups:
- - auth.go: Authentication routes
- - library.go: Library management
- - device.go: Device registration & management
- - sync.go: KOReader/Kobo/WebSocket sync routes
- - media.go: Media items and bulk operations
- - analytics.go: Analytics API + SSR
- - queue.go: Sync queue management
- - opds.go: OPDS feeds
- - collections.go: Collection management
- - frontend.go: SSR pages and health check
- - docs.go: Documentation routes
-
-- All 26 verification checks pass
-- All API endpoints tested and working
-- Zero API behavior changes (100% compatible)
-- Follows Go standard project layout
-
-Breaking Change: None - API compatibility maintained"
-```
-
-#### Step 7.4: Push
-```bash
-git push origin continue-router-refactor
-```
-
----
-
-## Rollback Procedures
-
-### If compilation fails at any point:
-```bash
-git checkout -- cmd/server/main.go
-# Or
-git reset --hard HEAD
-```
-
-### If tests fail:
-1. Check which endpoint failed
-2. Verify route is registered in router package
-3. Check handler method exists
-4. Check middleware is applied correctly
-5. Review error logs: `podman logs bookhoard`
-
-### If verification fails:
-1. Check which specific check failed
-2. Fix the issue
-3. Re-run verification
-4. Commit the fix separately
-
----
-
-## Testing Commands (Quick Reference)
-
-```bash
-# Compile
-go build ./cmd/server
-
-# Verification
-bash scripts/verify-guidelines.sh
-
-# Unit tests
-go test ./...
-
-# Rebuild containers
-podman-compose down
-podman-compose up -d --build
-
-# Wait for startup
-sleep 10
-
-# Health check
-curl -s http://localhost:8765/health | jq .
-
-# Frontend
-curl -s http://localhost:8765/ | grep -o ".*"
-
-# Auth endpoint test
-curl -X POST http://localhost:8765/api/auth/login \
- -H "Content-Type: application/json" \
- -d '{"login":"test","password":"wrong"}'
-
-# Library types
-curl -s http://localhost:8765/api/libraries/types | jq .
-
-# Documentation
-curl -s http://localhost:8765/docs | grep -o ".*"
-
-# Check container logs
-podman logs bookhoard | tail -30
-
-# Check container status
-podman ps
-```
-
----
-
-## Files Created/Modified
-
-### Created:
-- `internal/router/router.go` - Main router configuration
-- `internal/router/auth.go` - Authentication routes
-- `internal/router/library.go` - Library management routes
-- `internal/router/device.go` - Device routes
-- `internal/router/frontend.go` - Frontend SSR routes
-- `internal/router/docs.go` - Documentation routes
-- `internal/router/helpers.go` - Template helpers
-- `internal/router/sync.go` - Sync routes (Phase 4)
-- `internal/router/media.go` - Media routes (Phase 4)
-- `internal/router/analytics.go` - Analytics routes (Phase 4)
-- `internal/router/queue.go` - Queue routes (Phase 4)
-- `internal/router/opds.go` - OPDS routes (Phase 4)
-- `internal/router/collections.go` - Collection routes (Phase 4)
-
-### Modified:
-- `cmd/server/main.go` - Reduced from 858 to ~200 lines
-
----
-
-## Estimated Time
-- Phase 2: 15 minutes (integration and initial testing)
-- Phase 3: 45 minutes (incremental route removal and testing)
-- Phase 4: 90 minutes (implement remaining route groups)
-- Phase 5: 30 minutes (complete migration)
-- Phase 6: 30 minutes (final verification)
-- Phase 7: 15 minutes (commit and push)
-
-**Total: ~4 hours**
-
----
-
-## Notes for AI Assistants
-
-1. **Always test after each change** - don't batch multiple route removals
-2. **Keep main.go functional** - it should compile at all times
-3. **Verify API compatibility** - routes must respond identically
-4. **Use git commits** - commit after each successful phase to enable rollback
-5. **Check logs** - if something fails, check `podman logs bookhoard`
-6. **Verification script is authority** - if it fails, fix before continuing
-7. **Echo allows duplicate routes** - temporarily OK during migration
-8. **Middleware order matters** - maintain exact middleware application order
-9. **Import statements** - remove unused imports after route removal
-10. **Handler methods** - verify handler methods exist before calling them
-
----
-
-## Troubleshooting
-
-### Error: "route already registered"
-- **Cause:** Route defined multiple times
-- **Fix:** Remove from main.go, keep in router package only
-
-### Error: "handler method not found"
-- **Cause:** Typo in method name or handler not initialized in Config
-- **Fix:** Check method name in handler file, ensure handler is passed in Config
-
-### Error: "undefined: jwtMiddleware"
-- **Cause:** JWT middleware not created in that route file
-- **Fix:** Add JWT middleware creation at top of register function
-
-### Error: "404 on previously working endpoint"
-- **Cause:** Route not registered or middleware blocking access
-- **Fix:** Check route is registered, check middleware conditions
-
-### Health check returns 503
-- **Cause:** Database not connected or dbPool not passed to router
-- **Fix:** Ensure DBPool is set in routerConfig
-
-### Verification fails with "Build failed"
-- **Cause:** Compilation error
-- **Fix:** Run `go build ./cmd/server` to see specific error
-
----
-
-## Success Metrics
-
-Before:
-- `cmd/server/main.go`: 858 lines
-- All routes defined inline
-- Mixed concerns (setup + routes + server start)
-
-After:
-- `cmd/server/main.go`: ~200 lines
-- Routes organized by domain in `internal/router/`
-- Clear separation: setup → router registration → server start
-- Follows Go standard project layout
-- Easy to maintain and extend
-
----
-
-## End of Plan
diff --git a/SCANNER_RESTORATION_PLAN.md b/SCANNER_RESTORATION_PLAN.md
deleted file mode 100644
index aa39e3f..0000000
--- a/SCANNER_RESTORATION_PLAN.md
+++ /dev/null
@@ -1,699 +0,0 @@
-# Scanner System Fix & Enhancement Plan
-
-**Created:** February 6, 2026
-**Updated:** February 6, 2026 (clarified scope and priorities)
-**Status:** Ready to implement
-
----
-
-## Executive Summary
-
-This plan addresses TWO separate issues with the scanner system:
-
-### 1. **CRITICAL BUG** (Priority: HIGH)
-Auto-start functionality was removed during router refactor (commit 6784c25):
-- Scheduler doesn't start on server boot
-- Watch mode doesn't auto-start for libraries
-- No graceful shutdown for scanner services
-- `handlers.SetupRoutes()` returns Handler but return value isn't captured
-
-### 2. **ENHANCEMENTS** (Priority: MEDIUM)
-- Library-type-aware scanning (prevents cross-contamination between ebook/comic/manga libraries)
-- Comic/manga metadata extraction (ComicInfo.xml parsing)
-- Better code organization (extract scanner routes to separate file)
-
-**What WASN'T broken:**
-- ✅ Scanner routes still work (7 endpoints in `internal/handlers/ebook.go:145-154`)
-- ✅ Can manually start/stop scanner via API
-- ✅ Scan job tracking works
-- ✅ Watch mode works when manually triggered
-
----
-
-## Changes Summary
-
-**Files to Create:**
-1. `internal/router/scanner.go` (new - optional, for organization)
-2. `internal/app/app.go` (new - required, for lifecycle management)
-
-**Files to Modify:**
-1. `internal/router/router.go` (capture and return EbookHandler)
-2. `cmd/server/main.go` (restore auto-start calls, use App pattern)
-3. `internal/services/ebook_scanner.go` (library-type-aware scanning)
-4. `internal/handlers/ebook.go` (comic/manga metadata extraction)
-
-**Risk Assessment:** LOW-MEDIUM
-- Auto-start restoration: LOW (restores existing code that was removed)
-- Library-type-awareness: MEDIUM (core scanner logic change)
-- Comic/manga scanning: MEDIUM (new feature)
-
----
-
-## Phase 1: Restore Auto-Start Functionality [CRITICAL BUG FIX]
-
-### Problem
-Before router refactor (commit 799b640):
-```go
-h := handlers.SetupRoutes(protected, queries)
-
-// Start scheduler for auto-scanning
-go h.StartScheduler()
-defer h.StopScheduler()
-
-// Start watch mode for all libraries (background)
-go func() {
- time.Sleep(2 * time.Second)
- if err := h.StartWatchModeForAllLibraries(context.Background()); err != nil {
- log.Printf("Warning: failed to start watch mode for libraries: %v", err)
- }
-}()
-```
-
-After refactor (current):
-```go
-handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager) // Return value ignored!
-// No scheduler start
-// No watch mode start
-// No graceful shutdown
-```
-
-### Solution
-
-#### Step 1.1: Update `internal/router/router.go`
-
-**Location:** Line 113-114
-
-**Current Code:**
-```go
-jwtMiddleware := createJWTMiddleware(cfg)
-protected := e.Group("/api", jwtMiddleware)
-handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager)
-```
-
-**New Code:**
-```go
-jwtMiddleware := createJWTMiddleware(cfg)
-protected := e.Group("/api", jwtMiddleware)
-ebookHandler := handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager)
-return ebookHandler // Add return statement to RegisterRoutes
-```
-
-**Update RegisterRoutes signature:**
-
-**Current (line 86):**
-```go
-func RegisterRoutes(cfg *Config) {
-```
-
-**New:**
-```go
-func RegisterRoutes(cfg *Config) *handlers.Handler {
- // ... existing code ...
- return ebookHandler // Return at end of function
-}
-```
-
-#### Step 1.2: Update `cmd/server/main.go`
-
-**Location:** After line 151 (after `router.RegisterRoutes(routerConfig)`)
-
-**Add this code:**
-```go
-// Register all routes and get ebook handler
-ebookHandler := router.RegisterRoutes(routerConfig)
-
-// ========================================================================
-// BACKGROUND SERVICES - Restore auto-start functionality
-// ========================================================================
-
-// Start scheduler for auto-scanning
-go ebookHandler.StartScheduler()
-defer ebookHandler.StopScheduler()
-
-// Start watch mode for all libraries (background)
-go func() {
- time.Sleep(2 * time.Second) // Wait for server to be ready
- if err := ebookHandler.StartWatchModeForAllLibraries(context.Background()); err != nil {
- log.Printf("Warning: failed to start watch mode for libraries: %v", err)
- }
-}()
-```
-
-**Verification:**
-- [ ] Application compiles
-- [ ] Server starts without errors
-- [ ] Check logs for "Starting scheduler" message
-- [ ] Check logs for watch mode starting after 2 seconds
-
----
-
-## Phase 2: Implement Library-Type-Aware Scanning [ENHANCEMENT]
-
-### Purpose
-Prevent cross-contamination between library types:
-- Epub libraries should only scan .epub files
-- Comic libraries should only scan .cbz/.cbr files
-- Manga libraries should only scan appropriate formats
-- Each library type has configurable allowed extensions
-
-### File: `internal/services/ebook_scanner.go`
-
-#### Change 2.1: Add libraryTypes cache field
-
-**Location:** Line 59-65 (EbookScanner struct)
-
-**Current Code:**
-```go
-type EbookScanner struct {
- db *database.Queries
- watcher *fsnotify.Watcher
- folders []string
- adminID pgtype.UUID
- defaultLibraryID pgtype.UUID
-}
-```
-
-**New Code:**
-```go
-type EbookScanner struct {
- db *database.Queries
- watcher *fsnotify.Watcher
- folders []string
- adminID pgtype.UUID
- defaultLibraryID pgtype.UUID
- libraryTypes map[string][]string // folder -> allowed extensions cache
-}
-```
-
-#### Change 2.2: Initialize libraryTypes in NewEbookScanner
-
-**Location:** Line 73-74
-
-**New Code:**
-```go
-return &EbookScanner{
- db: db,
- watcher: watcher,
- folders: []string{},
- adminID: pgtype.UUID{},
- defaultLibraryID: pgtype.UUID{Valid: false},
- libraryTypes: make(map[string][]string), // ← ADD THIS
-}
-```
-
-#### Change 2.3: Build library types cache in SetFolders
-
-**Location:** Line 86-109 (SetFolders function)
-
-**New Code:**
-```go
-func (s *EbookScanner) SetFolders(folders []string) error {
- s.folders = folders
-
- // Remove old watch if exists
- if s.watcher != nil {
- s.watcher.Close()
- }
-
- // Create new watcher
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- return fmt.Errorf("failed to create watcher: %v", err)
- }
- s.watcher = watcher
-
- // Build cache of allowed extensions per folder
- s.libraryTypes = make(map[string][]string)
- ctx := context.Background()
-
- for _, folder := range folders {
- // Get library for this folder
- lib, err := s.db.GetLibraryByFolder(ctx, folder)
- if err != nil {
- fmt.Printf("Warning: failed to get library for folder %s: %v\n", folder, err)
- continue
- }
-
- // Get library type with allowed extensions
- libType, err := s.db.GetLibraryType(ctx, lib.LibraryTypeID)
- if err != nil {
- fmt.Printf("Warning: failed to get library type for %s: %v\n", folder, err)
- continue
- }
-
- // Cache allowed extensions for this folder
- s.libraryTypes[folder] = libType.AllowedExtensions
- fmt.Printf("Scanner: Folder %s (type: %s) allows extensions: %v\n",
- folder, libType.Name, libType.AllowedExtensions)
- }
-
- // Add all folders to watch
- for _, folder := range folders {
- if err := s.watcher.Add(folder); err != nil {
- fmt.Printf("Warning: failed to watch folder %s: %v\n", folder, err)
- }
- }
-
- return nil
-}
-```
-
-#### Change 2.4: Replace isEbookFile with isScannableFile
-
-**Location:** Line 168-177 (isEbookFile function)
-
-**New Code:**
-```go
-// isScannableFile checks if a file should be scanned based on library type configuration
-func (s *EbookScanner) isScannableFile(path string) bool {
- ext := strings.ToLower(filepath.Ext(path))
-
- // Find which folder this file belongs to
- var folder string
- for _, f := range s.folders {
- if strings.HasPrefix(path, f) {
- folder = f
- break
- }
- }
-
- // If no folder match, don't scan
- if folder == "" {
- return false
- }
-
- // Get allowed extensions for this folder's library
- allowed, ok := s.libraryTypes[folder]
- if !ok {
- // No library type info, skip file
- fmt.Printf("Warning: No library type info for folder %s, skipping %s\n", folder, path)
- return false
- }
-
- // Check if file extension is allowed for this library type
- for _, allowedExt := range allowed {
- if ext == strings.ToLower(allowedExt) {
- return true
- }
- }
-
- return false
-}
-```
-
-#### Change 2.5: Update ScanFolders to use isScannableFile
-
-**Location:** Line 146 (in ScanFolders function)
-
-**Current Code:**
-```go
-// Check if it's an ebook file
-if s.isEbookFile(path) {
-```
-
-**New Code:**
-```go
-// Check if file should be scanned based on library type
-if s.isScannableFile(path) {
-```
-
-**Verification:**
-- [ ] Code compiles
-- [ ] Ebook libraries scan only .epub files (check logs)
-- [ ] Comic libraries scan only .cbz/.cbr files
-- [ ] No cross-contamination between library types
-- [ ] Test: Create ebook library, add .cbz file → should be ignored
-- [ ] Test: Create comic library, add .epub file → should be ignored
-
----
-
-## Phase 3: Comic/Manga Metadata Extraction [NEW FEATURE]
-
-### Purpose
-Extract metadata from comic/manga archives (.cbz, .cbr, .cb7):
-- Parse ComicInfo.xml from archives
-- Extract cover images
-- Get series, issue number, publisher, etc.
-- Support comic library management
-
-### File: `internal/handlers/ebook.go`
-
-#### Step 3.1: Add ComicInfo.xml parsing
-
-**Add to imports:**
-```go
-import (
- "archive/zip"
- "encoding/xml"
- "image"
- _ "image/jpeg"
- _ "image/png"
- "path/filepath"
- "strings"
-)
-```
-
-#### Step 3.2: Define ComicInfo struct
-
-**Add after existing structs:**
-```go
-// ComicInfo represents metadata from ComicInfo.xml
-type ComicInfo struct {
- XMLName xml.Name `xml:"ComicInfo"`
- Title string `xml:"Title"`
- Series string `xml:"Series"`
- Number int `xml:"Number"`
- Volume int `xml:"Volume"`
- Publisher string `xml:"Publisher"`
- Year int `xml:"Year"`
- Month int `xml:"Month"`
- Day int `xml:"Day"`
- Writer string `xml:"Writer"`
- Penciller string `xml:"Penciller"`
- Inker string `xml:"Inker"`
- Colorist string `xml:"Colorist"`
- Letterer string `xml:"Letterer"`
- CoverArtist string `xml:"CoverArtist"`
- Genre string `xml:"Genre"`
- Tags string `xml:"Tags"`
- Web string `xml:"Web"`
- Notes string `xml:"Notes"`
-}
-```
-
-#### Step 3.3: Add extraction function
-
-**Add new function:**
-```go
-// extractComicMetadata extracts metadata from comic archive
-func extractComicMetadata(filePath string) (*ComicInfo, []byte, error) {
- // Open archive
- r, err := zip.OpenReader(filePath)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to open comic archive: %w", err)
- }
- defer r.Close()
-
- // Look for ComicInfo.xml
- var comicInfo *ComicInfo
- var coverImage []byte
-
- for _, f := range r.File {
- if f.Name == "ComicInfo.xml" {
- rc, err := f.Open()
- if err != nil {
- return nil, nil, fmt.Errorf("failed to open ComicInfo.xml: %w", err)
- }
-
- data, err := io.ReadAll(rc)
- rc.Close()
- if err != nil {
- return nil, nil, fmt.Errorf("failed to read ComicInfo.xml: %w", err)
- }
-
- comicInfo = &ComicInfo{}
- if err := xml.Unmarshal(data, comicInfo); err != nil {
- return nil, nil, fmt.Errorf("failed to parse ComicInfo.xml: %w", err)
- }
- }
-
- // Look for cover image (usually first image in root)
- if coverImage == nil && isImageFile(f.Name) {
- // Usually in root directory, not subdirectories
- if !strings.Contains(filepath.Dir(f.Name), string(filepath.Separator)) ||
- filepath.Dir(f.Name) == "." {
- rc, err := f.Open()
- if err != nil {
- continue
- }
-
- coverImage, err = io.ReadAll(rc)
- rc.Close()
- if err == nil {
- // Validate it's actually an image
- _, _, err = image.Decode(bytes.NewReader(coverImage))
- if err != nil {
- coverImage = nil // Not a valid image
- }
- }
- }
- }
- }
-
- if comicInfo == nil {
- // No ComicInfo.xml, create minimal metadata from filename
- comicInfo = &ComicInfo{}
- basename := filepath.Base(filePath)
- comicInfo.Title = strings.TrimSuffix(basename, filepath.Ext(basename))
- }
-
- return comicInfo, coverImage, nil
-}
-
-// isImageFile checks if a file is an image based on extension
-func isImageFile(filename string) bool {
- ext := strings.ToLower(filepath.Ext(filename))
- return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif"
-}
-```
-
-#### Step 3.4: Integrate into scanner
-
-**Update ScanFolders to extract comic metadata for .cbz/.cbr files:**
-
-**Location:** In the file processing loop (around line 160-180)
-
-**Add before creating media item:**
-```go
-var comicInfo *ComicInfo
-var coverImage []byte
-
-// Extract comic metadata if applicable
-if strings.ToLower(filepath.Ext(path)) == ".cbz" {
- info, cover, err := extractComicMetadata(path)
- if err != nil {
- log.Printf("Warning: failed to extract comic metadata from %s: %v", path, err)
- } else {
- comicInfo = info
- coverImage = cover
- }
-}
-
-// When creating media item, use comic metadata
-title := comicInfo.Title
-if title == "" {
- title = filepath.Base(path)
-}
-
-// Use cover image if available
-if len(coverImage) > 0 {
- // Use extracted cover
- // ... existing cover processing code ...
-}
-```
-
-**Verification:**
-- [ ] Code compiles
-- [ ] Create .cbz file with ComicInfo.xml
-- [ ] Scan comic library
-- [ ] Check metadata was extracted (title, series, issue)
-- [ ] Check cover image was extracted
-- [ ] Test .cbz without ComicInfo.xml (should use filename)
-
----
-
-## Phase 4: Organize Scanner Routes [OPTIONAL - LOW PRIORITY]
-
-### Purpose
-Move scanner routes from `internal/handlers/ebook.go` to `internal/router/scanner.go` for better organization.
-
-**Note:** This is purely cosmetic. Scanner routes already work fine where they are.
-
-### File: `internal/router/scanner.go` (NEW)
-
-```go
-package router
-
-import (
- "github.com/labstack/echo/v4"
-)
-
-// registerScannerRoutes registers all scanner-related endpoints
-func registerScannerRoutes(cfg *Config) {
- // Routes are already registered in handlers.SetupRoutes()
- // This file is for documentation/organization purposes
- // Actual routes are in:
- // - internal/handlers/ebook.go:145-154 (scanner endpoints)
- // - internal/handlers/auth.go (scan settings endpoints)
-}
-```
-
-**Decision:** SKIP this phase. The current organization works fine.
-
----
-
-## Phase 5: Application Lifecycle Management [OPTIONAL - DEFER]
-
-### Purpose
-Create `internal/app/app.go` for better lifecycle management, graceful shutdown, signal handling.
-
-**Decision:** DEFER to future implementation. The simple approach in Phase 1 is sufficient for now.
-
----
-
-## Implementation Order
-
-### Priority 1: Critical Bug Fix (Phase 1)
-- Time: 15 minutes
-- Risk: LOW
-- Impact: Restores auto-scan and watch mode
-
-### Priority 2: Library-Type-Awareness (Phase 2)
-- Time: 1 hour
-- Risk: MEDIUM
-- Impact: Prevents cross-contamination
-
-### Priority 3: Comic/Manga Scanning (Phase 3)
-- Time: 2-3 hours
-- Risk: MEDIUM
-- Impact: New feature for comic libraries
-
-### Priority 4: Code Organization (Phase 4)
-- Time: 30 minutes
-- Risk: LOW
-- Impact: Cosmetic (SKIP for now)
-
-### Priority 5: App Lifecycle (Phase 5)
-- Time: 2 hours
-- Risk: MEDIUM
-- Impact: Better structure (DEFER for now)
-
----
-
-## Testing Checklist
-
-### After Phase 1 (Auto-start restoration):
-- [ ] Application compiles
-- [ ] Server starts without errors
-- [ ] Logs show "Starting scheduler"
-- [ ] Logs show "Starting watch mode for all libraries" after 2 seconds
-- [ ] Scheduler triggers auto-scans
-- [ ] SIGTERM triggers graceful shutdown
-
-### After Phase 2 (Library-type-awareness):
-- [ ] Create ebook library, add .epub → scans correctly
-- [ ] Create ebook library, add .cbz → ignored
-- [ ] Create comic library, add .cbz → scans correctly
-- [ ] Create comic library, add .epub → ignored
-- [ ] Check logs for library type messages
-
-### After Phase 3 (Comic/manga scanning):
-- [ ] Create test .cbz with ComicInfo.xml
-- [ ] Add to comic library
-- [ ] Scan library
-- [ ] Verify metadata extracted (title, series, issue)
-- [ ] Verify cover image extracted
-- [ ] Test .cbz without ComicInfo.xml (uses filename)
-
----
-
-## Verification Commands
-
-### Test scanner auto-start:
-```bash
-# Start server
-podman-compose up -d --build
-
-# Check logs
-podman logs bookhoard | grep -i "scheduler\|watch mode"
-
-# Should see:
-# "Starting scheduler for auto-scanning"
-# "Starting watch mode for all libraries"
-```
-
-### Test library-type-awareness:
-```bash
-# Create ebook library
-curl -X POST http://localhost:8765/api/libraries \
- -H "Authorization: Bearer $TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"name":"Ebooks","library_type_id":"","folders":["/path/to/ebooks"]}'
-
-# Create comic library
-curl -X POST http://localhost:8765/api/libraries \
- -H "Authorization: Bearer $TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"name":"Comics","library_type_id":"","folders":["/path/to/comics"]}'
-
-# Add .epub to comic library → should be ignored
-# Add .cbz to ebook library → should be ignored
-# Check scan logs for filtering messages
-```
-
-### Test comic metadata extraction:
-```bash
-# Create test .cbz with ComicInfo.xml
-zip test.cbz ComicInfo.xml cover.jpg page1.jpg
-
-# Add to comic library and scan
-curl -X POST http://localhost:8765/api/scanner/scan \
- -H "Authorization: Bearer $TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"folder_paths":["/path/to/comics"]}'
-
-# Check media item has correct metadata
-curl http://localhost:8765/api/media-items?library_id= \
- -H "Authorization: Bearer $TOKEN" | jq .
-```
-
----
-
-## Rollback Plan
-
-### Rollback Phase 1:
-```bash
-git checkout cmd/server/main.go
-git checkout internal/router/router.go
-```
-
-### Rollback Phase 2:
-```bash
-git checkout internal/services/ebook_scanner.go
-```
-
-### Rollback Phase 3:
-```bash
-git checkout internal/handlers/ebook.go
-```
-
----
-
-## Summary
-
-**Critical Issues Fixed:**
-- ✅ Auto-start scheduler (restored)
-- ✅ Auto-start watch mode (restored)
-- ✅ Graceful shutdown (restored)
-
-**Enhancements Added:**
-- ✅ Library-type-aware scanning (prevents cross-contamination)
-- ✅ Comic/manga metadata extraction (ComicInfo.xml parsing)
-- ✅ Comic cover image extraction
-
-**Deferred:**
-- ⏸️ Code organization (scanner routes file)
-- ⏸️ App lifecycle management
-
-**Estimated Time:**
-- Phase 1 (bug fix): 15 minutes
-- Phase 2 (enhancement): 1 hour
-- Phase 3 (new feature): 2-3 hours
-- **Total: 4-5 hours**
-
-**Risk Level:**
-- Phase 1: LOW (restoring removed code)
-- Phase 2: MEDIUM (core scanner logic)
-- Phase 3: MEDIUM (new feature, isolated)
-
-**Ready for implementation in priority order.**