# Architektur & Code-Übersicht
Diese Dokumentation erklärt, wie der Code von slidewalk aufgebaut ist: welche
Datei wofür zuständig ist, wie die Packages zusammenspielen und wo du
ansetzen musst, wenn du etwas ändern willst. Für die Autoring-Seite (wie man
Slides schreibt) siehe [`docs/markdown-guide.md`](markdown-guide.md); für
Installation/Nutzung siehe [`README.md`](../README.md).
## 1. Der große Überblick
Alles dreht sich um einen einzigen Datenfluss: **Ordner mit `.md`-Dateien
rein, eine HTML-Seite raus.** Dev-Server und statischer Export sind nur zwei
verschiedene "Auslässe" für denselben Kern:
```mermaid
graph LR
A["Ordner mit
010-x.md, 020-y.md, ..."] --> B["internal/parser
ParseDir"]
B -->|"[]Slide"| C["internal/render
Render"]
C -->|"HTML-Bytes"| D{{"cmd/slidewalk"}}
D -->|"ohne -build"| E["internal/watch
Server"]
D -->|"mit -build"| F["internal/export
Export"]
E --> G["Browser
(HTTP + SSE)"]
F --> H["Ordner mit
index.html + Assets"]
```
Vier Packages, klare Verantwortung, jedes einzeln testbar:
| Package | Beantwortet die Frage |
| ------------------ | --------------------------------------------------------------- |
| `internal/parser` | Wie wird aus einer `.md`-Datei ein `Slide` (Struct mit HTML)? |
| `internal/render` | Wie werden mehrere `Slide`s zu **einer** HTML-Seite? |
| `internal/watch` | Wie liefere ich die Seite über HTTP aus und melde Änderungen? |
| `internal/export` | Wie schreibe ich die Seite + Assets als statische Dateien? |
| `web` | Wo liegen Templates/CSS/JS, und wie kommen sie ins Binary? |
| `cmd/slidewalk` | Wie übersetze ich CLI-Flags in einen Aufruf von `watch` oder `export`? |
Sowohl `internal/watch` als auch `internal/export` rufen **ausschließlich**
`parser.ParseDir` und `render.Render` auf — es gibt keine zweite Stelle im
Code, die Markdown parst oder HTML zusammenbaut. Wenn sich am
HTML-Slide-Markup etwas ändern soll, reicht eine Änderung in
`internal/render`.
## 2. `internal/parser` — Markdown-Dateien einlesen
**Zuständig:** aus einem Ordner-Pfad eine sortierte Liste von `Slide`-Structs
machen.
| Datei | Enthält |
| --------------------- | ------- |
| `parser.go` | Öffentliche API: `Slide`-Struct, `ParseDir(dir)`, Datei-Discovery (`discoverSlideFiles`), Zusammenbau eines einzelnen Slides (`parseSlide`) |
| `frontmatter.go` | YAML-Frontmatter-Handling: `splitFrontmatter` trennt den `---`-Block vom Markdown-Body, `parseFrontmatter` parst ihn in das `frontmatter`-Struct (`title`, `class`, `notes`, `skip`, `incremental`) |
| `markdown.go` | Die `goldmark`-Instanz (`md`) mit allen Extensions, plus der custom Fenced-Code-Block-Renderer (Mermaid-Sonderfall + Chroma-Syntax-Highlighting) |
| `*_test.go` | Table-driven Tests je Datei |
### Ablauf von `ParseDir` (`parser.go:35`)
1. `discoverSlideFiles(dir)` listet alle `.md`-Dateien im Ordner (nicht
rekursiv) und sortiert sie alphabetisch — das ist der Mechanismus hinter
der Präfix-Reihenfolge (`010-...`, `020-...`).
2. Für jede Datei: `os.ReadFile` → `parseSlide(name, data)`.
3. `parseSlide` (`parser.go:134`) ruft zuerst `parseFrontmatter` auf. Ist
`skip: true` gesetzt, bricht es sofort ab (zweiter Rückgabewert `skip
bool`), ohne den Markdown-Body überhaupt zu rendern.
4. Sonst: `renderMarkdown(body)` aus `markdown.go` wandelt den Rest in HTML
um, das Ergebnis landet als `template.HTML` im `Slide`-Struct.
5. `ParseDir` filtert alle als "skip" markierten Slides aus der
zurückgegebenen Liste heraus.
### Fenced-Code-Blöcke: Mermaid & Syntax-Highlighting (`markdown.go`)
`goldmark` rendert Fenced-Code-Blöcke standardmäßig als
`
`. slidewalk braucht davon zwei
Sonderfälle, deshalb registriert `mermaidExtension` einen eigenen
`NodeRenderer` (`mermaidRenderer`) mit höherer Priorität als goldmarks
Standard-Renderer für `ast.KindFencedCodeBlock` — er überschreibt dessen
Registrierung (`renderFencedCodeBlock`, `markdown.go`):
- Sprache `mermaid` → `…
` **ohne** ``-Wrapper,
das Format, das `mermaid.js` erwartet.
- Sonstige Sprache, für die `lexers.Get` (Chroma) einen Lexer findet →
`renderHighlighted` tokenisiert den Codeblock-Inhalt und lässt ihn per
`chromahtml.Formatter` (Modus `WithClasses(true)`, also CSS-Klassen statt
Inline-Styles) als `…
` mit ``-Tokens
rendern. Die eigentlichen Farben kommen aus der separat generierten
`web/assets/chroma.css` (siehe unten), nicht aus dem HTML selbst.
- Keine Sprache, oder eine, für die Chroma keinen Lexer kennt → fällt
zurück auf `renderDefaultFencedCodeBlock`, eine Kopie von goldmarks
Standardverhalten (``, funktioniert auch
ganz ohne Sprachangabe).
Zusätzlich aktiviert: `extension.GFM` (Tabellen, Strikethrough, Auto-Links,
Task-Listen), `extension.Footnote`, sowie `html.WithUnsafe()` — Letzteres
erlaubt rohes HTML inline/als Block im Markdown (siehe
[Markdown-Anleitung](markdown-guide.md#5-rohes-html-in-markdown)).
## 3. `internal/render` — aus Slides wird eine HTML-Seite
**Zuständig:** `[]parser.Slide` + ein paar Metadaten → ein vollständiges
HTML-Dokument. Das ist die **einzige** Stelle im Code, die das finale
Seiten-Markup erzeugt.
| Datei | Enthält |
| ------------- | ------- |
| `render.go` | `Page`-Struct (Input), `Render(w, page)` (Ausführung), interne `pageView`/`slideView`-Structs, die tatsächlich ans Template gehen |
`Render` (`render.go:50`) macht im Kern nur zwei Dinge:
1. Für jeden `parser.Slide` einen `slideView` bauen — dabei entsteht der
1-basierte `Index`, der später als `data-slide="n"` im HTML landet.
2. `pageTemplate.Execute(w, pageView{...})` — das eigentliche Template liegt
nicht in diesem Package, sondern wird über `web.Templates` (siehe unten)
eingebettet und beim Package-Init einmalig geparst
(`template.Must(template.ParseFS(...))`, `render.go:12`).
Wichtig: `Page.DevReload` (bool) ist der einzige Unterschied zwischen
Dev-Server- und Export-Ausgabe. Ist er `true`, bindet das Template
`live-reload.js` ein; beim Export bleibt er auf dem Zero-Value `false`.
Dadurch entsteht kein zweiter Codepfad — nur ein Flag.
`slideView.Incremental` reicht die Frontmatter-Einstellung `incremental`
unverändert durch: Ist sie gesetzt, bekommt das ``-Element im
Template zusätzlich `data-incremental="true"`. Das ist die einzige
serverseitige Beteiligung an den Fragments — die eigentliche
Ein-/Ausblende-Logik läuft komplett im Browser (`nav.js`, siehe Abschnitt 6).
## 4. `web` — Templates und Assets
**Zuständig:** alles, was nicht Go-Code ist (HTML-Template, CSS, JS,
Mermaid-Library), so einbetten, dass am Ende ein einziges Binary ohne externe
Dateien entsteht.
| Datei/Ordner | Enthält |
| ------------------------------------ | ------- |
| `embed.go` | Zwei `embed.FS`-Variablen: `Templates` (bindet `templates/`) und `Assets` (bindet `assets/`) |
| `templates/page.html.tmpl` | Das eine HTML-Template für die ganze Seite: `` mit Stylesheet-Links (`vendor/style.css`, `vendor/chroma.css`), `{{range .Slides}}` für die ``-Elemente, Mermaid-Init-Script, Nav-Buttons/Klick-Zonen/Fortschrittsanzeige, `