import { useState, useRef, useEffect } from 'react'; import { useNavigate, Link } from '@tanstack/react-router'; import { useTrends, useDocument } from '../api/hooks'; import { TrendArrow, ConfidenceBar, LoadingSpinner, Card } from '../components/ui'; import type { TrendSummary } from '../api/hooks'; const WINDOWS = ['intraday', '1d', '7d', '30d', '90d']; export function TrendsPage() { const navigate = useNavigate(); const [ticker, setTicker] = useState(''); const [debouncedTicker, setDebouncedTicker] = useState(''); const [window, setWindow] = useState(undefined); const inputRef = useRef(null); // Debounce ticker search — only query after 300ms of no typing useEffect(() => { const timer = setTimeout(() => setDebouncedTicker(ticker), 300); return () => clearTimeout(timer); }, [ticker]); const { data, isLoading } = useTrends({ ticker: debouncedTicker || undefined, window, limit: 100 }); if (isLoading) return ; return (

Trends

setTicker(e.target.value.toUpperCase())} className="w-24 rounded-md border border-surface-700 bg-surface-900 px-2 py-1 text-xs text-gray-200 placeholder-gray-500 focus:border-brand-500 focus:outline-none" aria-label="Filter by ticker" />
{WINDOWS.map((w) => ( ))}
{(data ?? []).map((trend) => ( navigate({ to: '/trends/$id', params: { id: trend.id } })} /> ))} {data?.length === 0 &&

No trends found

}
); } function TrendCard({ trend, onClick }: { trend: TrendSummary; onClick: () => void }) { const [expanded, setExpanded] = useState(false); return (
{trend.entity_id}
{trend.window}
Strength
Confidence
Contradiction 0.5 ? 'text-yellow-400' : 'text-gray-400'}`}> {(trend.contradiction_score * 100).toFixed(0)}%
{trend.dominant_catalysts && trend.dominant_catalysts.length > 0 && (
{trend.dominant_catalysts.map((c, i) => ( {c} ))}
)}
{/* Expandable evidence preview */} {(trend.top_supporting_evidence?.length || trend.top_opposing_evidence?.length) && ( )} {expanded && (
{[...new Set(trend.top_supporting_evidence ?? [])].map((e, i) => ( ))} {[...new Set(trend.top_opposing_evidence ?? [])].map((e, i) => ( ))}
)} ); } const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; function EvidenceRef({ id, direction }: { id: string; direction: 'supporting' | 'opposing' }) { const sign = direction === 'supporting' ? '+' : '−'; const color = direction === 'supporting' ? 'text-green-400' : 'text-red-400'; const isUuid = UUID_RE.test(id); const { data: doc } = useDocument(isUuid ? id : undefined); // Pattern IDs like "pattern:META:other:1d" are already readable if (id.startsWith('pattern:')) { const parts = id.split(':'); const label = parts.length >= 4 ? `${parts[1]} ${parts[2]} (${parts[3]})` : id.replace('pattern:', ''); return (
{sign} pattern {label}
); } // UUIDs — show document title if available, otherwise short ID if (isUuid) { const label = doc?.title ?? `doc:${id.slice(0, 8)}…`; return (
{sign}{' '} e.stopPropagation()} > {label}
); } // Fallback — show as-is but truncated return
{sign} {id}
; }