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>
185 lines
5.5 KiB
JavaScript
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');
|
|
}
|
|
});
|
|
});
|
|
})();
|