# 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 der Slides einer Datei (`parseSlideFile`) | | `frontmatter.go` | YAML-Frontmatter-Handling: `splitFrontmatter` trennt den `---`-Block vom Markdown-Body, `parseFrontmatter` parst ihn in das `frontmatter`-Struct (`title`, `class`, `notes`, `skip`, `incremental`) | | `split.go` | `splitSlideBreaks` teilt den Body einer Datei an ``-Markierungszeilen in mehrere Teile, fence-bewusst (ignoriert Marker innerhalb von ```` ``` ````/`~~~`-Codeblöcken) | | `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` → `parseSlideFile(name, data)`. 3. `parseSlideFile` (`parser.go:134`) ruft zuerst `parseFrontmatter` auf. Ist `skip: true` gesetzt, bricht es sofort ab (leere Slide-Liste), ohne den Markdown-Body überhaupt zu rendern. 4. Sonst: `splitSlideBreaks(body)` aus `split.go` teilt den Body an ``-Zeilen in einen oder mehrere Teile (ohne Marker: ein Teil = der unveränderte Body). Für jeden Teil wandelt `renderMarkdown(part)` aus `markdown.go` den Markdown-Text in HTML um; das Ergebnis landet als `template.HTML` in einem eigenen `Slide`-Struct. Alle Teile einer Datei teilen sich deren Frontmatter (`Title`, `Class`, `Notes`, `Incremental`). 5. `ParseDir` reiht die Slides aller Dateien aneinander; als "skip" markierte Dateien tragen keine Slides bei. ### 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, `