Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
29 KiB
quick_id, slug, date, status, relates_to, phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, estimate, must_haves
| quick_id | slug | date | status | relates_to | phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | estimate | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 260907-fgv | tessera-logo-und-favicon-fest-einbauen-b | 2026-09-07 | planned | 01-foundation-portal-shell | quick-260907-fgv | 01 | execute | 1 |
|
false |
|
|
|
Purpose: Die Plattform traegt bis heute an jeder Marken-Stelle nur das Wort "Tessera" als Text, dazu auf der Anmeldeseite ein handgezeichnetes Vier-Quadrate-Platzhalter-Icon. Das gelieferte Logo ersetzt beides.
Output: Eine zentrale Komponente TesseraLogo, eine Farbkonstanten-Datei, zwei
Icon-Dateien fuer den Browser-Tab, sechs umgestellte Verwendungsstellen.
Die gelieferten Dateien und was mit ihnen geschieht
Der Ordner user-files/logo/ ist ueber .gitignore:32 vom Repository ausgeschlossen.
Nichts darf von dort aus referenziert werden — was gebraucht wird, wird kopiert.
| Datei | Verwendung |
|---|---|
favicon.svg |
wird nach apps/web/src/app/icon.svg kopiert (Browser-Tab) |
tessera-logo.svg |
wird NICHT kopiert — diff weist sie als byteweise identisch zu favicon.svg aus |
apple-touch-icon.png |
wird nach apps/web/src/app/apple-icon.png kopiert |
tessera-logo-schriftzug.svg |
wird NICHT kopiert — Begruendung unten |
favicon-16.png, favicon-32.png |
werden NICHT kopiert — jeder von Tessera unterstuetzte Browser zeigt SVG-Favicons |
tessera-logo-512.png |
wird NICHT kopiert — es gibt keine Web-App-Manifest-Datei, die sie braeuchte |
Vier Entscheidungen, die vorab getroffen wurden
1. Der Schriftzug wird nicht als Bild ausgeliefert, sondern als echter Text neben der
Bildmarke gesetzt. Zwei unabhaengige Gruende. Erstens die Farbe: in
tessera-logo-schriftzug.svg steht die Textfarbe zweimal fest eingebacken als dunkler
Ton; auf dem dunklen Erscheinungsbild der Anwendung waere der Schriftzug unsichtbar.
Zweitens die Schrift: die Datei verlangt die Schriftart Archivo in Schnitt 900 — die
Anwendung laedt ueber --font-sans in globals.css:33 ausschliesslich Inter, Archivo
ist nirgends eingebunden. Der Browser wuerde also ohnehin eine andere Schrift einsetzen,
mit anderen Buchstabenbreiten, in einem starr auf 420x100 festgelegten Zeichenbereich —
der Schriftzug wuerde je nach Rechner ueberlaufen oder verloren in der Flaeche stehen.
Beides faellt weg, wenn "Tessera" normaler HTML-Text in der Hausschrift ist: er erbt die
Vordergrundfarbe, er bricht nie, und er kommt aus common.appName, ist also uebersetzbar.
Die Alternative — je eine helle und eine dunkle Variante der Bilddatei ausliefern — waere
zwei Dateien, ein Theme-Hook und ein Umschaltflackern beim Seitenaufbau; sie loest das
Schriftproblem ausserdem gar nicht.
2. Die dunkle Platte der Bildmarke bekommt im dunklen Erscheinungsbild eine Kontur.
Nachgerechnet, nicht vermutet: die Platte der Marke ist ein sehr dunkles Grau, der
dunkle Anwendungshintergrund oklch(0.23 0.01 260) entspricht #1a1d22, die dunkle
Seitenleiste oklch(0.20 0.01 260) entspricht #13161b. Platte und Hintergrund sind
damit praktisch derselbe Ton — die Platte verschwindet, die fuenf Kacheln wuerden frei
schweben. Die Marke selbst bleibt trotzdem unangetastet: die Platte erhaelt lediglich
ueber eine dark:-Utility-Klasse eine halbtransparente helle Haarlinie als Kontur. Im
hellen Erscheinungsbild ist die Kontur durchsichtig, dort aendert sich also nichts. Das
ist dieselbe Behandlung, die die Anwendung ohnehin fuer dunkle Flaechen auf dunklem
Grund verwendet. Auf dem gelben Anmelde-Panel wird die Kontur per Eigenschaft
abgeschaltet, weil dieses Panel unabhaengig vom gewaehlten Erscheinungsbild gelb bleibt
und die Platte dort von sich aus maximalen Kontrast hat. Fuer die Icon-Datei im
Browser-Tab bleibt die Platte ohne Kontur: die Tab-Leiste ist keine Flaeche dieser
Anwendung, und die Wiedererkennung traegt dort ohnehin die helle Signalkachel.
3. Der Gelbwert steht kuenftig genau einmal im Quellcode. Er kommt heute dreimal vor:
als Hintergrund-Utility des Anmelde-Panels, im Kopfkommentar derselben Datei und als
Vorgabewert der persoenlichen Akzentfarbe in account-settings-form.tsx:14. Kuenftig
steht er nur noch in apps/web/src/components/brand/brand.ts; die drei Stellen lesen ihn
von dort. Bewusst KEINE neue CSS-Variable in globals.css: die Auswahl der Akzentfarbe
laeuft ueber ein <input type="color">, das zwingend einen echten Farbwert braucht und
mit einer CSS-Variablen nichts anfangen kann. Eine TypeScript-Konstante bedient alle drei
Stellen, eine CSS-Variable koennte es nicht. Der vorhandene Token --primary bleibt
unberuehrt: er ist ein rechnerisch leicht abweichender Ton (#fbe405 gegen den Gelbwert
der Marke) und steuert Schaltflaechen, Fokusringe und Aktivmarkierungen — ihn auf den
Markenwert zu ziehen waere eine Umfaerbung der gesamten Anwendung und ist nicht Teil
dieser Aufgabe.
4. Der Browser-Tab wird ueber dateibasierte Metadaten verdrahtet, nicht ueber
metadata.icons. Next.js liegt hier in Version 15.5.19 mit App Router vor; eine Datei
src/app/icon.svg erzeugt den <link rel="icon">-Eintrag von selbst. Damit bleibt
layout.tsx unberuehrt — weniger Code, weniger Fehlerquellen. Das leere Verzeichnis
apps/web/public/ bleibt leer; dort abgelegte Icons wuerden ohne zusaetzlichen Eintrag
in layout.tsx nicht gefunden.
Zwei sichtbare Aenderungen, die ueber "Logo einbauen" hinausgehen und deshalb ausdruecklich zur Abnahme gestellt werden
- Der Schriftzug ist kuenftig nicht mehr gelb. Kopfleiste und Seitenleiste setzen "Tessera" heute in der Primaerfarbe, also Gelb auf weissem Grund — schwer lesbar. Das gelieferte Logo setzt den Schriftzug dunkel. Die Komponente folgt dem Logo und nutzt die Vordergrundfarbe der Oberflaeche.
- Auf dem gelben Anmelde-Panel steht die Bildmarke kuenftig OBEN. Heute steht dort Schriftzug, Zeile darunter, Platzhalter-Icon ganz unten. Das gelieferte Logo ist als Bildmarke-zuerst aufgebaut; die Komponente uebernimmt diese Reihenfolge. Der bestehende Werbesatz bleibt unveraendert darunter stehen.
Beides steht im Abnahmeschritt und kann dort zurueckgewiesen werden.
Was ausdruecklich NICHT passiert
Keine Hochlade-Maske, keine Einstellungsseite, kein Datenbankfeld, keine Mandantenabfrage fuer das Logo. Der Wunsch "ein Administrator soll das Erscheinungsbild je Mandant anpassen koennen" wird als eigener Backlog-Punkt festgehalten und vom Orchestrator eingereicht — nicht in diesem Plan gebaut.
<execution_context>
@/.claude/gsd-core/workflows/execute-plan.md
@/.claude/gsd-core/templates/summary.md
</execution_context>
Lege apps/web/src/components/brand/brand.ts an. Die Datei exportiert drei benannte
Konstanten fuer die drei Farben der Marke: den Gelbwert der gedrehten Signalkachel als
BRAND_YELLOW, den Olivton der vier uebrigen Kacheln als BRAND_OLIVE und die Farbe
der abgerundeten Grundplatte als BRAND_PLATE. Die Werte werden NICHT aus dieser
Beschreibung abgeschrieben, sondern beim Umsetzen direkt aus
user-files/logo/favicon.svg uebernommen — dort steht die Plattenfarbe am rect mit
rx="14", der Olivton an den vier achsenparallelen Kacheln und der Gelbwert an der
Kachel mit transform="rotate(...)". Ein kurzer Dateikommentar haelt fest, dass dies
die einzige Stelle im Web-Quellcode ist, an der eine Markenfarbe notiert wird, und dass
eine spaetere mandantenspezifische Gestaltung hier ansetzt. Schreibe in diesen Kommentar
keinen weiteren Farbwert hinein — die Konstanten tragen die Werte, der Kommentar
beschreibt nur ihren Zweck.
Lege apps/web/src/components/brand/tessera-logo.tsx an, als 'use client'-Komponente,
weil sie useTranslations aus next-intl benutzt. Exportiere TesseraLogo mit den
Eigenschaften variant ('mark' | 'horizontal' | 'stacked', Vorgabe 'horizontal'),
size (Kantenlaenge der Bildmarke in Pixeln, Vorgabe 32), plateOutline (Vorgabe
true), className und wordmarkClassName.
Die Bildmarke wird als JSX-SVG von Hand gesetzt: viewBox="0 0 72 72", width/height
aus size, role="img" und aria-label aus t('brand.logoAlt') bei
useTranslations('common'). Geometrie und Reihenfolge der sechs Rechtecke werden 1:1 aus
user-files/logo/favicon.svg uebernommen — Position, Groesse, Eckenradius und die
Drehung der Signalkachel unveraendert. Die Fuellfarben kommen aus den drei Konstanten,
nicht als Zahlenwerte im JSX. Setze das SVG als echte JSX-Elemente zusammen; verwende
ausdruecklich NICHT dangerouslySetInnerHTML und lies die Datei auch nicht zur Laufzeit
ein.
Die Grundplatte erhaelt zusaetzlich strokeWidth={2} und, sofern plateOutline wahr ist,
eine Klassenkombination, die die Kontur im hellen Erscheinungsbild durchsichtig laesst und
im dunklen auf eine halbtransparente weisse Linie schaltet (Tailwind-Utilities
stroke-transparent und die zugehoerige dark:-Variante, etwa mit rund 25 Prozent
Deckung). Ist plateOutline falsch, bleibt es bei der durchsichtigen Kontur ohne
dark:-Variante. So bleibt die Umschaltung reines CSS — kein Theme-Hook, kein Flackern
beim Seitenaufbau.
Die Variante mark liefert nur das SVG. horizontal liefert einen inline-flex-Container
mit Bildmarke und daneben dem Schriftzug aus t('appName'), gesetzt in halbfett,
leicht negativer Laufweite und in der Vordergrundfarbe der Oberflaeche (nicht in der
Primaerfarbe); wordmarkClassName wird an das Schriftzug-Element angehaengt, damit
aufrufende Stellen die Farbe ueberschreiben koennen. stacked liefert dieselben beiden
Bausteine als zentrierte Spalte, Bildmarke oben, Schriftzug darunter deutlich groesser.
inline-flex ist Absicht: die aufrufenden Seiten zentrieren ueber text-center des
Elternelements, ein Block-Element wuerde dort links kleben.
Ergaenze in de.json und en.json unterhalb von common das Objekt brand mit dem
Schluessel logoAlt ("Tessera Logo" beziehungsweise "Tessera logo"). Beide Dateien
bekommen exakt denselben Schluesselbaum.
Schreibe apps/web/src/components/brand/tessera-logo.test.tsx nach dem Muster von
sidebar.test.tsx: useTranslations aus next-intl per vi.mock ersetzen, Rueckgabe
ueber eine flache Zuordnung mit appName und 'brand.logoAlt'. Decke die unter
behavior genannten Faelle ab und lies fuer die Sprachparitaet de.json und en.json
direkt als Modul ein, so wie es src/messages/tenderRadar-parity.spec.ts vormacht.
cmp user-files/logo/favicon.svg apps/web/src/app/icon.svg && cmp user-files/logo/apple-touch-icon.png apps/web/src/app/apple-icon.png && pnpm --filter @tessera/web exec vitest run src/components/brand && pnpm --filter @tessera/web type-check
Die beiden Icon-Dateien liegen byteidentisch zur Lieferung unter src/app/. TesseraLogo rendert in allen drei Varianten, der Test ist gruen, beide Sprachdateien tragen den neuen Schluessel, die Typpruefung ist sauber.
Ersetze in sidebar.tsx das Schriftzug-Element in der Kopfzeile der mobilen Schublade
durch <TesseraLogo variant="horizontal" size={28} />. Die Schliessen-Schaltflaeche
rechts daneben bleibt unveraendert. In sidebar.test.tsx ergaenze die
Uebersetzungs-Attrappe im Bereich common um den Eintrag 'brand.logoAlt' mit einem
sprechenden Wert; die Attrappe arbeitet bereits mit punktierten Schluesseln.
Baue in login/page.tsx die linke, gelbe Spalte um. Der Gelbwert steht dort heute als
Hintergrund-Utility in eckigen Klammern und ein zweites Mal im Kopfkommentar der Datei.
Beide Vorkommen verschwinden: das Utility wird durch ein Inline-style mit
backgroundColor: BRAND_YELLOW ersetzt (Import aus @/components/brand/brand), alle
uebrigen Klassen der Spalte bleiben stehen; der Kopfkommentar wird ohne Nennung eines
Farbwerts neu formuliert und verweist stattdessen auf die Konstante. Setze auf derselben
Spalte zusaetzlich className="text-gray-900" an den inneren, zentrierten Container,
damit der Schriftzug die dunkle Schrift des Panels erbt.
Innerhalb der gelben Spalte entfaellt die Schriftzug-Ueberschrift ersatzlos, und ebenso
ersatzlos entfaellt der handgezeichnete Platzhalter — das ist der <svg>-Block mit den
vier gleich grossen Rechtecken im 24er-Koordinatensystem samt seinem mt-4-Wrapper. An
ihre Stelle tritt, als erstes Element der Spalte, <TesseraLogo variant="stacked" size={112} plateOutline={false} />. plateOutline={false} ist hier Pflicht: das Panel
ist unabhaengig vom Erscheinungsbild immer gelb, eine helle Kontur waere dort falsch.
Der bestehende Absatz mit t('branding.tagline') bleibt unveraendert darunter stehen.
In der nur auf schmalen Fenstern sichtbaren Marken-Gruppe derselben Datei entfaellt die
Schriftzug-Ueberschrift ebenfalls ersatzlos; an ihre Stelle tritt <TesseraLogo variant="horizontal" size={32} />. Der Container traegt bereits text-center, die
Komponente zentriert sich dadurch von selbst. Der Absatz mit dem Werbesatz bleibt.
Ersetze in reset-password/page.tsx und in reset-password/[token]/page.tsx jeweils die
Schriftzug-Ueberschrift ueber der Seitenueberschrift durch <TesseraLogo variant="horizontal" size={32} />. Die jeweils folgende <h2> mit dem Seitentitel
bleibt unveraendert — sie ist die eigentliche Ueberschrift der Seite und darf nicht
mitentfernt werden.
Ersetze in account-settings-form.tsx den Rumpf der lokalen Konstante DEFAULT_ACCENT
durch den aus @/components/brand/brand importierten BRAND_YELLOW. Der Name
DEFAULT_ACCENT bleibt bestehen, damit die drei bestehenden Verwendungsstellen
unveraendert bleiben; nur der Wert kommt jetzt aus der Markendatei.
set -e; test "$(grep -rl 'ffed00' apps/web/src --include='.ts' --include='.tsx')" = 'apps/web/src/components/brand/brand.ts'; test "$(grep -rl 'font-extrabold' 'apps/web/src/app/(auth)' --include='*.tsx' | wc -l)" -eq 0; test "$(grep -c 'width="7" height="7"' 'apps/web/src/app/(auth)/login/page.tsx' || true)" -eq 0; for f in apps/web/src/components/layout/header.tsx apps/web/src/components/layout/sidebar.tsx 'apps/web/src/app/(auth)/login/page.tsx' 'apps/web/src/app/(auth)/reset-password/page.tsx' 'apps/web/src/app/(auth)/reset-password/[token]/page.tsx'; do grep -q 'TesseraLogo' "$f"; done; pnpm --filter @tessera/web exec vitest run; pnpm --filter @tessera/web type-check; docker compose build web && docker compose up -d --force-recreate web; for i in $(seq 1 30); do curl -sf -o /dev/null http://localhost:3000/login && break || sleep 2; done; curl -sf -o /dev/null http://localhost:3000/login
Der Gelbwert der Marke steht im gesamten Web-Quellcode nur noch in brand.ts. In den Anmelde- und Passwort-Seiten steht kein von Hand gesetzter Schriftzug mehr und kein Platzhalter-Icon. Alle fuenf Verwendungsstellen ziehen TesseraLogo. Die vollstaendige Web-Testsuite und die Typpruefung sind gruen. Das LOKALE Web-Abbild ist neu gebaut, der Container laeuft mit dem neuen Stand und die Anmeldeseite antwortet — die Abnahme in Task 3 sieht damit garantiert den neuen Code. Der Testserver alpha.tessera.ctl.de wird dabei NICHT angefasst.
Sollte die Oberflaeche wider Erwarten noch den alten Stand zeigen, hilft
docker compose build web && docker compose up -d --force-recreate web — ein blosses
up baut nicht neu.
Jeder der folgenden sieben Punkte ist EINMAL im hellen und EINMAL im dunklen Erscheinungsbild anzusehen:
- Browser-Tab — im Tab steht die dunkle abgerundete Kachel-Marke, nicht mehr das leere Standard-Blatt. Bei hartnaeckigem alten Symbol den Tab neu laden mit geleertem Zwischenspeicher.
- Kopfleiste — Bildmarke links neben dem Schriftzug "Tessera", in einer Zeile, senkrecht mittig zur Zeilenhoehe, nicht abgeschnitten.
- Seitenleisten-Schublade — Browserfenster schmal ziehen, bis das Menue-Symbol erscheint, Schublade oeffnen: dieselbe Marke in der Kopfzeile der Schublade, die Schliessen-Schaltflaeche daneben unveraendert erreichbar.
- Anmeldeseite, breite Ansicht — abmelden,
http://localhost:3000/login, Fenster breit: das gelbe Panel links zeigt oben die Bildmarke, darunter "Tessera", darunter den bekannten Werbesatz. Das handgezeichnete Vier-Quadrate-Symbol ist verschwunden. Das Gelb des Panels ist unveraendert dasselbe wie vorher. - Anmeldeseite, schmale Ansicht — Fenster schmal ziehen: das gelbe Panel entfaellt wie bisher, ueber dem Anmeldeformular steht die Marke einzeilig und mittig.
- Passwort-vergessen-Seite —
http://localhost:3000/reset-password: Marke oben, darunter unveraendert die Seitenueberschrift und das E-Mail-Feld. - Passwort-neu-setzen-Seite —
http://localhost:3000/reset-password/test123(irgendein Zeichenkettenwert genuegt, die Seite rendert vor der Pruefung): Marke oben, darunter unveraendert Ueberschrift und beide Passwortfelder.
Beim Durchgang im DUNKLEN Erscheinungsbild zusaetzlich pruefen: die dunkle Platte der Bildmarke hebt sich in Kopfleiste, Schublade und auf den Passwort-Seiten durch eine feine helle Kontur vom Hintergrund ab und verschwimmt nicht mit ihm. Auf dem gelben Anmelde- Panel darf diese Kontur NICHT zu sehen sein.
Zwei Aenderungen ausdruecklich zur Freigabe oder Zurueckweisung:
- Der Schriftzug "Tessera" ist in Kopfleiste und Schublade jetzt dunkel (beziehungsweise hell im dunklen Erscheinungsbild) statt wie bisher gelb. Passt das, oder soll das bisherige Gelb zurueck?
- Auf dem gelben Anmelde-Panel steht die Bildmarke jetzt oben statt unten. Passt diese Reihenfolge, oder soll die Marke wieder unter den Text? "passt" — wenn alle sieben Punkte in beiden Erscheinungsbildern stimmen und beide Gestaltungsfragen freigegeben sind.
Andernfalls die abweichende Stelle benennen (welcher Punkt, welches Erscheinungsbild, was ist zu sehen) beziehungsweise die gewuenschte Antwort auf die zwei Gestaltungsfragen geben. Beides wird noch in diesem Plan nachgezogen und nicht vertagt.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Lieferant -> Repository | Die Logo-Dateien stammen von aussen und werden ins Repository uebernommen |
| Browser -> ausgelieferte Icon-Route | Next.js liefert icon.svg unter einer oeffentlichen Route aus, ohne Anmeldung |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-FGV-01 | Tampering | tessera-logo.tsx | medium | mitigate | Eine fremde SVG-Datei als Zeichenkette in die Seite zu geben waere ein Einfallstor fuer eingebettete Skripte. Task 1 verbietet dangerouslySetInnerHTML und jedes Einlesen der Datei zur Laufzeit ausdruecklich: die Bildmarke wird als JSX von Hand gesetzt, React entwertet dabei jeden Inhalt. |
| T-FGV-02 | Information Disclosure | apps/web/src/app/icon.svg | low | accept | Das Icon wird ohne Anmeldung ausgeliefert. Es ist eine Bildmarke ohne jeden Bezug zu Mandanten oder Nutzern — genau das ist der Zweck eines Favicons. Bewusst getragen. |
| T-FGV-03 | Tampering | user-files/logo/ | low | mitigate | Die Lieferung liegt in einem vom Repository ausgeschlossenen Ordner und koennte unbemerkt ausgetauscht werden. Der Verify-Schritt von Task 1 vergleicht die kopierten Dateien byteweise gegen die Lieferung, sodass Kopie und Quelle nachweislich uebereinstimmen; ab dem Commit ist die Repository-Fassung die massgebliche. |
| T-FGV-SC | Tampering | npm/pip/cargo installs | n/a | n/a | Dieser Plan installiert kein Paket — keine neue Abhaengigkeit, kein Legitimitaets-Tor noetig. |
| </threat_model> |
<success_criteria>
- Browser-Tab, Kopfleiste, Seitenleisten-Schublade, Anmeldeseite in breiter und schmaler Ansicht sowie beide Passwort-Seiten zeigen das gelieferte Logo.
- Schriftzug und Bildmarke bleiben in hellem UND dunklem Erscheinungsbild lesbar; die dunkle Platte verschwimmt im dunklen Erscheinungsbild nicht mit dem Hintergrund.
- Es existiert genau eine Datei, die eine spaetere mandantenspezifische Gestaltung
anfassen muss:
apps/web/src/components/brand/mit der Farbkonstanten-Datei und der Logo-Komponente. - Von der Lieferung wurde nichts doppelt uebernommen; die nicht uebernommenen Dateien sind mit Grund benannt.
- Es wurde keinerlei Hochlade-, Einstellungs- oder Mandanten-Funktion fuer das Logo gebaut. </success_criteria>
Die SUMMARY muss festhalten:
- welche gelieferten Dateien uebernommen wurden und welche mit welcher Begruendung nicht,
- warum der Schriftzug als Text und nicht als Bilddatei ausgeliefert wird (Farbe fest eingebacken UND Schriftart Archivo nicht geladen),
- wie die Kontur der Platte im dunklen Erscheinungsbild geloest ist und warum sie auf dem gelben Anmelde-Panel abgeschaltet ist,
- dass der Gelbwert jetzt an genau einer Stelle steht und
--primarybewusst unberuehrt blieb, - wie der Benutzer die beiden zur Freigabe gestellten Gestaltungsfragen entschieden hat.
Ausserdem als Backlog-Punkt zu vermerken (vom Orchestrator einzureichen, in diesem Plan
NICHT zu bauen): "Administrator kann das Erscheinungsbild je Mandant anpassen — eigenes
Logo hochladen, Markenfarben setzen. Ansatzpunkt ist
apps/web/src/components/brand/; die Oberflaeche zieht das Logo bereits ausschliesslich
von dort."