refactor(document): move document domain core to document/ package

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-05 12:39:20 +02:00
parent bb7d872a61
commit e85057bed2
2371 changed files with 385726 additions and 1971 deletions

View File

@@ -0,0 +1,538 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KI als Team, nicht als Werkzeug</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/theme/black.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/monokai.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" />
<style>
/* ── Tokens (same as persona cards) ─────────────────── */
:root {
--bg: #0D1117;
--surface: #161B22;
--surface-2: #1C2128;
--border: #21262D;
--text: #C9D1D9;
--text-muted: #6E7681;
--text-bright:#F0F6FC;
--accent: #22D3EE;
--red: #F85149;
/* reveal overrides */
--r-background-color: var(--bg);
--r-main-color: var(--text);
--r-heading-color: var(--text-bright);
--r-link-color: var(--accent);
--r-link-color-hover: #67E8F9;
--r-main-font: 'Inter', system-ui, sans-serif;
--r-heading-font: 'Inter', system-ui, sans-serif;
--r-code-font: 'JetBrains Mono', monospace;
--r-main-font-size: 36px;
}
/* ── Dot-grid background (persona card pattern) ──────── */
.reveal::before {
content: '';
position: fixed;
inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,.045) 1px, transparent 1px);
background-size: 26px 26px;
pointer-events: none;
z-index: 0;
}
.reveal .slides { z-index: 1; }
/* ── Headings ─────────────────────────────────────────── */
.reveal h1, .reveal h2, .reveal h3 {
font-family: 'Inter', sans-serif;
font-weight: 800;
letter-spacing: -.5px;
text-transform: none;
color: var(--text-bright);
}
.reveal h2 {
font-size: 1.8em;
padding-bottom: .35em;
border-bottom: 1px solid var(--border);
margin-bottom: .6em;
}
.reveal h2::before {
content: '// ';
font-family: 'JetBrains Mono', monospace;
font-size: .7em;
color: var(--accent);
font-weight: 500;
}
.reveal h3 {
font-size: 1.15em;
color: var(--accent);
margin-bottom: .5em;
}
/* ── Body text ───────────────────────────────────────── */
.reveal p, .reveal li { color: var(--text); font-size: .85em; line-height: 1.6; }
.reveal strong { color: var(--text-bright); }
.reveal em { color: var(--text-muted); font-style: italic; }
.reveal small { color: var(--text-muted); font-size: .65em; }
/* ── Links ───────────────────────────────────────────── */
.reveal a {
color: var(--accent);
font-family: 'JetBrains Mono', monospace;
font-size: .75em;
text-decoration: none;
border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.reveal a:hover { color: #67E8F9; border-bottom-color: #67E8F9; }
/* ── Tables ──────────────────────────────────────────── */
.reveal table {
width: 100%;
border-collapse: collapse;
font-size: .72em;
}
.reveal table thead tr {
border-bottom: 1px solid var(--accent);
}
.reveal table th {
font-family: 'JetBrains Mono', monospace;
font-size: .85em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--text-muted);
padding: 6px 12px;
text-align: left;
}
.reveal table td {
padding: 7px 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
vertical-align: top;
}
.reveal table tbody tr:last-child td { border-bottom: none; }
.reveal table tbody tr:hover td { background: rgba(255,255,255,.03); }
/* ── Code blocks ─────────────────────────────────────── */
.reveal pre {
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: 6px;
font-size: .58em;
box-shadow: none;
}
.reveal code:not(pre code) {
font-family: 'JetBrains Mono', monospace;
font-size: .85em;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1px 6px;
color: var(--accent);
}
/* ── Blockquotes ─────────────────────────────────────── */
.reveal blockquote {
background: color-mix(in srgb, var(--accent) 5%, transparent);
border-left: 3px solid var(--accent);
border-radius: 0 6px 6px 0;
padding: 12px 20px;
font-style: italic;
font-size: .8em;
color: var(--text-muted);
box-shadow: none;
width: 100%;
}
.reveal blockquote p { color: var(--text-muted); font-size: 1em; }
/* ── Topbar label (reusable) ─────────────────────────── */
.slide-label {
font-family: 'JetBrains Mono', monospace;
font-size: .45em;
color: var(--text-muted);
letter-spacing: 1px;
display: block;
margin-bottom: .5em;
}
.slide-label .accent { color: var(--accent); }
/* ── Slide number ────────────────────────────────────── */
.reveal .slide-number {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
padding: 2px 7px;
bottom: 12px;
right: 14px;
}
/* ── Progress bar ────────────────────────────────────── */
.reveal .progress { height: 2px; }
.reveal .progress span { background: var(--accent); }
/* ── highlight-row fragment: visible from start, styled on advance ── */
.reveal .fragment.highlight-row { opacity: 1; visibility: inherit; }
.reveal .fragment.highlight-row.visible {
font-weight: 700;
color: #22D3EE !important;
background: rgba(34,211,238,.08) !important;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<!-- ══════════════════════════════════════════════
1 · TITEL
══════════════════════════════════════════════ -->
<section>
<h2>KI als Team, nicht als Werkzeug</h2>
<p><small>Marcel Raddatz · 2026</small></p>
<aside class="notes">
Einstieg: „Die meisten nutzen KI als sehr schnelle Schreibkraft. Ich nutze sie als Team von Spezialisten."
Kein Agenda-Slide — das Workflow-Diagramm kommt nach der Team-Vorstellung.
</aside>
</section>
<!-- ══════════════════════════════════════════════
2 · DAS PROJEKT
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Das Projekt: Familienarchiv</h2>
<p>Handgeschriebene Kurrent- und Sütterlin-Briefe, 18991950</p>
<ul>
<li>Crowd-Transkription durch 60+-Jährige am Laptop</li>
<li>Jüngere Familienmitglieder lesen's am Handy</li>
<li>Solo-Entwickler — kein Team zum Review oder Pair-Programming</li>
</ul>
<aside class="notes">PM-Takeaway: echte Domäne, echte Nutzer, echte Constraints.</aside>
</section>
<section>
<h3>Stack &amp; Umfang</h3>
<table>
<tr><td><strong>Backend</strong></td><td>Spring Boot 4 · Java 21 · JPA · Flyway · Spring Security</td></tr>
<tr><td><strong>Frontend</strong></td><td>SvelteKit 2 / Svelte 5 · TypeScript · Tailwind CSS 4</td></tr>
<tr><td><strong>DB / Storage</strong></td><td>PostgreSQL 16 · MinIO (S3-kompatibel)</td></tr>
<tr><td><strong>OCR</strong></td><td>Python · FastAPI · lernende ML-Modelle für Kurrent &amp; Sütterlin</td></tr>
<tr><td><strong>i18n</strong></td><td>Paraglide.js — de / en / es</td></tr>
<tr><td><strong>Umfang</strong></td><td>40+ UI/UX-Specs · 360+ Gitea-Issues · 7 Personas</td></tr>
</table>
<aside class="notes">Dev-Takeaway: kein Toy-Projekt — voller Stack, Backend und Frontend.</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
3 · DAS PROBLEM MIT "EINFACH FRAGEN"
══════════════════════════════════════════════ -->
<section>
<h2>Das Problem mit „Einfach die KI fragen"</h2>
<table>
<thead><tr><th>Was man tut</th><th>Was schiefläuft</th></tr></thead>
<tbody>
<tr class="fragment"><td>Vage Idee → Code anfordern</td><td>Output passt nicht zu dem, was man wollte</td></tr>
<tr class="fragment"><td>Review in derselben Session</td><td>Befangenheit: genehmigt sich selbst</td></tr>
<tr class="fragment"><td>Die KI einfach fragen ob's gut ist</td><td>Kein echter Widerspruch</td></tr>
<tr class="fragment"><td>Auf Gesprächskontext vertrauen</td><td>Nächste Session startet blank</td></tr>
</tbody>
</table>
<p class="fragment"><em>Das ist ein Workflow-Problem, kein Prompt-Problem.</em></p>
<aside class="notes">
Dieser Slide schafft das Warum für alles was folgt.
Das Publikum erkennt sich in mindestens einer Zeile wieder.
</aside>
</section>
<!-- ══════════════════════════════════════════════
4 · STUFEN DER KI-ENTWICKLUNG
══════════════════════════════════════════════ -->
<section>
<h2>Wo stehst du?</h2>
<table style="font-size:.6em;border-collapse:collapse;width:100%">
<tbody>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">1 — Autocomplete</td><td style="padding:4px 10px">Tab-Completion, eine Zeile vorschlagen</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">2 — Chat / Pair</td><td style="padding:4px 10px">Fragen stellen, Code kopieren, selbst einfügen</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">3 — Agentic Edit</td><td style="padding:4px 10px">KI editiert Dateien direkt — Cursor, Claude Code</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">4 — Spec → Code</td><td style="padding:4px 10px">Mensch schreibt Spec, KI implementiert</td></tr>
<tr class="fragment highlight-row" style="color:#6E7681"><td style="padding:6px 10px;white-space:nowrap">5 — Idea → Spec → Code</td><td style="padding:6px 10px">KI hilft beim Spec-Schreiben — Mensch behält jedes Gate</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">6 — Fully Autonomous</td><td style="padding:4px 10px">KI plant, spezifiziert, implementiert, testet — kein Mensch</td></tr>
</tbody>
</table>
<aside class="notes">
Kurz im Publikum fragen: wer nutzt Autocomplete? Wer Chat? Wer Agentic?
Pointe: Stufe 6 klingt verlockend — aber ohne Gates verliert man Kontrolle und Kontext.
Stufe 5 ist der Sweet Spot: KI als Denkpartner, Mensch als Entscheider.
</aside>
</section>
<!-- ══════════════════════════════════════════════
5 · WORKFLOW ÜBERBLICK
══════════════════════════════════════════════ -->
<section>
<h2>Der Workflow im Überblick</h2>
<pre style="font-size:.42em;overflow:visible"><code class="text" data-trim>
Vage Idee
UI-Exploration ──▶ Spec (Elicit) ──▶ Gitea-Issue
Persona-Review ──▶ discuss(Persona) ─┐
(6 Spezialisten) │
▲ │
└──────────────────────────────┘
Feature-Branch
TDD: Red → Green → Refactor → Commit
Pull Request ──▶ Persona-PR-Review ──▶ Merge
</code></pre>
<aside class="notes">
Roter Faden: wir verfolgen Issue #358 (Stammbaum) durch jeden Schritt.
Jeder folgende Abschnitt zoomt in einen Schritt hinein.
</aside>
</section>
<!-- ══════════════════════════════════════════════
6 · DAS TEAM
══════════════════════════════════════════════ -->
<section>
<h2>Das Team</h2>
<table>
<thead><tr><th>Name</th><th>Rolle</th><th>Kernprinzip</th></tr></thead>
<tbody>
<tr><td>Elicit</td><td>Requirements Engineer</td><td>Schreibt nie Code</td></tr>
<tr><td>Markus Keller</td><td>Architekt</td><td>Boring technology wins</td></tr>
<tr><td>Felix Brandt</td><td>Senior Developer</td><td>Test first. Immer.</td></tr>
<tr><td>Leonie Voss</td><td>UX Design Lead</td><td>Hardest constraint first</td></tr>
<tr><td>Sara Holt</td><td>QA Engineer</td><td>A passing test that was never failing is a lie</td></tr>
<tr><td>Nora „NullX" Steiner</td><td>Security Engineer</td><td>Jeder neue Endpoint braucht <code>@RequirePermission</code></td></tr>
<tr><td>Tobias „tobi" Wendt</td><td>DevOps</td><td>Komplexität ist eine Liability</td></tr>
</tbody>
</table>
<aside class="notes">
Markdown-Dateien in .claude/personas/ — jede mit echtem Namen und Biografie.
Überleitung: jetzt zoomen wir in jede Persona hinein.
</aside>
</section>
<section>
<span class="slide-label"><span class="accent">.claude/personas/</span>developer.md</span>
<h2>Aufbau einer Persona</h2>
<div style="display:grid;grid-template-columns:auto 1fr;gap:1.6em;margin-top:.5em;align-items:start">
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;line-height:1.85;border-right:1px solid var(--border);padding-right:1.5em;white-space:nowrap">
<div style="color:var(--text-muted)">## Your Identity</div>
<div style="color:var(--text-muted)">## Readable &amp; Clean Code</div>
<div style="color:var(--text-muted)">## Reliable Code</div>
<div style="color:var(--text-muted)">## Modern Code</div>
<div style="color:var(--accent);font-weight:700">## Secure Code ◀</div>
<div style="color:var(--text-muted)">## Testable Code</div>
<div style="color:var(--text-muted)">## How You Work</div>
<div style="color:var(--text-muted)">## Relationships</div>
<div style="color:var(--text-muted)">## Your Tone</div>
</div>
<div style="background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:12px 18px;display:flex;flex-direction:column;gap:.55em">
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;font-weight:700;color:var(--accent)">## Secure Code</div>
<div>
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.3em">### General — projektunabhängig</div>
<p style="font-size:.58em;line-height:1.6;color:var(--text-muted);font-style:italic">
Secure code treats all external input as hostile. Authentication and authorization
are enforced via framework annotations, not scattered if-statements.
<strong style="color:var(--text-bright);font-style:normal">Error messages reveal nothing about the implementation.</strong>
</p>
</div>
<div>
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.35em">### per Stack — projektspezifisch (Frontend · Java · Python)</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5em;font-size:.58em">
<div style="background:rgba(52,211,153,.07);border:1px solid rgba(52,211,153,.25);border-radius:5px;padding:7px 12px">
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--accent);font-weight:700;margin-bottom:.25em">✓ DO</div>
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> <code>@RequirePermission</code> on every write endpoint</div>
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Python:</strong> SSRF host whitelist before any outbound request</div>
</div>
<div style="background:rgba(248,81,73,.07);border:1px solid rgba(248,81,73,.25);border-radius:5px;padding:7px 12px">
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--red);font-weight:700;margin-bottom:.25em">✗ DON'T</div>
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> string concat in JPQL queries</div>
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Svelte:</strong> <code>fetch('/api/…')</code> inside <code>onMount</code></div>
</div>
</div>
</div>
</div>
</div>
<aside class="notes">
Alle Personas teilen dieselbe Abschnittsstruktur — aber jede schreibt sie aus ihrer eigenen Perspektive.
Dieser Abschnitt ist Felix' Kernphilosophie: TDD ist kein Tool, es ist die einzige Arbeitsweise.
"Never write implementation code before a failing test exists" — das ist sein Hard Limit.
</aside>
</section>
<!-- ══════════════════════════════════════════════
7 · IDEE & UI-EXPLORATION
══════════════════════════════════════════════ -->
<section>
<h2>Idee &amp; UI-Exploration</h2>
<ol>
<li class="fragment">Vage Anforderung: <em>„Wir brauchen einen Stammbaum"</em></li>
<li class="fragment">UI-Persona bitten, <strong>4 Specs mit unterschiedlichem Fokus</strong> zu generieren</li>
<li class="fragment">Einen Spec auswählen — Elemente aus anderen übernehmen</li>
<li class="fragment">Finales vollständiges Spec schreiben lassen</li>
<li class="fragment">Committen und am Issue verlinken</li>
</ol>
<p class="fragment"><a href="../specs/stammbaum-tree-spec.html" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ stammbaum-tree-spec.html</a></p>
<aside class="notes">
stammbaum-tree-spec.html im Browser zeigen.
Der Trick mit den 4 Varianten: man merkt erst beim Vergleichen was man eigentlich will.
Scope-Entscheidungen auf Pixel-Ebene sind billiger als im Issue.
</aside>
</section>
<!-- ══════════════════════════════════════════════
7 · SPEC MIT ELICIT → GITEA-ISSUE
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Spec mit Elicit → Gitea-Issue</h2>
<p>Warum Gitea, nicht eine Datei?</p>
<ul>
<li class="fragment">LLM-Gespräche sind <strong>flüchtig</strong> — Issues sind <strong>dauerhaft</strong></li>
<li class="fragment">Issues sind verlinkbar: jeder Commit referenziert sie</li>
<li class="fragment">Review-Kommentare, Spec und Entscheidungen leben im selben Thread</li>
<li class="fragment">Kein Risiko einer veralteten Spec-Datei im Repo</li>
</ul>
<aside class="notes">
Das ist die architektonische Kernidee des ganzen Talks.
</aside>
</section>
<section>
<h3>Aufbau eines Spec-Issues</h3>
<ul>
<li class="fragment"><strong>Problem statement</strong> — was fehlt und warum es wichtig ist</li>
<li class="fragment"><strong>User Journey</strong> — Schritt für Schritt in Prosa</li>
<li class="fragment"><strong>E2E-Szenarien</strong> (Given/When/Then) → werden zu Red-Tests</li>
<li class="fragment"><strong>Requirements</strong> — funktional + NFR</li>
<li class="fragment"><strong>Out of scope</strong> — explizite Ausschlüsse</li>
</ul>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Stammbaum</a></p>
<aside class="notes">Echtes Issue #358 zeigen — annotiert.
PM: das ist der Vertrag vor Baubeginn.
Dev: das Szenario IST der erste fehlschlagende Test.</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
8 · PERSONA ISSUE REVIEW
══════════════════════════════════════════════ -->
<section>
<h2>Persona Issue Review</h2>
<p>Vor dem ersten Code: die Spec reviewen lassen</p>
<ul>
<li class="fragment">Jede Persona liest das Issue für sich — kein Austausch untereinander</li>
<li class="fragment">Kommentare werden direkt als Gitea-Kommentare gepostet</li>
<li class="fragment">Nora: Auth spezifiziert? · Sara: testbar? · Leonie: Mobile? · Markus: Domain-Grenzen?</li>
</ul>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358#issuecomment-5176" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Kommentar</a></p>
<aside class="notes">review-issue-Skill schickt einen Agenten pro Persona.
„Hier zu finden kostet null. Nach dem PR-Öffnen kostet es einen Tag."
Den Kommentar zeigen der den größten Impact hatte.</aside>
</section>
<!-- ══════════════════════════════════════════════
9 · IMPLEMENTIERUNG: TDD
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Implementierung: Red/Green/Refactor</h2>
<ul>
<li class="fragment">Die Szenarien aus dem Issue werden direkt zu Unit-Tests</li>
<li class="fragment">Failing Unit-Test → minimaler Code → grün → Refactor → Commit</li>
<li class="fragment">Immer Feature-Branch · ein logischer Change pro Commit</li>
</ul>
<aside class="notes">deliver-issue-Skill koordiniert den gesamten Zyklus.
PM: die Akzeptanzkriterien aus dem Issue sind die Definition of Done.</aside>
</section>
<section>
<h3>Red/Green im git log</h3>
<pre><code class="java" data-trim>
// Erst der Test — schlägt fehl (RED)
@Test
void getFamilyNetwork_excludesEdgesWithNonFamilyEndpoints() {
var result = service.getFamilyNetwork(personId);
assertThat(result.edges())
.noneMatch(e -> isNonFamilyPerson(e.targetId()));
}
</code></pre>
<pre><code class="text" data-trim>
32622b9b test(stammbaum): getFamilyNetwork excludes edges ... ← RED
656c93ca fix(stammbaum): exclude non-family edges in network ← GREEN
</code></pre>
<aside class="notes">
Das Log ist der Beweis, dass der Red-Schritt stattgefunden hat.
Test und Implementierung im selben atomaren Commit.
</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
10 · PR REVIEW LOOP
══════════════════════════════════════════════ -->
<section>
<h2>PR Review Loop</h2>
<p><code>review-pr</code>-Skill liest den Diff — jede Persona postet Kommentare zur PR</p>
<ul>
<li class="fragment">Felix: Projektstil, Dead Code?</li>
<li class="fragment">Nora: <code>@RequirePermission</code> auf jedem neuen Endpoint?</li>
<li class="fragment">Sara: fehlende Randfallstests?</li>
<li class="fragment">Leonie: stimmt der Output mit der Spec überein?</li>
</ul>
<p class="fragment">Kommentare adressieren → pushen → Review wiederholen → Merge</p>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/pulls/360" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ PR #360 — feat(stammbaum)</a></p>
<aside class="notes">
Einen PR-Kommentar zeigen der den Merge blockiert hat.
Merge erst wenn alle Stimmen zufrieden sind.
</aside>
</section>
<!-- ══════════════════════════════════════════════
11 · FAZIT
══════════════════════════════════════════════ -->
<section>
<h2>Fazit</h2>
<ol>
<li><strong>Separation of Concerns gilt auch für KI-Rollen</strong> — eine Persona die alles macht, reviewed sich selbst</li>
<li><strong>Das LLM vergisst. Das Issue nicht.</strong> — Spec, Entscheidungen und Review-Kommentare überleben jeden Context Reset</li>
</ol>
<aside class="notes">
Ehrliches Caveat: Aufwand am Anfang. Der Ertrag ist kumulativ.
</aside>
</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/highlight.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/notes/notes.js"></script>
<script>
Reveal.initialize({
hash: true,
slideNumber: 'c/t',
transition: 'slide',
plugins: [RevealHighlight, RevealNotes],
});
</script>
</body>
</html>