/** * dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx — planet evolution. * * Thin shell over TopicEvolutionGraph: fetches GET /api/wiki/evolution via * useApi and refetches when a `wiki_rebuilt` SSE event lands (emitted by the * extension after a topic-model rebuild), so the graph + scrubber stay live * between polls. No data is fabricated — only the contract fields are used. * * Styling: Tailwind + shadcn (Card). No legacy CSS classes. */ import type React from "react"; import { useCallback, useEffect, useMemo } from "react"; import { useApi } from "../../hooks/useApi"; import { useSSE } from "../../hooks/useSSE"; import { fetchTopicEvolution } from "../../api/client"; import type { TopicEvolutionResponse, SseEvent } from "@contracts"; import { Card, CardContent } from "../../components/ui/card"; import TopicEvolutionGraph from "./TopicEvolutionGraph"; function isWikiRebuilt(e: SseEvent): e is Extract { return e.type === "wiki_rebuilt"; } export default function TopicEvolutionView(): React.ReactElement { const { data, error, loading, refetch } = useApi( useCallback(() => fetchTopicEvolution(), []), { pollInterval: 60_000 }, ); const { events } = useSSE(); const lastRebuiltTs = useMemo(() => { const rebuilt = events.filter(isWikiRebuilt); if (rebuilt.length === 0) return null; return rebuilt[rebuilt.length - 1].ts; }, [events]); useEffect(() => { if (lastRebuiltTs != null) refetch(); }, [lastRebuiltTs, refetch]); if (error && !data) { return (
Error loading evolution graph: {error.message}
); } if (loading && !data) { return (
Loading evolution graph…
); } return (

Topic evolution

{data ? ( ) : (

No data yet.

)}
); }