// Live contribution stream — the last N submissions with their scores. // // Distinct from the Metagraph tab: that shows accepted totals per miner, this // shows individual submissions as they land, including the ones that were // REJECTED. A rejected delta never enters the graph, so it cannot be recovered // from any graph query — this feed is the only place it is visible. // // The source buffer is in-memory on central and resets when it restarts, so an // empty feed means "nothing since the last restart", not "nothing happening". // The empty state says so rather than looking broken. window.ContributionFeed = function ContributionFeed() { const [rows, setRows] = React.useState([]); const [meta, setMeta] = React.useState(null); const [loading, setLoading] = React.useState(true); const [live, setLive] = React.useState(true); const [filter, setFilter] = React.useState('all'); // all | accepted | rejected React.useEffect(() => { let alive = true; async function load() { const { contributions, feed } = await window.api.fetchRecentContributions(100); if (!alive) return; setRows(contributions); setMeta(feed); setLoading(false); } load(); if (!live) return () => { alive = false; }; const t = setInterval(load, 15000); return () => { alive = false; clearInterval(t); }; }, [live]); const filtered = React.useMemo(() => rows.filter((r) => { if (filter === 'accepted') return r.accepted; if (filter === 'rejected') return !r.accepted; return true; }), [rows, filter]); function ago(iso) { const t = Date.parse(iso); if (Number.isNaN(t)) return '—'; const s = Math.max(0, Math.floor((Date.now() - t) / 1000)); if (s < 60) return s + 's ago'; if (s < 3600) return Math.floor(s / 60) + 'm ago'; if (s < 86400) return Math.floor(s / 3600) + 'h ago'; return Math.floor(s / 86400) + 'd ago'; } // Score colour tracks the merge decision rather than a fixed threshold, so // the column always agrees with the STATUS badge beside it. function scoreColor(r) { if (!r.accepted) return 'var(--red)'; if (r.score >= 0.7) return 'var(--green)'; if (r.score >= 0.45) return 'var(--yellow)'; return 'var(--fg-dim)'; } const grid = '92px 110px 130px 1fr 150px 110px'; const accepted = rows.filter((r) => r.accepted).length; return (

Contribution Stream

Individual submissions as validators merge them, newest first, with the score each received. Rejected submissions are shown too — they score below the merge threshold and never enter the graph, so this is the only place they appear.

{rows.length}
Shown
{accepted}
Accepted
{rows.length - accepted}
Rejected
WHEN
MINER
SCORE
COMPONENTS
ITEMS LANDED
STATUS
{loading && (
Loading…
)} {!loading && filtered.length === 0 && (
No submissions buffered yet. {' '}The stream is held in memory and clears when the server restarts, so this fills as new submissions arrive rather than showing past history.
)} {!loading && filtered.map((r, i) => (
{ago(r.at)}
{r.miner_uid === null || r.miner_uid === undefined ? '—' : 'UID ' + r.miner_uid}
{typeof r.score === 'number' ? r.score.toFixed(3) : '—'}
{r.components && Object.keys(r.components).length ? Object.entries(r.components) .map(([k, v]) => k.slice(0, 4) + ' ' + Number(v).toFixed(2)) .join(' · ') : (r.reason || '—')}
{(r.items_total || 0).toLocaleString()} {r.items && r.items_total > 0 && ( {r.items.facts}f/{r.items.relationships}r/{r.items.entities}e )}
{r.accepted ? MERGED : REJECTED}
))}
{meta && (

buffer {meta.buffered}/{meta.capacity} {meta.oldest ? ' · oldest ' + ago(meta.oldest) : ''}

)}
); };