Zum Inhalt springen

JSON-Schema für Entitätstypen

Jeder Entitätstyp trägt ein JSON-Schema im Feld descJsonb. Aus diesem Schema baut der Editor automatisch das Eingabeformular für Create- und Update-Drafts.

Schema-Editor im Entitätstyp-Admin

Das Schema steuert drei Schichten:

  • Struktur — welche Felder existieren (Standard-JSON-Schema)
  • Form-Verhalten — wie die Felder im Edit-Formular gerendert werden (Standard + projektspezifische Extensions)
  • Anzeige — wie die Werte im Normalmodus dargestellt werden (x-presentation, siehe Normalmodus & Expertenmodus)

Der Dialog zum Anlegen und Bearbeiten eines Entitätstyps hat zwei Reiter.

Visuell (Voreinstellung) zeigt eine Tabelle aller Felder. Dort legst du Felder mit einem Knopf an, vergibst Feldnamen und Beschriftung, wählst den Typ aus einer Liste, setzt das Pflichtkennzeichen und pflegst die Auswahlwerte einer Liste — ohne eine Zeile JSON zu schreiben. Darunter stehen wie gewohnt die Editoren für Darstellung, Knotenfarbe und Knotentextur; alle arbeiten am selben Schema.

JSON zeigt weiterhin das rohe Schema im Texteditor. Beide Reiter hängen am selben Stand: was du visuell änderst, steht sofort im JSON und umgekehrt. Für alles, was der visuelle Reiter (noch) nicht kann — showWhen, verschachtelte Objekte, format-Angaben — bleibt der JSON-Reiter der Weg.

Beim Umbenennen und Löschen eines Feldes zieht der visuelle Reiter die abhängigen Stellen automatisch mit: das required-Array und die Treiberfelder von x-node-color/x-node-texture. Entfernst du einen Auswahlwert, verschwinden auch dessen Farb- und Texturzuordnungen — sonst weist der Server das Speichern mit einer Fehlermeldung ab.

Achtung bei bestehenden Daten: Ein Feld umzubenennen oder zu löschen ändert nur das Schema. Für die bereits gespeicherten Datensätze öffnet sich danach der Migrationsdialog, in dem du entscheidest, welches alte Feld auf welches neue abgebildet wird. Bei Beziehungstypen gibt es diesen Dialog noch nicht — dort gehen Werte eines umbenannten Attributs verloren.

Beziehungstypen haben denselben visuellen Reiter für ihre Attribute. Die zweisprachige Bedeutung und die Beteiligten-Konfiguration bleiben davon unberührt — der Builder sieht nur den Schema-Anteil.

Ein Schema hat immer type: "object" auf der Wurzel, eine properties-Map und optional ein required-Array:

{
"type": "object",
"properties": {
"description": { "type": "string", "title": "Beschreibung" }
},
"required": ["description"]
}
typeRenderer im Edit-Formular
"string"Text-Input mit @-Mention-Autocomplete (Default)
"number" / "integer"Zahlen-Input
"boolean"Checkbox
"array"Listen-Editor mit Add/Remove-Buttons (braucht items)
"object"Verschachteltes Sub-Form mit eigenen properties
  • title setzt das Label. Ohne title zeigt der Editor den Property-Namen.
  • description erscheint als Hilfetext unter dem Feld.
  • default wird beim Create-Draft vorbelegt.
{
"type": "object",
"properties": {
"priority": {
"type": "string",
"title": "Priorität",
"description": "Steuert die Reihenfolge in Listenansichten.",
"default": "normal"
}
}
}

Ein Pflichtfeld setzt du am einfachsten im „Visuell”-Reiter: Häkchen in der Spalte Pflicht. Im JSON steht es als Array auf der Root-Ebene — nicht am Property selbst. Häufiger Fehler: "required": true ans Property hängen funktioniert nicht.

{
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" }
},
"required": ["name"]
}

„Pflicht” heißt ausgefüllt, nicht nur „Feld vorhanden”: ein leeres Textfeld, eines mit nur Leerzeichen und eine leere Liste zählen als fehlend. Eine 0 und ein abgewähltes Häkchen dagegen sind getroffene Entscheidungen und gelten als ausgefüllt.

Wo es greift: Das Formular zeigt fehlende Pflichtfelder an und sperrt das Veröffentlichen — als Entwurf speichern bleibt möglich, damit unfertige Arbeit parkbar ist. Beim Veröffentlichen prüft zusätzlich der Server, also auch für Wege, die nicht über das Formular kommen: Konnektoren, Automator, Assistent und CSV-Import. Der Import legt Entwürfe an; fehlende Pflichtfelder erscheinen dort als Warnung, nicht als Fehler.

Anlege-Dialog mit leeren Pflichtfeldern: „Veröffentlichen" ist gesperrt, die Zusammenfassung nennt die fehlenden Felder, „Als Entwurf speichern" bleibt möglich

Ein ausgeblendetes Feld ist nie Pflicht. Trägt ein Pflichtfeld eine Bedingung (showWhen, siehe unten) und ist sie nicht erfüllt, wird es nicht verlangt — sonst entstünde eine Sackgasse: nicht ausfüllbar, aber gefordert.

Ein Pflichtfeld muss es geben. Steht in required ein Feld, das in properties fehlt (Tippfehler, umbenanntes Feld), lehnt der Editor das Schema ab. Ohne diese Prüfung wäre danach kein einziger Datensatz dieses Typs mehr veröffentlichbar — mit einer Meldung, die nur du als Admin beheben kannst.

Soll ein Feld nur in bestimmten Fällen Pflicht sein, geht das über die JSON-Schema-Mittel if/then bzw. dependencies im Reiter „JSON”:

{
"type": "object",
"properties": {
"vertragsart": { "type": "string", "enum": ["intern", "extern"] },
"lieferant": { "type": "string" }
},
"if": { "properties": { "vertragsart": { "const": "extern" } } },
"then": { "required": ["lieferant"] }
}

Der Feld-Builder kann das noch nicht erzeugen — eingetragenes JSON wirkt aber sofort. Damit der Nutzer das Feld auch nur dann sieht, ergänze zusätzlich eine showWhen-Bedingung.

Aus enum macht der Editor automatisch ein Dropdown — der Standard-String-Renderer wird umgangen, daher funktionieren in Enum-Feldern keine @-Mentions.

{
"status": {
"type": "string",
"title": "Status",
"enum": ["draft", "active", "archived"],
"default": "draft"
}
}

Der Untertyp (subtype) — ein reservierter Feldname

Abschnitt betitelt „Der Untertyp (subtype) — ein reservierter Feldname“

Manche Module unterscheiden innerhalb eines Entitätstyps verschiedene Arten: Rolle, Kreis und Gilde bei den Rollen, Unternehmensziel, Objective und Key Result bei den OKRs, Kette, Wertstrom, Teilstrom und Tätigkeit in der Wertschöpfung. Dieses Feld heißt in jedem Modul gleich: subtype (Beschriftung „Untertyp”).

  • Die Werte pflegst du wie bei jeder Auswahlliste: ergänzen, beschriften, sortieren.
  • Das Feld selbst ist gesperrt (Schloss-Symbol im visuellen Editor): Es lässt sich weder umbenennen noch löschen und muss eine Auswahlliste aus Texten bleiben. Karten, Texturen und Auswertungen verlassen sich darauf.
  • Frühere Namen (entityType bei Rollen, okrType bei OKRs, processLevel in der Wertschöpfung) stellt roleALPHA automatisch um — samt aller Datensätze und offener Entwürfe. Ältere Stände in der Historie und in Sicherungen bleiben lesbar.
  • Hattest du selbst schon ein eigenes Feld namens subtype angelegt, wird dein Mandant nicht automatisch umgestellt; der Betrieb klärt das mit dir.

Ein Gate in der Wertschöpfung ist übrigens kein Untertyp, sondern ein eigener Entitätstyp — es hat eigene Felder und eine eigene Form.

  • format: "date" → Date-Picker statt einfachem Input
  • format: "time" → Uhrzeit-Picker
  • format: "date-time" → Datum + Uhrzeit
  • format: "email" → E-Mail-Validierung
  • format: "uri" → Link-Feld: URL-Validierung, Öffnen-Knopf neben dem Eingabefeld und Ergänzung eines fehlenden https:// beim Verlassen des Feldes (siehe Dokumente verlinken)
{
"startDate": { "type": "string", "format": "date", "title": "Startdatum" }
}

Hinweis: Damit der Date-Picker erscheint, muss das Feld vom Typ string sein und format: "date" (bzw. "time" / "date-time") tragen. Wählst du im Editor „Darstellung pro Feld” eine der Datums-Darstellungen (date-relative / date-absolute), wird format: "date" automatisch gesetzt — dann zeigt der Editor den Date-Picker, ohne dass du das JSON anfassen musst.

PropertyWirkung
minLength, maxLengthMindest- / Maximallänge für Strings
patternRegex-Validierung (z.B. "^[A-Z]{2,3}$")
minimum, maximumBereich für Zahlen
multipleOfSchrittweite für Zahlen
minItems, maxItemsListen-Länge
uniqueItemsDoppelte Listeneinträge verbieten

Diese Grenzen setzt du im „Visuell”-Reiter in der Spalte Grenzen — je Feldtyp erscheinen genau die Angaben, die dort auch wirken (Text: Länge und Muster; Zahl: Bereich und Schrittweite; Liste: Anzahl der Einträge). Ein ungültiger regulärer Ausdruck wird schon beim Tippen markiert, und eine in sich unmögliche Regel (minimum größer als maximum) lässt sich gar nicht erst speichern.

Wo die Grenzen greifen: Beim Ausfüllen zeigt das Formular den Verstoß am Feld an und sperrt das Veröffentlichen; als Entwurf speichern bleibt möglich, damit unfertige Arbeit parkbar ist. Beim Veröffentlichen prüft zusätzlich der Server — ein Verstoß wird dort abgewiesen, auch wenn er auf anderem Weg als über das Formular kommt.

Bis August 2026 waren diese Grenzen faktisch wirkungslos: Der Editor zeigte den Fehler zwar an, das Speichern lief trotzdem durch, und serverseitig prüfte sie niemand. Wenn du Grenzen zu einem Typ ergänzt, dessen Daten schon lange bestehen, prüfe vorher mit dem Bericht scripts/report-schema-violations.ts, wie viele Datensätze die neue Regel verletzen würden.

Pflichtfelder (required) greifen genauso — siehe oben. Bis August 2026 waren sie ausgenommen und dem Validator überlassen; seither gilt: was JSON Schema über einen einzelnen Datensatz sagen kann, steht im Schema, und der Validator trägt, was darüber hinausgeht — Regeln über Beziehungen.

"multi": true an einem String-Feld → der Editor rendert eine Textarea statt eines einzeiligen Inputs. Die @-Mention-Autocomplete funktioniert weiter.

{
"description": {
"type": "string",
"title": "Beschreibung",
"multi": true
}
}

multi ist eine projektspezifische Extension. Sie wird vom uiSchema-Generator in JsonForms-Optionen (options.multi) übersetzt.

Auswahllisten und ihre Beschriftungen (x-enum-labels)

Abschnitt betitelt „Auswahllisten und ihre Beschriftungen (x-enum-labels)“

Ein enum-Eintrag ist der gespeicherte Wert, nicht der Anzeigetext. Das ist kein Formalismus: an genau diesem Wert hängen die Farb- und Texturzuordnungen im Graphen, die Bedingungen aus dem vorigen Abschnitt und die Regeln des Validator-Services. Er darf sich deshalb nicht ändern, nur weil jemand die Sprache der Oberfläche umstellt.

Die Beschriftung ist eine eigene Schicht daneben:

"orgType": {
"type": "string",
"enum": ["company", "department"],
"x-enum-labels": {
"company": { "de": "Unternehmen", "en": "Company" },
"department": { "de": "Abteilung", "en": "Department" }
}
}

Der Rohwert ist verbindlich englisch und maschinenlesbar (Kleinbuchstaben, Ziffern, Unterstrich). Im „Visuell”-Reiter gibt es je Auswahlwert einen Übersetzungs-Knopf; Deutsch und Englisch sind beide Pflicht. Fehlt eine Sprache, markiert der Editor das und der Server lehnt das Speichern ab — sonst bekäme ein Nutzer mit dieser Spracheinstellung den technischen Rohwert zu sehen.

Wo die Beschriftung erscheint: im Auswahlfeld beim Ausfüllen, als Badge in der Detailansicht und als Gruppenüberschrift in den Cluster-Ansichten. Wo sie nicht erscheint: überall dort, wo verglichen wird — dort zählt weiterhin ausschließlich der Rohwert.

Umstellung im August 2026: Die mitgelieferten Auswahllisten trugen bis dahin deutschen Klartext als Wert ("In Überprüfung", "Unternehmen"). Sie wurden auf englische Rohwerte umgestellt; die deutschen Bezeichnungen sind jetzt Beschriftungen. Für bestehende Mandanten läuft das über ein eigenes Migrationsskript (scripts/migrate-default-enums.ts) — es schreibt Schema, Farbzuordnungen, Bedingungen, Datensätze und offene Entwürfe gemeinsam um. Der Trockenlauf ist die Voreinstellung.

showWhen blendet ein Feld nur dann ein, wenn ein anderes Feld einen bestimmten Wert hat. Praktisch für „Andere Begründung…”-Eingaben oder modusabhängige Felder.

{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "blocked", "other"]
},
"blockReason": {
"type": "string",
"title": "Grund der Blockierung",
"multi": true,
"showWhen": { "field": "status", "values": ["blocked", "other"] }
}
}
}

showWhen ist eine projektspezifische Extension und wird in eine JsonForms-rule mit effect: "SHOW" übersetzt.

Die Bedingung gilt überall, nicht nur im Formular: Was showWhen ausblendet, zeigen auch die Detailansicht, die Änderungsvorschau eines Entwurfs und die Karte im Explorer nicht. Ein vorhandener Wert wird dabei nicht gelöscht — wechselt eine Entität ihren Untertyp zurück, ist er wieder da. Nur die Experten-Ansicht zeigt weiterhin alle Rohdaten, damit ein ausgeblendeter Altwert auffindbar bleibt. Und ein ausgeblendetes Feld ist nie Pflicht.

Im „Visuell”-Reiter trägt jede Feldzeile die Spalte „Nur sichtbar wenn”: dort wählst du das steuernde Feld und markierst die Werte, bei denen das Feld erscheinen soll. Angeboten werden nur Felder mit fester Auswahl (enum) oder Ja/Nein-Felder — bei einem Freitextfeld wäre die Bedingung praktisch nie erfüllt, und es gäbe keine Werte zum Markieren.

Zwei Dinge nimmt dir das System dabei ab, weil sie sonst stillschweigend schiefgehen:

  • Wird das steuernde Feld umbenannt, zieht die Bedingung mit. Wird es gelöscht oder verliert es seine Auswahlwerte, verschwindet die Bedingung — und das abhängige Feld ist wieder immer sichtbar. Das ist die harmlosere Richtung: eine Bedingung, die niemand mehr erfüllen kann, würde das Feld dauerhaft aus dem Formular nehmen, ohne Fehlermeldung.
  • Beim Speichern wird geprüft, dass das steuernde Feld existiert, eine feste Auswahl hat und die genannten Werte auch annehmen kann. Eine unmögliche Bedingung wird abgelehnt statt gespeichert.

Listen brauchen ein items-Schema. Verschachtelte Objekte haben eigene properties.

{
"tags": {
"type": "array",
"title": "Tags",
"items": { "type": "string" },
"uniqueItems": true
},
"contact": {
"type": "object",
"title": "Kontakt",
"properties": {
"email": { "type": "string", "format": "email" },
"phone": { "type": "string" }
}
}
}

Jedes einfache String-Feld bekommt automatisch die @-Mention-Suche. Tippe @ in einem Textfeld, um eine andere Entität zu referenzieren. Die Mention wird als @[Name](entity:UUID) im Wert gespeichert und beim Anzeigen als klickbarer Link gerendert. Siehe @-Mentions für Details.

Mentions sind deaktiviert bei:

  • Feldern mit enum (Dropdown)
  • Feldern mit format: "date" (Date-Picker)

Im Normalmodus rendert ein eigener Renderer die Werte nicht stumpf als Text, sondern abhängig vom Feldtyp und einer optionalen Presentation-Auswahl. Siehe Normalmodus & Expertenmodus.

x-presentationEffekt
"long-text"Mehrzeilig mit line-clamp + „Mehr”-Toggle
"markdown"Markdown-Rendering
"badge"Badge statt Text
"date-relative"„vor 5 Minuten”
"date-absolute"„2026-05-27”
"email", "url"Klickbarer Link
"boolean-icon"Check-/X-Icon
"currency" + x-presentation-config.currencyWährungsformat
"progress" + x-presentation-config.maxFortschrittsbalken
"bullet-list"Aufzählung (Bulletlist) aus Array — Standard für jedes Array
"chip-list"Chips aus Array
"link-list"Link-Liste aus Array nackter URLs
"document-links"Dokumentenliste aus Array von {label, url} — Dateityp-Icon, Bezeichnung, Vorschau, siehe Dokumente verlinken
"image"Bild (Upload im Formular)
"icon"Icon (Auswahlliste im Formular)
"hidden"Wird im Normalmodus ausgeblendet

Ein Feld vom Typ array ist eine Aufzählung und wird im Normalmodus deshalb ohne weitere Konfiguration als Bulletlist gerendert — je Eintrag eine Zeile, wie im Expertenmodus. Wer stattdessen Chips oder eine Link-Liste möchte, wählt sie explizit aus.

Diese Werte setzt du nicht im rohen JSON, sondern komfortabel im UI-Editor „Darstellung pro Feld” direkt unter dem JSON-Editor.

Die beiden Datums-Darstellungen (date-relative, date-absolute) betreffen nicht nur die Anzeige im Normalmodus: Bei der Auswahl wird am Feld zusätzlich format: "date" gesetzt, damit im Edit-Modus ein Date-Picker erscheint. Wechselst du wieder auf eine Nicht-Datums-Darstellung, wird dieses automatisch gesetzte format: "date" wieder entfernt.

Dasselbe gilt für Links: Wählst du die Darstellung URL, wird format: "uri" gesetzt — damit erscheint im Edit-Modus das Link-Feld mit Öffnen-Knopf, und beim CSV-Import wird der Wert geprüft. Bei Link-Liste landet das Format auf items, weil dort die Einträge die URLs tragen. Links sind in beiden Ansichtsmodi klickbar. Details in Dokumente verlinken.

Manchmal soll nicht der Entitätstyp ein Icon tragen, sondern die einzelne Entität — z.B. ein Symbol je Abteilung oder je Risikokategorie. Dafür legst du ein ganz normales string-Feld an und wählst im Editor „Darstellung pro Feld” die Darstellung Icon:

"symbol": {
"type": "string",
"title": "Symbol",
"x-presentation": "icon"
}

Wirkung:

  • Im Formular erscheint statt eines Textfeldes eine durchsuchbare Icon-Auswahl — dieselbe Liste, die du auch für das Icon des Entitätstyps benutzt. Niemand muss Icon-Namen abtippen.

    Icon-Auswahl mit Suchfeld und Icon-Raster — hier im Entitätstyp-Dialog; bei einem Icon-Feld erscheint dieselbe Auswahl im Formular

  • Im Normalmodus wird das Icon samt Namen angezeigt statt der Rohreferenz.

  • In der Listen- und der Cluster-Ansicht trägt jeder Eintrag das Icon seiner Entität statt des für alle Einträge gleichen Typ-Icons. Ohne gesetztes Icon bleibt es beim Typ-Icon. Hat ein Typ mehrere Icon-Felder, zählt für diese Ansichten das erste im Schema — die übrigen bleiben normale Felder in der Detailansicht.

Gespeichert wird eine Icon-Referenz der Form lucide:<Name>, z.B. lucide:Shield. Das Präfix benennt die Icon-Quelle; heute gibt es genau eine (die mitgelieferte Icon-Sammlung), später können weitere dazukommen, ohne dass bestehende Werte angepasst werden müssen. Referenzen ohne Präfix (Shield) gelten weiterhin und werden als Icons der mitgelieferten Sammlung gelesen.

Ist ein gespeichertes Icon unbekannt (z.B. weil es aus einem Backup eines neueren Stands kommt), zeigt die Detailseite die Referenz als Text an — der Wert geht nie verloren.

Soll eine einzelne Entität eine eigene Farbe tragen — etwa eine Kennzeichnungsfarbe je Team oder je Kategorie —, legst du ein normales string-Feld an und wählst die Darstellung Farbe:

"tint": {
"type": "string",
"title": "Kennfarbe",
"x-presentation": "color"
}

Wirkung:

  • Im Formular erscheint ein echter Farbwähler mit Vorschau-Kachel und Hex-Eingabe. Niemand muss einen Farbcode wie #3B82F6 von Hand tippen.
  • Im Normalmodus wird die Kachel neben dem Hex-Code angezeigt.

Gespeichert wird der Hex-String — kein eigenes Format. Ein Wert, der kein Hex-Code ist (z.B. aus einem Import), wird als Text angezeigt statt als leere Kachel; der Wert geht nie verloren.

Nicht zu verwechseln mit x-node-color weiter unten: dort wird die Farbe abgeleitet (aus einem Enum-Wert, für Graphen-Knoten), hier ist die Farbe selbst der eingegebene Wert.

Es gibt vier Stellen, an denen eine Farbe entstehen kann, und sie können sich widersprechen. Damit das nicht vom Zufall abhängt, gilt überall dieselbe Rangfolge — geprüft wird von oben nach unten, die erste Stelle mit einem gültigen Wert gewinnt:

#WoherWo eingestelltGilt für
1Farbe der EntitätFarbfeld an der Entität selbst (x-presentation: "color")genau diese eine Entität
2Attributwert, erste Ebenex-node-color, erste Ebenealle Entitäten mit diesem Wert
3Typ-FarbeFeld „Farbe” am Entitätstypalle Entitäten des Typs
4Palettenfarbe des Modulsnichts — sie gilt automatischalle Entitäten des Moduls

Zwei Dinge laufen daneben und werden von dieser Rangfolge nicht verdrängt:

  • Die zweite Farbebene aus x-node-color färbt den Akzent (Rand, Ring, Hülle) und bleibt auch dann sichtbar, wenn jemand die Entität einfärbt. Sonst ginge die zweite Aussage verloren, sobald eine einzelne Entität eine eigene Farbe bekommt.
  • Das Muster aus x-node-texture legt sich über die Fläche, gleich aus welcher Ebene deren Farbe stammt.

Ein Wert, der kein gültiger Hex-Code ist, zählt als nicht gesetzt — dann greift die nächste Ebene, statt dass die Farbe ersatzlos verschwindet.

Ist am Entitätstyp keine Farbe gesetzt, malt die App die Palettenfarbe des Moduls. Der Farbwähler im Typ-Editor zeigt genau diese Farbe an und markiert sie gestrichelt: Sie gilt, ist aber nicht gesetzt.

Der 2D-/3D-Beziehungsgraph teilt die Rangfolge auf. Dort liegen Knoten vieler Typen nebeneinander, und „welche Sorte Ding ist das?” ist die primäre Frage. Fläche und Außenring tragen deshalb Ebene 1 → 3 → 4 (also ohne die Attributfarbe), und die Attributfarbe erscheint als kleiner Punkt in der Mitte. Die Legende zeichnet das nach: dort ein Punkt im Ring, sonst ein voller Punkt.

Der Explorer macht es wie die Fachansichten, obwohl auch er Typen mischt: Dort trägt die Fläche die volle Rangfolge einschließlich Attributfarbe, und die Sorte des Dings sagt ein kleiner Punkt am Anfang der Zeile. Der Grund ist der Zweck der Ansicht — im Explorer geht man von einer Entität zur nächsten und will unterwegs dieselbe Entität wiedererkennen, die man aus ihrer Liste kennt. Ein OKR sieht dort deshalb aus wie ein OKR — mit Kopfband und Key Results —, nicht wie ein Punkt in einem Netz.

Was der Explorer nicht zeigen kann: Zahlen und Listen einer einzelnen Entität — den Fortschritt eines Key Results etwa, oder die Mitglieder einer Einheit. Er lädt seine Nachbarschaft über eine schlanke Übersicht, die Auswahlfelder und das Farbfeld enthält, aber keine Detailwerte. Farbe, Muster und die Auswahlfelder (Typ, Status, Quartal …) kommen an; alles Weitere steht auf der Detailseite.

Manche technischen Entitätstypen bündeln semantisch unterschiedliche Dinge im selben Typ — z.B. Rollen und Kreise (Feld subtype) oder Objectives und Key Results (Feld subtype). In Graphen haben diese Knoten dieselbe Typ-Farbe, sodass man ihre Bedeutung nur durch Hineinklicken erkennt.

Mit einer Textur (Muster über der Farbe) unterscheidest du sie auf einen Blick. Dazu wählst du genau ein Enum-Attribut als „Textur-Attribut” und ordnest jedem seiner Werte eine Textur zu:

  • Ohne (einfarbig, Standard), Diagonalstreifen, Waagerechte / Senkrechte Streifen, Punkte, Gitter, Kreuzschraffur.

Die Textur liegt als Muster über der bestehenden Typ-Farbe (die Farbe bleibt erhalten) und wird in allen Graphen berücksichtigt — im Explorer, in der Rollen-Kreis-Ansicht sowie im 2D-/3D-Beziehungsgraphen. Eine Legende erklärt, welches Muster welchem Wert entspricht — und zwar für jede konfigurierte Musterebene, auch für die, die am Knoten nicht mehr gezeichnet wird.

Konfiguriert wird das nicht im rohen JSON, sondern im UI-Abschnitt „Knoten-Textur im Graphen” direkt unter dem Editor „Darstellung pro Feld”. Wähle dort das Treiberattribut und ordne pro Wert eine Textur zu. Nur Enum-Felder kommen als Treiber infrage.

Gespeichert wird die Zuordnung als x-node-texture am Schema-Wurzelobjekt:

{
"type": "object",
"properties": {
"subtype": { "type": "string", "enum": ["objective", "key_result"] }
},
"x-node-texture": {
"field": "subtype",
"map": { "key_result": "diagonal-stripes" }
}
}

Die Schwester der Knoten-Textur — nur eben für Farbe. Die Farbe eines Knotens hängt sonst am Entitätstyp und ist damit für alle Entitäten dieses Typs gleich: Muster unterscheiden, Farbe nicht.

Du wählst ein Enum-Attribut als Farb-Treiber und ordnest jedem Wert eine Farbe aus dem Katalog zu. Kein Wert zugeordnet ⇒ es bleibt bei der Typ-Farbe.

Mehrere Ebenen kombinieren. Farbe und Muster sind zwei unabhängige Kanäle und dürfen von verschiedenen Attributen getrieben werden. Auch innerhalb der Farbe sind mehrere Ebenen möglich:

  • Die erste Farbebene färbt die Fläche des Knotens.
  • Die zweite setzt einen Akzent auf den Rand.
  • Weitere Ebenen sind erlaubt, werden aber nicht am Knoten gezeichnet: mehr als zwei Farbflächen sind auf einem Knoten nicht auseinanderzuhalten. Sie erscheinen in der Legende und lassen sich filtern; der Editor beschriftet sie dort sichtbar mit „nur Legende”.
  • Bei den Mustern wird genau eines gezeichnet — zwei übereinandergelegte Muster kann niemand mehr trennen.

Warum ein fester Farb-Katalog statt freier Farbwerte? Jeder Eintrag bringt eine geprüfte Hell- und eine Dunkelstufe mit, und die App wählt zur Laufzeit die passende. Eine frei gewählte dunkle Farbe wäre im Dunkelmodus praktisch unsichtbar.

Barrierefreiheit. Neun frei zuweisbare Farben lassen sich nicht so wählen, dass jedes Paar auch für Farbfehlsichtige sicher trennbar ist — Orange und Bernstein, Rot und Orange, Petrol und Grün liegen nah beieinander. Deshalb ist die Textur der zweite Kanal: Wo eine sichere Unterscheidung gebraucht wird, vergib zusätzlich Muster. Die Farbe schmückt dann, das Muster trägt die Information.

Wo die Farbe wirkt: Listenansicht, Cluster-Ansicht und Rollen-Kreis-Ansicht — dort jeweils als Fläche. Im Beziehungsgraphen tragen Fläche und Außenring bewusst die Identität des Knotens (siehe Rangfolge oben) — dort ist „welche Sorte Ding ist das?” die wichtigere Frage; die erste Farbebene erscheint als kleiner Punkt in der Mitte, die zweite als Ring knapp außerhalb des Knotens (im 3D-Graphen als durchscheinende Hülle). So bleiben beide Ebenen lesbar, ohne dass die Typzugehörigkeit ihren Platz verliert.

Drei Zustände können eine Farbe im Graphen vorübergehend ersetzen: ein zum Stichtag der Zeitleiste gelöschter Knoten wird grau mit rotem Rand, ein seit dem Stichtag neuer bekommt einen grünen Rand, der Fokusknoten einen dicken roten. Die Legende führt sie auf, sobald sie auftreten können.

In der Rollen-Kreis-Ansicht kodiert die Hell-Dunkel-Staffelung normalerweise die Hierarchie-Ebene. Färbst du dort auch Kreise ein, verliert diese Staffelung ihre Aussage. Das ist bewusst erlaubt — jeder Knoten darf seine eigene Farbe tragen —, aber es ist eine Entscheidung mit Nebenwirkung.

Konfiguriert wird das im UI-Abschnitt „Knoten-Farbe nach Attributwert” direkt unter der Textur-Konfiguration.

Knoten-Farbe nach Attributwert: Treiberattribut und Zuordnung Wert → Farbe Gespeichert wird es als x-node-color am Schema-Wurzelobjekt:

{
"type": "object",
"properties": {
"eaType": { "type": "string", "enum": ["application", "capability"] },
"criticality": { "type": "string", "enum": ["hoch", "niedrig"] }
},
"x-node-color": [
{ "field": "eaType", "map": { "application": "blue", "capability": "violet" } },
{ "field": "criticality", "map": { "hoch": "red" } }
]
}

In den Diagrammansichten erscheint eine Entität als Karte. Woraus diese Karte besteht, steht im Schema — genau wie Felddarstellung, Farbe und Muster. Damit sieht dieselbe Entität überall gleich aus: in ihrer Fachansicht, im Explorer und im Vorschaubild der Detailseite.

{
"x-card": {
"width": 260,
"padding": 0,
"slots": [
{ "slot": "band", "field": "subtype" },
{ "slot": "related-list", "via": "HAS_KEY_RESULT", "valueField": "progress" }
]
}
}

Eine Karte besteht aus Slots, die von oben nach unten erscheinen:

Slotzeigtbraucht
bandfarbiges Kopfband mit dem Wert eines Auswahlfeldes und dem Namenfield
titleden Namen, ein bis zwei Zeilen–
subtitleeine gedämpfte Zeile aus einem Feldfield
badgesmehrere Felder als kleine Chipsfields
metriceine Zahl mit Fortschrittsbalkenfield
related-listverknüpfte Einträge als Zeilen, optional mit Balkenvia, optional valueField
avatarsdie verknüpften Personen als Profilbildervia
portraitdas eigene Profilbild (oder die Initialen)–

width ist die Breite in Pixeln, padding der Innenabstand oben und unten. Beides geht in die Größe des Kästchens ein, denn das Diagramm muss sie kennen, bevor es zeichnet.

related-list und avatars sind der Unterschied zu allen anderen Annotationen: sie sprechen nicht über ein Feld, sondern über eine Beziehung. via nennt den Beziehungstyp, direction die Richtung (outgoing — Vorgabe —, incoming oder both).

So zeigt eine Rolle die Menschen, die sie ausfüllen: { "slot": "avatars", "via": "EXECUTES", "direction": "incoming" } — die Beziehung läuft von der Person zur Rolle, aus Sicht der Rolle also eingehend.

Eine Karte kann ihre Farbe an zwei Stellen tragen, und das sagt tones:

"x-card": { "width": 260, "padding": 0, "tones": "band", "slots": [ … ] }
  • "flaeche" (Vorgabe) — der Kartenkörper ist eingefärbt. Der Normalfall; du musst ihn nicht hinschreiben.
  • "band" — das Kopfband trägt den Vollton, der Körper bleibt neutral. Das setzt einen band-Slot voraus; ohne ihn hätte die Karte danach gar keine Farbe mehr, und die Angabe wird beim Speichern abgelehnt.

Neu ist daran nicht die Bedeutung — Farbe steht weiter für die Identität einer Entität. Neu ist, dass du entscheidest, wo sie sitzt. Bis dahin entschied das die Ansicht, in der eine Entität zufällig auftauchte: die frühere OKR-Kaskade legte den Vollton ins Kopfband, jede andere Ansicht in die Fläche — dieselbe Entität sah also je nach Ansicht anders aus.

Ein badges-Slot darf eines seiner Felder als Zustand ausweisen. Der Wert bekommt dann einen kleinen Punkt in der passenden Statusfarbe:

{
"slot": "badges",
"fields": ["projectType", "status"],
"states": {
"field": "status",
"map": { "in_progress": "good", "planned": "neutral", "stopped": "critical" }
}
}

Erlaubt sind genau vier Zustände: good, warning, critical, neutral. Keine freien Farben — Statusfarben sind in der Anwendung für genau diese Aussage reserviert, und eine frei gewählte Farbe für „Region 3” würde jede echte Warnung entwerten. Umgekehrt bleibt die Farbpalette der Knoten (x-node-color) für Kategorien und sagt nie etwas über einen Zustand.

Drei Dinge, die dabei gelten:

  • Das Wort bleibt stehen. Der Punkt steht davor, nicht an seiner Stelle — rund acht Prozent der Männer unterscheiden Rot und Grün nicht zuverlässig.
  • Ein Wert ohne Eintrag bekommt keinen Punkt. Nicht etwa einen grauen: ein Zeichen, das erscheint, obwohl die Zuordnung fehlt, behauptet etwas über Daten, die es nicht gibt.
  • Nur das genannte Feld trägt den Punkt, nicht alle Chips der Zeile.

field muss eines der Felder desselben Slots sein. Benennst du das Feld später um, zieht die Zuordnung mit; löschst du es, verschwindet sie.

Ein related-list-Slot hat eine zweite Wirkung: Die verknüpften Einträge bekommen im Explorer kein eigenes Kästchen mehr — sie stehen ja schon in der Karte. Ein Key Result erscheint dort nur, wenn sein Objective gerade nicht im Bild ist oder du es selbst zum Anker gemacht hast. Beziehungen, die an einem solchen Eintrag hängen, führen sichtbar zur Karte seines Wirts.

Manche Dinge enthalten andere: ein Unternehmen seine Bereiche, eine Prozessgruppe ihre Schritte. Im Explorer lässt sich das als Kasten zeigen — die enthaltenen Knoten liegen darin statt daneben:

"x-nesting": {
"via": "PARENT_OF",
"direction": "outgoing",
"wenn": { "field": "orgType", "equals": "company" }
}
  • via — über welchen Beziehungstyp die Kinder hängen. Ohne Angabe gilt jede Beziehung, die in deinem Mandanten als Über-/Unterordnung eingerichtet ist.
  • direction — outgoing (Vorgabe): der Knoten am Anfang der Beziehung ist der Behälter.
  • wenn — optional. Nur Entitäten, deren Feld den genannten Wert trägt, werden zum Behälter. Ohne die Bedingung wird genistet, sobald es Kinder gibt.
  • eigenstaendig — optional. true heißt: ein Behälter dieser Art liegt nie in einem anderen, sondern steht immer für sich. Das Organigramm setzt es, weil eine Gesellschaft nicht Teil einer anderen ist — eine Beteiligung ist keine Zugehörigkeit. Ohne die Angabe darf ein Behälter in einem anderen liegen (Kasten im Kasten), so wie eine Teilkette in ihrem Wertstrom.
  • richtung — optional, "unten" (Vorgabe) oder "rechts". In welche Richtung der Inhalt dieses Kastens angeordnet wird. Eine Hierarchie liest sich von oben nach unten, ein Ablauf von links nach rechts; die Wertschöpfung setzt deshalb "rechts". Betrifft nur das Innere — die Ebene darüber bleibt, wie sie ist.

Der Kasten hält alles unter sich, nicht nur die unmittelbaren Kinder: eine Gesellschaft umschließt ihre Bereiche, deren Abteilungen und deren Teams. Der Abstieg endet an jedem weiteren Behälter — was unter ihm liegt, gehört ihm. Gehört eine Entität zu zwei Behältern, liegt sie in genau einem (dem näheren); die andere Beziehung bleibt als Linie sichtbar.

Der Kasten hat einen durchgezogenen Rand und eine durchscheinende Fläche. Beides hat eine Bedeutung: durchgezogen heißt „diese Sache besteht” (gestrichelt hieße „vorläufig”), und durchscheinend, damit die Linien der eingeschlossenen Knoten sichtbar bleiben.

Zwei Dinge, die dabei gelten:

  • Die Beziehung, die das Nisten ausmacht, wird nicht mehr als Linie gezeichnet. Der Kasten sagt bereits „was darin liegt, ist Teil davon” — eine Linie daneben sagte dasselbe ein zweites Mal. Alle übrigen Beziehungen bleiben.
  • x-nesting und related-list dürfen nicht auf denselben Beziehungstyp zeigen. Sie meinen das Gegenteil: die Liste sagt „die Einträge stehen in meiner Karte und bekommen kein eigenes Kästchen”, das Nisten sagt „sie behalten ihr Kästchen und werden in meinen Grenzen gezeichnet”. Die Anwendung lehnt das beim Speichern ab.

Die Fachansichten (Organigramm, Wertschöpfung) bringen ihre eigene Verschachtelung mit und ändern sich dadurch nicht. Sie zeigen zusätzlich Dinge, die der Kasten allein nicht ausdrückt — etwa Beteiligungen zwischen Unternehmen.

Zeigt ein Listen-Slot Einträge, deren Datensätze noch nicht geladen sind, erscheint die schlanke Standardkarte statt einer leeren Liste. Der Grund: „keine Key Results” und „noch nicht geladen” sähen sonst gleich aus, und das erste wäre eine Behauptung ins Blaue.

Dasselbe gilt für Fehler in der Deklaration: Ein Slot, dem sein Feld fehlt, wird übersprungen — die übrige Karte bleibt. Beim Speichern dagegen meldet die Anwendung solche Fehler, damit eine Konfiguration nicht still wirkungslos bleibt.

Steht kein x-card im Schema, zeigt die Entität die schlanke Standardkarte: Name plus eine Zeile aus ihren Auswahlfeldern. Das ist selten gewollt — ein Objective ohne Kopfband und ohne seine Key Results sagt kaum noch etwas. Lege deshalb die Vorlage deines Moduls zugrunde, statt ohne Deklaration zu bleiben.

Die Karte, die dein Entitätstyp mitbekommen hat, ist eine Kopie der Vorlage aus dem Modul — sie gehört ab dann dir. Verbessern wir die Vorlage später, wäre diese Kopie also für immer auf dem alten Stand.

Deshalb gilt: Solange du deine Karte nicht selbst angefasst hast, ziehen wir sie mit. Beim nächsten Start prüft die Anwendung, ob deine Karte einer von uns ausgelieferten Fassung entspricht — wenn ja, bekommst du die neue.

Hast du sie angepasst, bleibt sie unangetastet. Auch dann, wenn die Änderung klein war. Wir können nicht unterscheiden, ob du etwas absichtlich weggelassen hast oder ob es aus einer alten Fassung stammt — und im Zweifel gehört die Entscheidung dir. Der Preis ist, dass spätere Verbesserungen der Vorlage dich nicht mehr erreichen; wer eine eigene Karte führt, zieht sie selbst nach. Im Serverprotokoll steht bei jedem Start, welche Entitätstypen das betrifft.

Zusammengeführt wird dabei nichts: entweder die ganze Karte wird ersetzt, oder gar nichts. Eine halb gehobene Karte wäre weder deine noch unsere.

Neben der Karte kann ein Entitätstyp ein Brett deklarieren: welches Auswahlfeld die Spalten aufspannt und über welche Beziehungen die Bahnen (Swimlanes) laufen. Steht nichts da, bietet die Seite keine Brett-Ansicht an.

"x-kanban": {
"spalten": { "field": "status" },
"bahnen": [
{ "id": "rolle", "via": "ASSIGNED_TO", "direction": "incoming", "ende": "gegenueber",
"label": { "de": "Rolle", "en": "Role" } },
{ "id": "person", "via": "ASSIGNED_TO", "direction": "incoming", "beteiligte": "holder",
"label": { "de": "Person", "en": "Person" } }
]
}

spalten.field muss ein Feld sein, das es im Schema gibt und das eine Auswahlliste (enum) führt. Ohne Auswahlliste gäbe es keine feste Spaltenmenge, sondern so viele Spalten wie zufällig Werte im Bestand stehen — ein Brett, dessen Form von den Daten abhängt, ist keines. Die Reihenfolge der Spalten steht bewusst nicht in dieser Deklaration: sie kommt aus deiner enum-Reihenfolge. Zwei Quellen für dieselbe Ordnung liefen beim nächsten Umbau auseinander.

Jede Bahn nennt eine Beziehung (via) und eine von zwei Fragen an sie:

AngabeDie Bahn trägt …
"ende": "gegenueber"wer am anderen Ende der Beziehung hängt (bei ASSIGNED_TO: die eingesetzte Rolle)
"beteiligte": "<slot>"wer als Beteiligter an der Beziehung selbst hängt (bei ASSIGNED_TO, Slot holder: die Personen, die die Rolle an diesem Projekt ausfüllen)

Beides zugleich wären zwei Achsen unter einer Kennung; keines von beiden wäre eine Achse ohne Wert. Die Anwendung lehnt beides beim Speichern ab. direction liest sich wie bei den Karten-Slots (incoming — jemand zeigt auf mich, die Voreinstellung — outgoing, both).

Zwei Dinge, die man wissen muss:

  • Eine Bahn ist zugleich eine Datenforderung: Der genannte Beziehungstyp muss in deinem Mandanten existieren. Fehlt er, bietet die Oberfläche die Achse gar nicht erst an — sie zeigte sonst nur leere Bänder, und das sähe aus wie „niemand ist zugeordnet”.
  • Benennst du das Spaltenfeld um, zieht die Schema-Migration die Deklaration mit. Löschst du es, fällt die ganze Deklaration weg — ein Brett ohne Spaltenfeld hätte keine Bedeutung.

Wie das Brett sich für Nutzerinnen und Nutzer anfühlt, steht in Kanban-Brett.

Überall dort, wo Farben oder Muster wirken, erklärt eine Legende, welcher Wert wofür steht. Jeder Eintrag ist zugleich ein Filter: ein Klick blendet die betroffenen Knoten ab — sie bleiben stehen und treten nur zurück.

Das gilt in allen Ansichten, die diese Kodierungen zeichnen: im Explorer, in der Rollen-Kreis-Ansicht, im Beziehungsgraphen (2D und 3D), im Nachbar-Graphen auf der Detailseite und im Graph-Baustein des Dashboards.

Auch die Ebenen, die nicht gezeichnet werden, stehen in der Legende — jede weitere Farb- oder Musterebene erscheint dort mit dem Zusatz „nur Legende” und lässt sich trotzdem filtern. Eine konfigurierte Ebene ohne Legendeneintrag wäre sonst unsichtbar und unfilterbar zugleich.

Replikate haben einen eigenen Eintrag: gespiegelte Entitäten tragen Diagonalstreifen und einen gestrichelten Rahmen. Der Rahmen ist nötig, weil eine konfigurierte Textur denselben Kanal belegt wie die Streifen — so bleibt das Replikat auch dann erkennbar, wenn der Knoten zusätzlich ein Muster trägt. Der Eintrag erscheint nur, wenn die Ansicht überhaupt Replikate enthält.

Abblenden statt Ausblenden ist Absicht: Der Explorer und die Rollen-Kreis-Ansicht zeichnen Bäume. Würden gefilterte Knoten verschwinden, hingen ihre Kinder in der Luft oder verschwänden mit. Ein Wert-Filter ist außerdem eine Lesehilfe („wo sitzen die Applikationen?”), keine Datenreduktion.

Filter und Suche addieren sich: ein Knoten tritt zurück, wenn die Suche ihn nicht trifft oder sein Wert abgewählt ist. Der Filter gilt für die aktuelle Ansicht und wird nicht gespeichert.

Ein Schema, das mehrere Features kombiniert:

{
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "Name",
"minLength": 2
},
"description": {
"type": "string",
"title": "Beschreibung",
"multi": true,
"x-presentation": "long-text"
},
"status": {
"type": "string",
"title": "Status",
"enum": ["draft", "active", "archived"],
"default": "draft",
"x-presentation": "badge"
},
"blockReason": {
"type": "string",
"title": "Blockierungsgrund",
"multi": true,
"showWhen": { "field": "status", "values": ["archived"] }
},
"validFrom": {
"type": "string",
"format": "date",
"title": "Gültig ab",
"x-presentation": "date-relative"
},
"tags": {
"type": "array",
"title": "Tags",
"items": { "type": "string" },
"uniqueItems": true,
"x-presentation": "chip-list"
}
},
"required": ["name", "status"]
}