Files
Laufzettel/docs/vorlagen-format.md
T
florian.wilde d8d5444e9f
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(vorlagen): Versionierung, Unveraenderlichkeit und JSON-Austausch (#23)
2026-09-25 06:10:01 +00:00

225 lines
12 KiB
Markdown

# Austauschformat für Vorlagen
Eine Vorlage lässt sich als JSON-Datei exportieren und wieder einspielen. Diese Datei beschreibt
das Format — für jemanden, der eine Vorlage von Hand schreiben oder ein Skript dafür bauen will.
Die Prüfung liegt in [`src/lib/vorlagen-format.ts`](../src/lib/vorlagen-format.ts), Export und
Import in [`src/server/services/vorlagen-austausch.ts`](../src/server/services/vorlagen-austausch.ts).
Beide benutzen dasselbe Schema: was hier steht, gilt für den Export, den Import und den Editor.
## Wozu das Format da ist
Eine exportierte Vorlage soll sich in eine **andere** Laufzettel-Installation einspielen lassen.
Deshalb enthält das Dokument nur das, was eine Vorlage inhaltlich ausmacht: Kopfdaten, Variablen,
Abschnitte, Schritte. Alles, was nur in der Quellinstallation eine Bedeutung hat, bleibt draußen.
**Nicht im Dokument enthalten:**
- **Kennungen** (`id`, `versionId`, `abschnittId`) — sie zeigen in der Zielinstallation ins Leere
- **Zeitstempel** (`erstelltAm`, `geaendertAm`, `veroeffentlichtAm`) und der veröffentlichende
Benutzer
- **Kundenbezug** (`kundeId`) — eine eingespielte Vorlage gilt zunächst für alle Kunden; wer sie
einem Kunden zuordnen will, tut das danach in der Oberfläche
- **`position`** — die Reihenfolge steckt in der Reihenfolge der Listen. Zwei Wahrheiten über
dieselbe Reihenfolge laufen früher oder später auseinander.
- **Läufe**, Schrittergebnisse, Kommentare und das Protokoll — das ist Betriebsgeschehen, keine
Vorlage
- der Status der Version und das Archiv-Kennzeichen
## Beispiel
Das folgende Dokument ist vollständig: zwei Abschnitte, zwei Variablen und ein `BEFEHL`-Schritt mit
Platzhaltern. **JSON kennt keine Kommentare** — die Zeilen mit `//` müssen vor dem Import raus.
```jsonc
{
// Fassung des Austauschformats, derzeit immer 1.
"format": 1,
"name": "Onboarding Notebook",
"beschreibung": "Neue Person, neues Gerät.",
"kategorie": "Onboarding",
"schlagworte": ["notebook", "eintritt"],
// Nur zur Information: welche Version der Quelle exportiert wurde. Beim
// Import wird der Wert gelesen und nicht übernommen.
"version": 3,
"struktur": {
// Wird beim Start eines Laufs abgefragt und füllt die Platzhalter.
"variablen": [
{ "schluessel": "vorname", "beschriftung": "Vorname", "typ": "TEXT", "pflicht": true },
{
"schluessel": "abteilung",
"beschriftung": "Abteilung",
"typ": "AUSWAHL",
"optionen": ["IT", "Vertrieb", "Buchhaltung"]
}
],
// Die Reihenfolge dieser Liste ist die Reihenfolge in der Ansicht.
"abschnitte": [
{
"titel": "01 · Vorbereitung",
"schritte": [
{ "titel": "Gerät auspacken", "pflicht": true },
{
"titel": "Seriennummer erfassen",
"hilfetext": "Aufkleber auf der Unterseite. **Nicht** die Artikelnummer.",
"typ": "TEXT",
"pflicht": true,
"konfig": { "mehrzeilig": false },
"dokuUrl": "https://intern.example/seriennummern"
},
{
"titel": "Arbeitsspeicher prüfen",
"typ": "ZAHL",
"konfig": { "min": 8, "max": 64, "einheit": "GB" }
}
]
},
{
"titel": "02 · Konto",
"schritte": [
{
"titel": "Benutzerkonto anlegen",
"typ": "BEFEHL",
"pflicht": true,
// Die Platzhalter werden erst beim Abarbeiten ersetzt; hier stehen
// sie wörtlich.
"konfig": {
"shell": "powershell",
"befehl": "New-ADUser -Name \"{{vorname}}\" -Department \"{{abteilung}}\""
}
},
{ "titel": "Eintrittsdatum hinterlegen", "typ": "DATUM" }
]
}
]
}
}
```
## Felder
### Kopf
| Feld | Pflicht | Bedeutung |
| -------------- | -------- | ------------------------------------------------------------------------------- |
| `format` | Pflicht | Fassung des Austauschformats. Derzeit `1`; ein anderer Wert wird abgewiesen. |
| `name` | Pflicht | Name der Vorlage, nicht leer. Namen sind **nicht** eindeutig. |
| `beschreibung` | optional | Ein bis zwei Sätze, was die Vorlage abdeckt. |
| `kategorie` | optional | Freitext, gruppiert die Vorlagenliste (etwa `Onboarding`). |
| `schlagworte` | optional | Liste nicht leerer Texte; Standard `[]`. |
| `version` | optional | Versionsnummer der exportierten Quelle. Reine Information, siehe unten. |
| `struktur` | Pflicht | Objekt mit `variablen` und `abschnitte`. |
### `struktur`
| Feld | Pflicht | Bedeutung |
| ------------ | -------- | ---------------------------------------------------------- |
| `variablen` | optional | Liste von Variablen in Abfragereihenfolge; Standard `[]`. |
| `abschnitte` | optional | Liste von Abschnitten in Anzeigereihenfolge; Standard `[]`. |
### Variable
| Feld | Pflicht | Bedeutung |
| -------------- | -------- | ------------------------------------------------------------------------- |
| `schluessel` | Pflicht | Der Name im Platzhalter `{{schluessel}}`. Regeln siehe unten. |
| `beschriftung` | Pflicht | Was im Startformular über dem Feld steht, nicht leer. |
| `typ` | optional | `TEXT`, `ZAHL`, `AUSWAHL` oder `DATUM`; Standard `TEXT`. |
| `optionen` | optional | Nur bei `AUSWAHL`: die Auswahlwerte in Anzeigereihenfolge; Standard `[]`. |
| `pflicht` | optional | `true` verlangt eine Eingabe beim Start; Standard `false`. |
Ein Schlüssel darf in einem Dokument nur **einmal** vorkommen. Ein zweiter Eintrag mit demselben
Schlüssel ist ein Fehler und kein stilles Überschreiben — sonst verschwände eine Variable, ohne
dass es jemand merkt.
### Abschnitt
| Feld | Pflicht | Bedeutung |
| ---------- | -------- | ----------------------------------------------------------------------------- |
| `titel` | Pflicht | Überschrift, nicht leer. Üblich mit zweistelliger Nummer: `01 · Vorbereitung`. |
| `schritte` | optional | Liste von Schritten in Abarbeitungsreihenfolge; Standard `[]`. |
### Schritt
| Feld | Pflicht | Bedeutung |
| ----------- | -------- | -------------------------------------------------------------------------------- |
| `titel` | Pflicht | Was zu tun ist, nicht leer. Darf Platzhalter enthalten. |
| `hilfetext` | optional | Markdown, in der Abarbeitungsansicht ausklappbar. **Kein rohes HTML.** |
| `typ` | optional | Einer der Schritt-Typen unten; Standard `HAKEN`. |
| `pflicht` | optional | Pflichtschritte müssen vor dem Abschluss erledigt oder ausdrücklich als nicht zutreffend markiert sein; Standard `false`. |
| `konfig` | optional | Typabhängige Einzelheiten; Standard `{}`. Siehe unten. |
| `dokuUrl` | optional | Link auf eine Anleitung. Muss eine gültige Adresse sein. |
## Schritt-Typen und ihre `konfig`
| Typ | Was der Techniker tut | Schlüssel in `konfig` |
| --------- | ------------------------------ | -------------------------------------------------------------- |
| `HAKEN` | abhaken | keine |
| `TEXT` | Text eintragen | `mehrzeilig` (`true`/`false`) |
| `ZAHL` | Zahl eintragen | `min`, `max` (Zahlen), `einheit` (Text, höchstens 40 Zeichen) |
| `AUSWAHL` | aus einer Liste wählen | `optionen` (Liste nicht leerer Texte, in Anzeigereihenfolge) |
| `DATUM` | Datum wählen | keine |
| `BEFEHL` | Befehl kopieren und ausführen | `shell` (`powershell`, `bash` oder `cmd`), `befehl` (Text) |
`min` und `max` sind Hinweise: ein Wert außerhalb wird markiert, nicht blockiert.
`konfig` wird **nachsichtig** geprüft. Unbekannte Schlüssel bleiben erhalten — eine Datei aus einer
späteren Fassung verliert hier nichts, auch wenn diese Fassung die Schlüssel nicht kennt. Was die
Anwendung kennt, wird geprüft: `shell` etwa akzeptiert nur die drei genannten Werte.
Ein Schlüssel, der nicht zum Typ passt, ist kein Fehler; er wird schlicht nicht beachtet. Wer den
Typ eines Schritts ändert, räumt die `konfig` also besser selbst auf.
## Platzhalter und Variablenschlüssel
Ein Variablenschlüssel muss der Form `^[a-z][a-z0-9_]{0,39}$` entsprechen: Beginn mit einem
Kleinbuchstaben, danach Kleinbuchstaben, Ziffern und Unterstrich, höchstens 40 Zeichen.
`vorname`, `ad_gruppe` und `raum2` sind in Ordnung; `Vorname`, `mit-strich`, `2te_wahl` und ein
leerer Schlüssel nicht.
Die Strenge hat einen Grund: der Schlüssel steht als `{{schluessel}}` in Titeln, Hilfetexten und
Befehlen. Ein Platzhalter wird genau mit diesem Muster wiedererkannt. Wäre `Vor-Name` erlaubt,
müsste der Suchausdruck Groß- und Kleinschreibung, Striche und Leerzeichen auseinanderhalten — und
`{{Vor-Name}}` in einem PowerShell-Befehl wäre nicht mehr von dessen eigener Syntax zu trennen.
Deshalb: ein Zeichensatz, eine Regel, keine Ausnahmen.
Geschweifte Klammern dürfen Leerzeichen enthalten (`{{ vorname }}` wird erkannt). Neben den
Variablen der Vorlage gibt es eingebaute Platzhalter — `{{generiertes_passwort}}` etwa —, die nicht
im Austauschdokument stehen und auch nirgends gespeichert werden.
## Was beim Import passiert
1. Das Dokument wird gegen das Schema geprüft. Schlägt das fehl, wird **nichts** geschrieben.
2. Es entsteht eine **neue Vorlage** — der Import schreibt nie in eine bestehende hinein.
3. Die Vorlage bekommt genau eine Version: `version = 1`, Status **`ENTWURF`**. Eine eingespielte
Vorlage wird bewusst nicht sofort veröffentlicht; der Redakteur sieht sie sich erst an,
ergänzt vielleicht etwas und veröffentlicht dann. Erst danach lässt sich ein Lauf daran starten.
4. `position` wird aus der Reihenfolge der Listen abgeleitet (0, 1, 2 …).
5. Das mitgeschickte `version`-Feld wird nicht übernommen. Eine Vorlage, die in der Quelle auf
Version 7 stand, fängt hier wieder bei 1 an — die Versionsgeschichte der Quelle wandert nicht mit.
6. Der Vorlagenname kommt so an, wie er im Dokument steht. Gibt es ihn schon, ist das kein Fehler
und es wird nichts angehängt: Vorlagennamen sind nicht eindeutig.
7. Alles geschieht in einer Transaktion. Ein Fehler auf halbem Weg hinterlässt keine halbe Vorlage.
8. Ein Protokolleintrag mit der Aktion `Vorlage importiert` hält fest, wer die Vorlage eingespielt hat.
Wird das Dokument abgewiesen, nennt die Meldung die **erste** Beanstandung samt Pfad, etwa:
```
struktur.abschnitte.1.schritte.2.titel: Ein Schritt braucht einen Titel.
```
## Wie sich das Format ändern darf
Das Feld `format` ist die Fassung des Formats, nicht die der Anwendung. Es gilt:
- **Neue optionale Felder** und neue `konfig`-Schlüssel erhöhen `format` **nicht**. Alte Dateien
bleiben gültig, und weil `konfig` nachsichtig geprüft wird, überleben neue Schlüssel auch einen
Rundlauf durch eine ältere Fassung.
- **Neue Schritt- und Variablentypen** erhöhen `format` ebenfalls nicht. Eine Fassung, die den Typ
nicht kennt, weist die Datei allerdings ab, weil der Typ eine geprüfte Aufzählung ist.
- Erst eine Änderung, die alte Dateien **unbrauchbar** macht — ein umbenanntes Pflichtfeld, eine
andere Verschachtelung —, erhöht `format` auf `2`.
- Ein unbekannter `format`-Wert wird mit einer klaren Meldung abgewiesen, nie geraten. Ein Import,
der die Hälfte versteht und die andere Hälfte still verwirft, wäre schlimmer als ein Fehlschlag.
Die Zahl steht als `FORMAT_VERSION` in `src/lib/vorlagen-format.ts` an genau einer Stelle.