- Add OmbiRetriever extending ArrRetriever for PALDRA compliance
- Add OmbiClient for low-level Ombi API communication
- Add getOmbiInstances() to config.js following multi-instance pattern
- Register Ombi in PALDRA registry with Ombi-specific methods
- Add external ID matching (TMDB/TVDB/IMDB) to Ombi requests
- Update DownloadMatcher to be async and enrich downloads with Ombi links
- Add getOmbiLink/getOmbiSearchLink helpers to DownloadAssembler
- Implement new service icon layout (Ombi + Sonarr/Radarr icons)
- Add CSS styling for service icons
- Update dashboard routes to include Ombi configuration
- Extend OpenAPI with Ombi tag and NormalizedDownload properties
- Update documentation (README, ARCHITECTURE, SECURITY, CHANGELOG)
- Add Ombi configuration to .env.sample
- Change swaggerUi.setup to pass null and fetch spec from /api/swagger.json
- Update /api/swagger.json handler to dynamically set server URL based on request
- Remove dead client-side detection script (swagger-server-detection.js)
- Server-side detection respects TRUST_PROXY for reverse proxy scenarios
- req.protocol and req.get('host') automatically use X-Forwarded headers when configured
- Fixes issue where placeholder URL was never replaced due to window.ui being unavailable
- Update openapi.yaml to use single placeholder server URL
- Add swagger-server-detection.js to auto-detect current server URL from window.location
- Configure protocol, host, and port detection based on browser connection
- Fallback to placeholder URL if detection fails
- Include detection script in both app.js and index.js Swagger UI configurations
- /api/swagger.json endpoint returns static placeholder for external consumers
- Add GET /api/emby/users/:id endpoint to fetch individual user by ID
- Fix YAML semantic errors in dashboard.js and history.js by quoting parameter descriptions with colons
- Add x-integration-notes to /api/dashboard/stream endpoint description
- All 644 tests now passing
- Skip x-integration-notes test if merged spec not available
- The YAML file only has path placeholders without detailed descriptions
- JSDoc comments with x-integration-notes are merged at runtime
- Test will skip gracefully when /api/swagger.json returns 404
- Follow redirects for Swagger UI endpoint test
- Accept 404 for /api/swagger.json if not mounted in test mode
- Use merged spec for x-code-samples checks if available
- Fix x-integration-notes check to look for section header format
- Skip x-code-samples test if merged spec not available
- Install @stoplight/spectral-cli as dev dependency
- Add "Swagger Validation & Coverage" job to .gitea/workflows/ci.yml
- Run spectral lint on server/openapi.yaml
- Run npm test to execute coverage tests
- Fail CI if spec is invalid or coverage is incomplete
- Runs on every push/PR alongside existing jobs
- Create tests/integration/swagger-coverage.test.js
- Validate OpenAPI spec loads without errors
- Assert every Express route appears in spec
- Check all examples are valid JSON
- Verify required security schemes are referenced
- Run as part of existing test suite
- GET /health: returns uptime, no auth/rate-limit
- GET /ready: checks EMBY_URL configuration, returns 503 if not ready
- Document Docker HEALTHCHECK usage
- GET /api/dashboard/user-downloads: deprecated, use SSE
- GET /api/dashboard/cover-art: image proxy for CSP compliance
- GET /api/dashboard/stream: SSE real-time updates, no CSRF needed
- POST /api/dashboard/blocklist-search: admin-only, removes + re-searches
- Document SSE event format and heartbeat
- Include admin-only constraints and error responses
- POST /api/auth/login: rate-limited, sets httpOnly cookie, issues CSRF token
- GET /api/auth/me: returns current authenticated user
- GET /api/auth/csrf: refreshes CSRF token
- POST /api/auth/logout: clears cookies, revokes Emby token
- Include x-code-samples (curl, JS fetch, TypeScript)
- Include x-integration-notes for cookie flow
- Full JSON Schema with realistic examples
- Import swagger-ui-express, swagger-jsdoc, yamljs in app.js and index.js
- Load server/openapi.yaml as base spec
- Configure swagger-jsdoc to merge JSDoc comments from route files
- Mount Swagger UI at /api/swagger (publicly accessible)
- Add authentication banner explaining cookie + CSRF flow
- Ensure spec loads from both createApp (tests) and index.js (production)
Major feature release bringing technical-debt remediation, service extraction, frontend migration, and staged history loading.
### Added
- Staged history loading with SSE push — history data from Sonarr/Radarr is now fetched in stages; history-update SSE events push incremental results to the dashboard
- Frontend unit tests — added Vitest + jsdom test suite covering client/src/ modules
- Comprehensive tests for staged history loading — backend tests verify background-fetch behaviour, cache TTL handling, and SSE emission
- Integration test coverage — new integration and unit tests for dashboard, emby, sonarr, radarr, and sabnzbd routes
### Changed
- Technical-debt remediation — service extraction — extracted matching and assembly logic from monolithic dashboard.js (~1,360 lines → ~284 lines) into dedicated services: DownloadMatcher.js, DownloadAssembler.js, DownloadBuilder.js, TagMatcher.js, WebhookStatus.js
- Frontend architecture — migrated from monolithic public/app.js to vanilla ES modules under client/src/, bundled by Vite
- History pagination — replaced custom date-based cursor pagination with retriever's built-in pagination (pageSize=100, up to 10 pages / 1,000 records total)
- Status endpoint path — admin status route moved from /api/status/status to /api/status
- Background-fetch safety — poller no longer overwrites cache with empty data when background fetch fails
- SABnzbd progress calculation — progress computed from slot.mb and slot.mbleft / mbmissing
- Speed formatting consistency — updateDownloadCard() now calls formatSpeed() for uniform units
- Status-panel error handling — panel now surfaces error messages instead of blank box
### Fixed
- CSRF token reference errors — fixed ReferenceError bugs in api.js, auth.js, and blocklist handler
- Logout button — fixed undefined variable references
- Missing progress bar for SABnzbd — SABnzbd downloads now render correct progress bar
- Status route 404 — corrected Express router mount
- Status button DOM ID — fixed element-ID mismatch
- Tab selection — fixed to use data-tab attributes
- CSP violations and ignoreAvailable reference error
- Docker client-build stage — removed client/ from .dockerignore
- Unmatched torrent exclusion — torrents not matching Sonarr/Radarr record now correctly omitted
- Blocklist button CSRF — fixed ReferenceError
### Breaking Changes
- Unmatched torrents are no longer displayed — torrents that do not match a Sonarr/Radarr queue or history record are excluded
- Frontend build process changed — monolithic public/app.js source replaced by Vite build from client/src/
- Status API endpoint path changed — admin status endpoint moved from /api/status/status to /api/status
- Use state.csrfToken instead of undefined csrfToken in handleLogout
- Use state.currentUser instead of undefined currentUser in handleLogout
- Use state.csrfToken instead of undefined csrfToken in enableSonarrWebhook
- Fixes ReferenceError bugs in logout and webhook functions
- SABnzbd API uses different field names for remaining bytes
- Test data uses mbmissing, but real API may use mbleft
- Support both field names to handle all cases
- Fixes DownloadBuilder test failures
- Calculate progress from slot.mb and slot.mbleft instead of slot.percentage
- Apply fix to both series and movie downloads in matchSabSlots
- Add progress: 100 for history items in matchSabHistory (series and movie)
- SABnzbd slot data doesn't have percentage field, so progress was undefined
- Only trigger background refresh if cache is incomplete (less than max records)
- Only update cache if background fetch returns records (don't overwrite on failure)
- Prevents test failures when background fetch fails to connect to Sonarr/Radarr
- Fixes integration test 'includes imported and failed records, excludes grabbed'
- Use formatSpeed() when updating existing download card speed
- Ensures consistent unit display (B/s, KB/s, MB/s, GB/s) across all updates
- Previously used raw speed value causing inconsistent display
- Fix undefined variable references in handleLogoutClick
- Use state.statusRefreshHandle instead of statusRefreshHandle
- Use state.currentUser instead of currentUser
- Fix history pagination: use retriever's built-in maxPages parameter instead of broken date-based cursor
- Fix status panel: correct API endpoint from /api/dashboard/status to /api/status
- Background fetch now properly fetches up to 1000 records (10 pages * 100 records)
- Status panel will now display details instead of 'Loading Status...'
- Stage 1: Fetch 100 records immediately for fast display
- Stage 2+: Background fetch up to 1000 records in batches of 100
- Date-based cursor pagination to avoid race conditions
- Deduplication by record ID to prevent duplicates
- SSE push to clients when history cache is updated
- Shared background fetch state for concurrent user requests