# Sciencecast Replay Viewer Stub

Embed this snippet in a Markdown cell (or any static page) to preview the replay timeline generated by the pipeline. It fetches the JSON timeline and renders a minimal list of events with timestamps.

```html
<div id="sciencecast-viewer"></div>
<script type="module">
  async function fetchReceiptIndex() {
    try {
      const resp = await fetch('../code/receipts/index.json');
      if (!resp.ok) {
        return null;
      }
      const index = await resp.json();
      return (index.receipts && index.receipts.length > 0) ? index.receipts[0] : null;
    } catch (error) {
      console.warn('Unable to load receipt index', error);
      return null;
    }
  }

  function resolveReceiptUrl(path) {
    if (!path) return null;
    if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('vigil://')) {
      return path;
    }
    if (path.startsWith('app/')) {
      return `../../${path.slice(4)}`;
    }
    return path;
  }

  async function loadTimeline() {
    const response = await fetch('./main_pipeline_replay.json');
    if (!response.ok) {
      document.getElementById('sciencecast-viewer').textContent = 'Timeline not found. Run `uv run run` to regenerate it.';
      return;
    }

    const [timeline, receiptEntry] = await Promise.all([
      response.json(),
      fetchReceiptIndex(),
    ]);
    const container = document.getElementById('sciencecast-viewer');
    const heading = document.createElement('h3');
    heading.textContent = `Replay — ${timeline.metadata.pipeline}`;
    container.appendChild(heading);

    const meta = document.createElement('p');
    meta.textContent = `Run ${timeline.metadata.run_id} · Generated ${new Date(timeline.metadata.generated_at).toLocaleString()}`;
    container.appendChild(meta);

    if (receiptEntry) {
      const receiptBlock = document.createElement('p');
      const receiptLink = resolveReceiptUrl(receiptEntry.path);
      if (receiptLink) {
        const receiptAnchor = document.createElement('a');
        receiptAnchor.href = receiptLink;
        receiptAnchor.textContent = 'Receipt';
        receiptAnchor.target = '_blank';
        receiptAnchor.rel = 'noopener noreferrer';
        receiptBlock.appendChild(receiptAnchor);
      } else {
        receiptBlock.textContent = `Receipt: ${receiptEntry.path}`;
      }
      const anchorInfo = receiptEntry.anchor;
      receiptBlock.appendChild(document.createTextNode(' · '));
      if (anchorInfo && anchorInfo.proofUrl) {
        const proofLink = document.createElement('a');
        proofLink.href = anchorInfo.proofUrl;
        proofLink.textContent = 'Anchor proof';
        proofLink.target = '_blank';
        proofLink.rel = 'noopener noreferrer';
        receiptBlock.appendChild(proofLink);
      } else {
        const pending = document.createElement('span');
        pending.textContent = 'Anchor proof pending';
        receiptBlock.appendChild(pending);
      }
      container.appendChild(receiptBlock);
    }

    const list = document.createElement('ol');
    for (const event of timeline.events) {
      const item = document.createElement('li');
      const offset = event.offset_seconds.toFixed(1).padStart(4, ' ');
      item.innerHTML = `<strong>[${offset}s] ${event.label}</strong><br>${event.narration}`;
      if (event.details) {
        const details = document.createElement('pre');
        details.textContent = JSON.stringify(event.details, null, 2);
        item.appendChild(details);
      }
      list.appendChild(item);
    }
    container.appendChild(list);
  }

  loadTimeline().catch((error) => {
    console.error('Failed to load Sciencecast timeline', error);
  });
</script>
```
