From 6acfdf6711da6119eea5c4d3bb34c25cdfeb9b35 Mon Sep 17 00:00:00 2001 From: jack-stormentswe Date: Thu, 14 May 2026 15:00:07 +0200 Subject: [PATCH] fix(watchlist): prevent phantom miners from leaking into watchlist via /miners/details with an unknown id --- src/pages/MinerDetailsPage.tsx | 20 +++++++++++---- src/pages/WatchlistPage.tsx | 45 +++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/pages/MinerDetailsPage.tsx b/src/pages/MinerDetailsPage.tsx index 610fc07f..62a6e636 100644 --- a/src/pages/MinerDetailsPage.tsx +++ b/src/pages/MinerDetailsPage.tsx @@ -15,6 +15,7 @@ import { SEO, } from '../components'; import { WatchlistButton } from '../components/common'; +import { useMinerStats } from '../api'; type ViewMode = 'prs' | 'issues'; @@ -84,6 +85,13 @@ const MinerDetailsPage: React.FC = () => { setSearchParams(p, { replace: true }); }; + const { data: minerStats, isLoading: isLoadingMinerStats } = useMinerStats( + githubId ?? '', + ); + // Only show the star once we've confirmed the miner exists — otherwise + // users can pin phantom entries via /miners/details?githubId=. + const minerExists = !isLoadingMinerStats && !!minerStats; + if (!githubId) { return ; } @@ -131,11 +139,13 @@ const MinerDetailsPage: React.FC = () => { }} > - + {minerExists && ( + + )} = ({ const MinersList: React.FC<{ itemKeys: string[] }> = ({ itemKeys }) => { const { data: allMinersStats, isLoading } = useAllMiners(); + const { remove } = useWatchlist('miners'); const watchedSet = useMemo(() => new Set(itemKeys), [itemKeys]); const minerStats = useMemo(() => { @@ -933,8 +934,50 @@ const MinersList: React.FC<{ itemKeys: string[] }> = ({ itemKeys }) => { })); }, [allMinersStats, watchedSet]); + const unresolvedIds = useMemo(() => { + if (isLoading || !allMinersStats) return []; + const known = new Set(allMinersStats.map((m) => m.githubId)); + return itemKeys.filter((id) => !known.has(id)); + }, [allMinersStats, isLoading, itemKeys]); + + const handleRemoveUnresolved = useCallback(() => { + unresolvedIds.forEach((id) => remove(id)); + }, [unresolvedIds, remove]); + return ( - + + {unresolvedIds.length > 0 && ( + ({ + p: 1.5, + display: 'flex', + alignItems: { xs: 'flex-start', sm: 'center' }, + justifyContent: 'space-between', + gap: 1.5, + flexDirection: { xs: 'column', sm: 'row' }, + backgroundColor: alpha(theme.palette.status.warningOrange, 0.08), + border: `1px solid ${alpha(theme.palette.status.warningOrange, 0.3)}`, + })} + elevation={0} + > + + {unresolvedIds.length} watched{' '} + {unresolvedIds.length === 1 ? 'miner' : 'miners'} could not be + loaded (the account may not be tracked by Gittensor). + + + + )}