feat(ui): one-click 'Hide off-topic' toggle on the Trump feed

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 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-30 03:23:03 +08:00
parent 3f35a3fba2
commit c8dd7176fa
2 changed files with 40 additions and 10 deletions
+26 -2
View File
@@ -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<SentimentFilter>('all')
const [sigFilter, setSigFilter] = useState<SignalFilter>('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 */}
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
{/* One-click collapse of off-topic (non-crypto, un-scored) posts. */}
{noiseCount > 0 && (
<button
onClick={() => { setHideNoise(v => !v); setPage(1) }}
title="Collapse posts the AI judged off-topic (not crypto-related)"
style={{
padding: '4px 10px', borderRadius: 6,
border: '1px solid var(--line)',
background: hideNoise ? 'var(--ink)' : 'transparent',
color: hideNoise ? 'var(--bg)' : 'var(--ink-3)',
fontSize: 11, cursor: 'pointer', fontWeight: 600,
marginRight: 4,
}}
>
{hideNoise ? `✓ Off-topic hidden (${noiseCount})` : `🙈 Hide off-topic (${noiseCount})`}
</button>
)}
<span style={{ fontSize: 11, color: 'var(--ink-4)', marginRight: 4 }}>
Sentiment
<InfoTip