/** * dashboard-client/src/tabs/WikiTab/WikiPage.tsx — single wiki topic page. * * Renders the extractive summary, key member memories, the member provenance * table (memory → session/assigned-at/method), related topics (resolved from * the wiki index), and the per-topic memory timeline. Mutations * (rename/merge/split) delegate to WikiPageControls; on mutation success the * page + index are refetched. * * Styling: Tailwind + shadcn (Card, Badge, Button) — no legacy CSS. */ import type React from "react"; import { useCallback, useMemo, useState } from "react"; import { useApi } from "../../hooks/useApi"; import { fetchWikiTopic, fetchWikiIndex } from "../../api/client"; import type { WikiPageResponse, WikiIndexResponse } from "@contracts"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { Card, CardContent } from "../../components/ui/card"; import WikiPageControls from "./WikiPageControls"; import TopicTimeline from "./TopicTimeline"; interface WikiPageProps { topicId: string; onBack: () => void; } function fmtTs(ms: number): string { if (!ms) return "—"; return new Date(ms).toLocaleString(); } const METHOD_LABELS: Record = { "kmeans+tfidf": "kmeans", merge: "merge", split: "split", manual: "manual", }; export default function WikiPage({ topicId, onBack, }: WikiPageProps): React.ReactElement { const [dirty, setDirty] = useState(0); const bump = useCallback(() => setDirty((d) => d + 1), []); const { data: page, error, loading } = useApi( useCallback(() => fetchWikiTopic(topicId), [topicId, dirty]), {}, ); const { data: index } = useApi( useCallback(() => fetchWikiIndex(), []), {}, ); const labelOf = useMemo(() => { const map = new Map(); for (const t of index?.topics ?? []) map.set(t.id, t.label); return map; }, [index]); if (error && !page) { return (
Error loading topic: {error.message}
); } if (loading && !page) { return (
Loading topic…
); } if (!page) { return (
No topic data available.
); } const otherTopics = (index?.topics ?? []) .filter((t) => t.id !== topicId) .map((t) => ({ id: t.id, label: t.label })); return (

{page.topic.label}

{page.topic.edited && edited} {page.provenance.length} memories
{page.summary && (

Summary

{page.summary}

)} {page.keyMemories.length > 0 && (

Key memories

    {page.keyMemories.map((m) => (
  • {m.content}

    {m.memoryId} · {fmtTs(m.timestamp)}

  • ))}
)} {page.provenance.length > 0 && (

Provenance

{page.provenance.map((p) => ( ))}
Memory Session Assigned Method
{p.memoryId} {p.sessionId || "unknown"} {fmtTs(p.assignedAt)} {METHOD_LABELS[p.method] ?? p.method}
)} {page.relatedTopicIds.length > 0 && (

Related topics

{page.relatedTopicIds.map((id) => ( {labelOf.get(id) ?? id} ))}
)}
); }