Release v1.3.0: Multi-source downloads, audio analyzer resilience, mobile improvements

Major Features:
- Multi-source download system (Soulseek/Lidarr with fallback)
- Configurable enrichment speed control (1-5x)
- Mobile touch drag support for seek sliders
- iOS PWA media controls (Control Center, Lock Screen)
- Artist name alias resolution via Last.fm
- Circuit breaker pattern for audio analysis

Critical Fixes:
- Audio analyzer stability (non-ASCII, BrokenProcessPool, OOM)
- Discovery system race conditions and import failures
- Radio decade categorization using originalYear
- LastFM API response normalization
- Mood bucket infinite loop prevention

Security:
- Bull Board admin authentication
- Lidarr webhook signature verification
- JWT token expiration and refresh
- Encryption key validation on startup

Closes #2, #6, #9, #13, #21, #26, #31, #34, #35, #37, #40, #43
This commit is contained in:
Your Name
2026-01-06 20:07:33 -06:00
parent 8fe151a0d1
commit cc8d0f6969
242 changed files with 20562 additions and 7725 deletions

View File

@@ -370,6 +370,29 @@ function DownloadJobItem({
}
};
const getSourceColor = () => {
if (!job.metadata?.currentSource) return "text-white/60";
switch (job.metadata.currentSource) {
case "lidarr":
return "text-purple-400";
case "soulseek":
return "text-teal-400";
default:
return "text-white/60";
}
};
const getStatusText = () => {
if (job.metadata?.statusText) {
return job.metadata.statusText;
}
// Fallback for backward compatibility
if (job.status === "processing" || job.status === "pending") {
return "Processing";
}
return null;
};
const handleDelete = async () => {
try {
setIsDeleting(true);
@@ -397,7 +420,7 @@ function DownloadJobItem({
<p className="text-sm font-medium text-white truncate">
{job.subject}
</p>
<div className="flex items-center gap-2 mt-1">
<div className="flex items-center gap-2 mt-1 flex-wrap">
<span
className={cn(
"text-xs font-medium capitalize",
@@ -406,6 +429,19 @@ function DownloadJobItem({
>
{job.status}
</span>
{getStatusText() && (
<>
<span className="text-xs text-white/40"></span>
<span
className={cn(
"text-xs font-medium",
getSourceColor()
)}
>
{getStatusText()}
</span>
</>
)}
<span className="text-xs text-white/40"></span>
<span className="text-xs text-white/40 capitalize">
{job.type}
@@ -457,13 +493,48 @@ function DownloadJobItemCompact({
}
};
const getSourceColor = () => {
if (!job.metadata?.currentSource) return "text-white/60";
switch (job.metadata.currentSource) {
case "lidarr":
return "text-purple-400";
case "soulseek":
return "text-teal-400";
default:
return "text-white/60";
}
};
const getStatusText = () => {
if (job.metadata?.statusText) {
return job.metadata.statusText;
}
// Fallback for backward compatibility
if (job.status === "processing" || job.status === "pending") {
return "Processing";
}
return null;
};
return (
<div className="px-3 py-2 flex items-center gap-2">
<div className="flex-shrink-0">{getStatusIcon()}</div>
<p className="flex-1 text-xs font-medium text-white truncate">
{job.subject}
</p>
<span className="text-[10px] text-white/40 capitalize">
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-white truncate">
{job.subject}
</p>
{getStatusText() && (
<p
className={cn(
"text-[10px] font-medium",
getSourceColor()
)}
>
{getStatusText()}
</p>
)}
</div>
<span className="text-[10px] text-white/40 capitalize shrink-0">
{job.status}
</span>
</div>