Files
Laufzettel/docs/vorlagen-format.md
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

15 KiB

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, Export und Import in 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.

{
  // 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:

{ "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 ids — 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.