stemedb/docs/presentations/reveal/renderer.js
jordan 3cfaa1e1d3 feat: Complete Phase 1 (The Spine) - storage foundation
Phase 1 delivers the complete durability and storage layer:

- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
  fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
  aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
  and multi-cycle durability

New crates: stemedb-wal, stemedb-storage, stemedb-ingest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:15:34 -07:00

185 lines
5.5 KiB
JavaScript

/**
* Episteme Presentation Renderer
*
* Reads JSON data and renders Reveal.js slides
*/
(async function () {
// Load presentation data
const dataUrl = window.PRESENTATION_DATA_URL || '../generated/agile-agent-team.json';
let data;
try {
const response = await fetch(dataUrl);
data = await response.json();
} catch (error) {
console.error('Failed to load presentation data:', error);
document.getElementById('slides').innerHTML = `
<section class="slide-title">
<h1>Error Loading Presentation</h1>
<p class="subtitle">Could not load ${dataUrl}</p>
<p>Run: <code>npx ts-node scripts/generate.ts data/agile-agent-team.yaml</code></p>
</section>
`;
Reveal.initialize();
return;
}
const slidesContainer = document.getElementById('slides');
// Actor icon mapping (simple initials for now)
function getActorInitials(actor) {
return actor.label.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
}
// Format JSON for display
function formatData(data) {
if (!data) return '';
return JSON.stringify(data, null, 2)
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
.replace(/: "([^"]+)"/g, ': <span class="json-string">"$1"</span>')
.replace(/: (\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
.replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
.replace(/: (null)/g, ': <span class="json-null">$1</span>');
}
// Render slides
function renderSlides(slides) {
let sectionNumber = 0;
for (const slide of slides) {
const section = document.createElement('section');
switch (slide.type) {
case 'title':
section.classList.add('title-slide');
section.innerHTML = `
<div class="slide-title">
<h1 class="brand">stemedb</h1>
</div>
`;
break;
case 'section-title':
sectionNumber++;
section.innerHTML = `
<div class="slide-section-title">
<span class="section-number">Section ${sectionNumber}</span>
<h2>${slide.title}</h2>
<p class="subtitle">${slide.subtitle}</p>
<p class="description">${slide.description.trim()}</p>
</div>
`;
break;
case 'sequence':
section.setAttribute('data-auto-animate', '');
section.innerHTML = renderSequenceSlide(slide, data.actors);
break;
}
slidesContainer.appendChild(section);
}
}
// Render sequence slide
function renderSequenceSlide(slide, allActors) {
const actors = slide.actors;
const steps = slide.steps;
// Actor columns
const actorHtml = Object.values(actors).map(actor => `
<div class="actor">
<div class="actor-icon" style="background-color: ${actor.color}">
${getActorInitials(actor)}
</div>
<span class="actor-label">${actor.label}</span>
</div>
`).join('');
// Step cards as fragments
const stepsHtml = steps.map((step, index) => {
const statusClass = step.danger ? 'danger' : (step.success ? 'success' : (step.warning ? 'warning' : ''));
const fromActor = step.fromActor || actors[step.from] || { label: step.from };
const toActor = step.toActor || actors[step.to] || { label: step.to };
return `
<div class="step-card ${statusClass} fragment" data-fragment-index="${index}">
<div class="step-index">${index + 1}</div>
<div class="step-content">
<div class="step-header">
<div class="step-actors">
<span class="step-from">${fromActor.label}</span>
<span class="step-arrow">→</span>
<span class="step-to">${toActor.label}</span>
</div>
<span class="step-action">${step.action}</span>
</div>
<div class="step-label">${step.label}</div>
${step.note ? `<div class="step-note">${step.note}</div>` : ''}
${step.data ? `<div class="step-data"><pre>${formatData(step.data)}</pre></div>` : ''}
${step.callout ? `<div class="step-callout">${step.callout}</div>` : ''}
</div>
</div>
`;
}).join('');
return `
<div class="slide-sequence">
<div class="header">
<h3>${slide.title}</h3>
</div>
<div class="actors-row">
${actorHtml}
</div>
<div class="steps-container">
${stepsHtml}
</div>
</div>
`;
}
// Render slides
renderSlides(data.slides);
// Initialize Reveal.js
Reveal.initialize({
hash: true,
history: true,
controls: true,
progress: true,
center: true,
transition: 'fade',
transitionSpeed: 'fast',
// Keyboard shortcuts
keyboard: {
// Arrow keys for fragments within slide
39: 'next', // right
37: 'prev', // left
},
});
// Make step cards visible as fragments appear
Reveal.on('fragmentshown', event => {
event.fragment.classList.add('visible');
});
Reveal.on('fragmenthidden', event => {
event.fragment.classList.remove('visible');
});
// Show all fragments on slide enter if coming from later slide
Reveal.on('slidechanged', event => {
const currentSlide = event.currentSlide;
const fragments = currentSlide.querySelectorAll('.fragment');
// Reset visibility on slide change
fragments.forEach(frag => {
if (frag.classList.contains('visible')) {
frag.classList.add('visible');
}
});
});
})();