import { useMemo, useState } from 'react' import { Link } from 'react-router-dom' import PublicLayout from '../../components/PublicLayout.jsx' import PageHeader from '../../components/PageHeader.jsx' import { Loading, ErrorState } from '../../components/PageState.jsx' import { useAsync } from '../../lib/useAsync.js' import { useShardFeed } from '../../lib/useShardFeed.js' import { describe, categoryOf, kindLabel, CATEGORIES } from '../../lib/shardEvents.js' import { ago } from '../../lib/format.js' import { api } from '../../api/client.js' // Public activity feed: the full shard event log, filterable by category, with a // live tail that prepends new events as they happen. export default function ShardActivity() { const { loading, error, data } = useAsync(() => api.shard.feed({ limit: 150 })) const { events: live } = useShardFeed({ max: 60 }) const [cat, setCat] = useState('all') // Merge the live tail with the loaded history, de-duped by kind+t, newest first. const merged = useMemo(() => { const seen = new Set() const out = [] for (const e of [...live, ...(data || [])]) { const key = `${e.kind}-${e.t}` if (seen.has(key)) continue seen.add(key) out.push(e) } return out.sort((a, b) => (b.t || 0) - (a.t || 0)) }, [live, data]) const filtered = cat === 'all' ? merged : merged.filter((e) => categoryOf(e.kind) === cat) return (

← Back to shard

{/* Category tabs */}
{CATEGORIES.map((c) => ( ))}
{loading && } {error && } {!loading && !error && ( filtered.length === 0 ? (

Nothing here yet — events will appear as they happen in the world.

) : (
    {filtered.map((e) => (
  • {kindLabel(e.kind)} {describe(e)} {ago(e.t)}
  • ))}
) )}
) }