Files
Laufzettel/docs/vorlagen-format.md
T
florian.wilde 0fa41b9e08
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(seed): mitgelieferte Vorlagen am Schluessel erkennen (#42)
2026-10-04 14:49:00 +00:00

264 lines
15 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": "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": "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. |
| `seedSchluessel` | optional | **Nur für mitgelieferte Vorlagen.** Siehe unten — beim Import verfällt das Feld. |
| `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 und **ohne** Nummer — siehe unten. |
| `schritte` | optional | Liste von Schritten in Abarbeitungsreihenfolge; Standard `[]`. |
Abschnitte werden überall mit einer zweistelligen Nummer angezeigt (`01 · Vorbereitung`). Diese
Nummer wird **aus der Position abgeleitet und nie gespeichert**. Stünde sie im Titel, wäre sie beim
ersten Umsortieren falsch — und niemand ginge hinterher durch und zöge sie nach. Trägt ein Titel
dennoch eine vorangestellte Nummer mit `·` als Trenner (ältere Dateien taten das), wird sie beim
Öffnen im Editor entfernt, damit nicht `01 · 01 · Vorbereitung` dasteht; mit dem nächsten Speichern
gilt der bereinigte Titel. Andere Trenner bleiben unangetastet: „2. Etage prüfen“ ist ein Titel und
keine Nummerierung.
### 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.
## `seedSchluessel` — nur für mitgelieferte Vorlagen
Die sieben Vorlagen, die Laufzettel mitbringt, tragen in ihrer Datei ein zusätzliches Feld:
```json
{ "format": 1, "name": "PC Neuinstallation", "seedSchluessel": "pc-neuinstallation" }
```
Daran erkennt der Seed sie wieder. Vorher ging das über den **Namen** — und wer eine mitgelieferte
Vorlage umbenannte, bekam beim nächsten Seed-Lauf eine zweite, samt doppeltem Schnellstart auf der
Geräteseite (#021).
**Was der Schlüssel nicht tut:**
- Er macht eine Vorlage **nicht unveränderlich.** Umbenennen, umbauen, archivieren — alles bleibt
erlaubt. Der Seed erkennt sie danach wieder und lässt sie in Ruhe.
- Er erlaubt dem Seed **nicht**, eine bearbeitete Vorlage zu überschreiben. Gibt es die Vorlage, legt
er nichts an und ändert nichts. Das war vorher so und bleibt so.
- Er ist **keine Kennung über Installationen hinweg.** Zwei Installationen haben für dieselbe
mitgelieferte Vorlage denselben Schlüssel, aber verschiedene `id`s — der Schlüssel sagt nur
„diese Zeile stammt aus dem Seed“.
**Beim Import verfällt das Feld.** Wer eine mitgelieferte Vorlage exportiert und wieder einspielt,
bekommt eine gewöhnliche Vorlage ohne Schlüssel. Das ist Absicht: die Kopie soll nicht den Platz des
Originals einnehmen, sonst hielte der Seed sie dafür und würde das Original ein zweites Mal anlegen.
Der Export schreibt das Feld deshalb auch nicht — ein Feld, das beim Einlesen verfällt, verwirrt nur.
Eigene Vorlagen brauchen den Schlüssel nicht und sollten ihn nicht bekommen. Er ist eindeutig, wo er
gesetzt ist; eine zweite Vorlage mit demselben Schlüssel weist die Datenbank ab.