diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index 303a4ae..633f0ed 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -146,7 +146,15 @@ export const useAddWatchlistInstrument = () => { export const useInstrumentCatalogIds = () => useQuery({ queryKey: ['instrument-analysis-catalog'], - queryFn: () => api.get('/instruments/').then(r => new Set((r.data ?? []).map((i: any) => String(i.id).toUpperCase()))), + // No trailing slash: the backend route is registered at "/api/instruments" exactly + // (router.get("")) — a trailing slash still resolves via FastAPI's redirect_slashes, + // but calling the exact path avoids depending on that 307 round-trip surviving the + // nginx proxy_pass unchanged. + queryFn: () => api.get('/instruments').then(r => { + const ids = new Set((r.data ?? []).map((i: any) => String(i.id).toUpperCase())) + console.debug('[useInstrumentCatalogIds] loaded', ids.size, 'catalog ids:', Array.from(ids)) + return ids + }), staleTime: 10 * 60_000, }) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index e1a3054..861dc0e 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState, useRef, useLayoutEffect } from 'react' +import { useMemo, useState, useRef, useLayoutEffect, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { useGeoRiskScore, useAllQuotes, @@ -132,7 +132,7 @@ export default function Dashboard() { const { data: geoNews } = useGeoNews() const { data: watchlistItems } = useInstrumentsWatchlist() const { data: watchlistQuotesData } = useInstrumentsWatchlistQuotes() - const { data: instrumentCatalogIds } = useInstrumentCatalogIds() + const { data: instrumentCatalogIds, isError: instrumentCatalogError, error: instrumentCatalogErrorObj } = useInstrumentCatalogIds() const [ecoImpactFilter, setEcoImpactFilter] = useState>(new Set(['high', 'medium', 'low'])) const [ecoCurrencyFilter, setEcoCurrencyFilter] = useState>(new Set(ECO_CURRENCY_FILTERS)) const [ecoShowNextWeek, setEcoShowNextWeek] = useState(false) @@ -145,6 +145,24 @@ export default function Dashboard() { const { data: waveletSignalsData } = useWaveletWatchlistSignals() const { data: saxoIvWatchlistData } = useSaxoIvWatchlist() + // Diagnostic for the Watchlist card's "open in Instrument Analysis" arrow/double-click: + // logs whether the catalog query succeeded and how each watchlist ticker resolves + // against it, so a missing arrow can be traced to "catalog query failed" vs. "ticker + // genuinely isn't registered in instruments.json" without needing a debugger. + useEffect(() => { + if (instrumentCatalogError) { + console.error('[Watchlist] instrument catalog query failed:', instrumentCatalogErrorObj) + return + } + if (!instrumentCatalogIds || !watchlistQuotesData) return + const items: any[] = (watchlistQuotesData as any)?.items ?? [] + console.debug('[Watchlist] catalog ids:', Array.from(instrumentCatalogIds as Set)) + console.debug('[Watchlist] ticker -> catalogId resolution:', items.map((it: any) => ({ + ticker: it.ticker, + resolved: resolveCatalogId(it.ticker, instrumentCatalogIds as Set) ?? null, + }))) + }, [instrumentCatalogIds, instrumentCatalogError, instrumentCatalogErrorObj, watchlistQuotesData]) + // Historical PnL curve (realized, from closed portfolio positions) + latest VaR snapshot const { data: pnlHistoryData } = usePnlHistory() const { data: latestVarData } = useQuery({