feat(discovery): acquire recommendations that keep their names
Acquisition ran yt-dlp without --embed-metadata, so every download arrived untagged. The scanner then stored the video id as the title and "Unknown Artist" as the artist, the vetted-candidate tag check rejected the mismatch, and all 18 acquired tracks were hidden and retired. - Pass --embed-metadata so downloads carry real tags. - Let a scan take fallback title/artist from the candidate, for sources that still ship untagged files. - Install Deno alongside yt-dlp: YouTube guards some formats with a JS challenge yt-dlp must execute, and no other runtime is enabled. - Dedupe candidates by artist and title. The (source, external_id) key misses the same song reaching us under two Deezer release ids. Also carries the in-flight discovery work this builds on: the Recommendations page replacing Discover, the discovery source service, and the acquisition spec tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ const NAV_COMMANDS: CommandItem[] = [
|
||||
{ id: 'nav-artists', label: 'Artists', icon: Users, action: () => {}, keywords: ['artists', 'bands'] },
|
||||
{ id: 'nav-genres', label: 'Genres', icon: Tag, action: () => {}, keywords: ['genres', 'tags', 'categories'] },
|
||||
{ id: 'nav-vibe', label: 'Vibe', description: 'Endless recommendations', icon: Zap, action: () => {}, keywords: ['vibe', 'recommendations', 'radio'] },
|
||||
{ id: 'nav-discover', label: 'Discover', description: 'Browse by genre', icon: Compass, action: () => {}, keywords: ['discover', 'explore'] },
|
||||
{ id: 'nav-recommendations', label: 'Found', description: 'Discovered tracks and their fate', icon: Compass, action: () => {}, keywords: ['found', 'discovery', 'recommendations', 'probation', 'new releases'] },
|
||||
{ id: 'nav-quarantine', label: 'Quarantine', icon: ShieldAlert, action: () => {}, keywords: ['quarantine', 'disliked', 'trash'] },
|
||||
{ id: 'nav-jobs', label: 'Jobs', description: 'Background tasks', icon: Terminal, action: () => {}, keywords: ['jobs', 'tasks', 'queue'] },
|
||||
{ id: 'nav-settings', label: 'Settings', icon: Settings, action: () => {}, keywords: ['settings', 'preferences', 'config'] },
|
||||
@@ -63,7 +63,7 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
|
||||
'nav-artists': '/artists',
|
||||
'nav-genres': '/genres',
|
||||
'nav-vibe': '/vibe',
|
||||
'nav-discover': '/discover',
|
||||
'nav-recommendations': '/recommendations',
|
||||
'nav-quarantine': '/quarantine',
|
||||
'nav-jobs': '/jobs',
|
||||
'nav-settings': '/settings',
|
||||
|
||||
@@ -35,7 +35,7 @@ const NAV_GROUPS: NavGroup[] = [
|
||||
label: 'AI',
|
||||
items: [
|
||||
{ to: '/vibe', icon: Zap, label: 'Vibe' },
|
||||
{ to: '/discover', icon: Compass, label: 'Discover' },
|
||||
{ to: '/recommendations', icon: Compass, label: 'Found' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,8 +17,8 @@ const PAGE_TITLES: Record<string, string> = {
|
||||
'/albums': 'Albums',
|
||||
'/artists': 'Artists',
|
||||
'/genres': 'Genres',
|
||||
'/recommendations': 'Found',
|
||||
'/vibe': 'Vibe',
|
||||
'/discover': 'Discover',
|
||||
'/search': 'Search',
|
||||
'/settings': 'Settings',
|
||||
'/quarantine': 'Quarantine',
|
||||
|
||||
@@ -22,9 +22,11 @@ export function VibeAura({ profile, ambient = false }: { profile: VibeProfile; a
|
||||
const energy = clamp(profile.energy, 0.5);
|
||||
const novelty = clamp(profile.noveltyHunger ?? profile.explorationCoefficient, 0.3);
|
||||
const { energyLabel, discoveryLabel } = describeProfile(energy, novelty);
|
||||
// Warm range only (amber → gold → orange). The old 205 base put a teal-blue
|
||||
// glow in a warm brown room, which read as a different app's accent.
|
||||
const hue = Math.round(28 + novelty * 30 - energy * 12);
|
||||
// Hue is discovery alone: ember red when the Vibe stays familiar, gold when it
|
||||
// reaches. Energy is deliberately absent — it already drives four motion
|
||||
// channels below, and mixing it in here cancelled half the novelty swing.
|
||||
// Warm range only; the old 205 base put a teal-blue glow in a warm brown room.
|
||||
const hue = Math.round(15 + novelty * 40);
|
||||
const style = {
|
||||
'--vibe-hue': String(hue),
|
||||
'--vibe-pulse': `${(4.8 - energy * 2.3).toFixed(2)}s`,
|
||||
@@ -53,13 +55,47 @@ export function VibeAura({ profile, ambient = false }: { profile: VibeProfile; a
|
||||
);
|
||||
|
||||
if (ambient) {
|
||||
// Displacing clean gradients by animated fractal noise is what separates a
|
||||
// plasma from a blurred blob. baseFrequency is animated in SMIL rather than
|
||||
// CSS because no CSS property reaches inside an SVG filter primitive.
|
||||
return (
|
||||
<div
|
||||
className="vibe-aura-blob pointer-events-none absolute left-1/2 top-[28%] z-0 h-[190px] w-[230px] -translate-x-1/2 -translate-y-1/2 opacity-40 mix-blend-screen sm:h-[400px] sm:w-[480px] sm:opacity-70"
|
||||
className="vibe-aura-blob pointer-events-none absolute z-0 h-[380px] w-[420px] -translate-x-1/2 -translate-y-1/2 opacity-85 mix-blend-screen sm:h-[560px] sm:w-[660px] sm:opacity-95"
|
||||
style={style}
|
||||
role="img"
|
||||
aria-label={`Current Vibe: ${energyLabel} energy and ${discoveryLabel} discovery`}
|
||||
/>
|
||||
>
|
||||
<svg aria-hidden className="absolute h-0 w-0">
|
||||
<filter id="vibe-plasma" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.009 0.014"
|
||||
numOctaves={3}
|
||||
seed={7}
|
||||
result="noise"
|
||||
>
|
||||
<animate
|
||||
attributeName="baseFrequency"
|
||||
dur={`${(18 - energy * 8).toFixed(1)}s`}
|
||||
values="0.009 0.014; 0.021 0.007; 0.009 0.014"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="noise"
|
||||
scale={String(Math.round(46 + energy * 70))}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</filter>
|
||||
</svg>
|
||||
<div className="vibe-aura-stack">
|
||||
<div className="vibe-aura-layer vibe-aura-rays-layer" />
|
||||
<div className="vibe-aura-layer vibe-aura-swirl-layer" />
|
||||
<div className="vibe-aura-layer vibe-aura-core-layer" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user