- Replace Ombi API-based matching with simple TMDB ID link generation
- Movies link to {ombiBaseUrl}/details/movie/{tmdbId}
- TV shows link to {ombiBaseUrl}/details/tv/{tmdbId}
- Add ombiLink to all history items (Sonarr + Radarr) for all users
- Add ombiLink to torrent history matches that were previously missing it
- addOmbiMatching is now synchronous (no Ombi API calls)
Add arrType field to Sonarr and Radarr history items for admin users to enable proper icon display in the recently downloaded section. This mirrors the existing behavior in active downloads where arrType is set by DownloadMatcher.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The buildUserDownloads function was calling async matcher functions
without awaiting them, causing Promise objects to be returned instead
of resolved arrays. This resulted in empty download lists and 17 failing
tests.
- Made buildUserDownloads async
- Added await to matchSabSlots, matchSabHistory, and matchTorrents calls
- Updated unit tests to await buildUserDownloads calls
All 759 tests now pass.
- 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