From c8dd7176faab6b502725616b3f1f3abff12e044f Mon Sep 17 00:00:00 2001 From: k Date: Sat, 30 May 2026 03:23:03 +0800 Subject: [PATCH] feat(ui): one-click 'Hide off-topic' toggle on the Trump feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets the user collapse every post the AI judged off-topic (not crypto-related, never scored) so the feed shows only real signals. - PostCards exports isAiScored(post) as the single source of truth for what counts as noise — the card de-emphasis and the list filter now share it, so they can never disagree. - TrumpPageClient: 'Hide off-topic (N)' toggle in the filter row (only shown when N>0), with live noise count. Resets to page 1 on toggle. tsc + build clean. Co-Authored-By: Claude Sonnet 4.6 --- app/[locale]/trump/TrumpPageClient.tsx | 28 ++++++++++++++++++++++++-- components/dashboard/PostCards.tsx | 22 ++++++++++++-------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/[locale]/trump/TrumpPageClient.tsx b/app/[locale]/trump/TrumpPageClient.tsx index 9c2a2d1..52d61ad 100644 --- a/app/[locale]/trump/TrumpPageClient.tsx +++ b/app/[locale]/trump/TrumpPageClient.tsx @@ -5,7 +5,7 @@ import { useLocale } from 'next-intl' import type { TrumpPost } from '@/types' import { getPosts } from '@/lib/api' import { swrFetch } from '@/lib/cache' -import PostRow from '@/components/dashboard/PostCards' +import PostRow, { isAiScored } from '@/components/dashboard/PostCards' import SystemControl from '@/components/signals/SystemControl' import PageHint from '@/components/ui/PageHint' import InfoTip from '@/components/ui/InfoTip' @@ -29,6 +29,7 @@ export default function TrumpSignalPage({ initialPosts = null }: TrumpSignalPage const [loadErr, setLoadErr] = useState('') const [sentFilter, setSentFilter] = useState('all') const [sigFilter, setSigFilter] = useState('all') + const [hideNoise, setHideNoise] = useState(false) const [page, setPage] = useState(1) @@ -49,13 +50,19 @@ export default function TrumpSignalPage({ initialPosts = null }: TrumpSignalPage [posts], ) + const noiseCount = useMemo( + () => trumpPosts.filter(p => !isAiScored(p)).length, + [trumpPosts], + ) + const filtered = useMemo(() => trumpPosts.filter(p => { + if (hideNoise && !isAiScored(p)) return false if (sentFilter !== 'all' && p.sentiment !== sentFilter) return false if (sigFilter === 'actionable' && p.signal !== 'buy' && p.signal !== 'short') return false if (sigFilter === 'buy' && p.signal !== 'buy') return false if (sigFilter === 'short' && p.signal !== 'short') return false return true - }), [trumpPosts, sentFilter, sigFilter]) + }), [trumpPosts, sentFilter, sigFilter, hideNoise]) const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)) const safePage = Math.min(page, totalPages) @@ -116,6 +123,23 @@ export default function TrumpSignalPage({ initialPosts = null }: TrumpSignalPage {/* Sentiment filter */}
+ {/* One-click collapse of off-topic (non-crypto, un-scored) posts. */} + {noiseCount > 0 && ( + + )} Sentiment ): boolean { + return (post.ai_confidence ?? 0) > 0 || !!post.ai_reasoning +} + function fmtPct(n: number | null | undefined) { if (n == null || isNaN(n)) return '—' // null = window not yet closed const s = n.toFixed(2) + '%' @@ -102,14 +112,10 @@ const PostRow = memo(function PostRow({ post, selected, onClick }: PostRowProps) onClick?.() } - // Did this post actually get an AI score, or was it filtered as off-topic - // noise before any AI call? Trump posts mostly aren't crypto-related, so the - // entry filter / AI marks them relevant=false with confidence 0 and no - // reasoning. Showing "AI confidence 0%" + an empty bar for those is - // misleading — it reads as "AI looked and had zero confidence" when really - // "this was skipped as not crypto-relevant". Treat a post as AI-scored only - // when it has a real confidence or reasoning. - const aiScored = post.ai_confidence > 0 || !!post.ai_reasoning + // Off-topic Trump posts (most of them) are filtered before any AI call, so + // they carry confidence 0 + no reasoning. Treat those as un-scored noise and + // de-emphasise them. Shared helper so list-level "collapse off-topic" agrees. + const aiScored = isAiScored(post) return (