/// Fehler des Anzeigenamens. enum DisplayNameError { empty, tooLong, /// Enthält U+200B (Zero Width Space). Die UI zeigt dieselbe Meldung wie für /// einen ungültigen Namen (Spec #1 § 5.4), kein eigener Text. invalidCharacter, } /// Regel für Anzeigenamen (KTO-01 Kriterium 10, KTO-02 Kriterium 1). /// /// Identisch zum Server (`public.trim_ws` + `char_length`): Leerraum am Rand /// wird entfernt (Dart `trim()` und `trim_ws` entfernen dieselben Zeichen, /// auch NBSP und Tab). Danach 1 bis 30 Unicode-Codepoints, gezählt über /// `runes`, nicht über `String.length` (UTF-16). U+200B wird nicht getrimmt, /// sondern überall abgelehnt. abstract final class DisplayNameRule { static const maxLength = 30; static const zeroWidthSpace = '​'; /// Der Wert, der gespeichert und gesendet wird. static String normalize(String value) => value.trim(); /// Länge in Codepoints nach dem Trim (für den Zähler `n/30`). static int length(String value) => normalize(value).runes.length; static DisplayNameError? validate(String value) { final name = normalize(value); if (name.contains(zeroWidthSpace)) { return DisplayNameError.invalidCharacter; } final count = name.runes.length; if (count == 0) { return DisplayNameError.empty; } if (count > maxLength) { return DisplayNameError.tooLong; } return null; } }