---
quick_id: 260907-fgv
slug: tessera-logo-und-favicon-fest-einbauen-b
date: 2026-09-07
status: planned
relates_to: 01-foundation-portal-shell
phase: quick-260907-fgv
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- apps/web/src/app/icon.svg
- apps/web/src/app/apple-icon.png
- apps/web/src/components/brand/brand.ts
- apps/web/src/components/brand/tessera-logo.tsx
- apps/web/src/components/brand/tessera-logo.test.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- apps/web/src/components/layout/header.tsx
- apps/web/src/components/layout/sidebar.tsx
- apps/web/src/components/layout/sidebar.test.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
- apps/web/src/components/settings/account-settings-form.tsx
autonomous: false
requirements: [QUICK-260907-FGV]
estimate:
tokens: 55000
raw_tokens: 55000
tasks: 3
confidence: low
must_haves:
truths:
- "Der Browser-Tab zeigt die Bildmarke statt des Standard-Globus — in hellem und in dunklem Erscheinungsbild."
- "Kopfleiste, Seitenleisten-Schublade, Anmeldeseite (breite und schmale Ansicht) und beide Passwort-Seiten zeigen dieselbe Bildmarke aus derselben Komponente."
- "Der Schriftzug bleibt in beiden Erscheinungsbildern lesbar, weil er echter HTML-Text in der Vordergrundfarbe der Oberflaeche ist und kein eingebetteter Text mit fest eingebackener Farbe."
- "Die dunkle Platte der Bildmarke hebt sich im dunklen Erscheinungsbild vom Hintergrund ab, weil sie dort eine helle Haarlinie als Kontur bekommt; im hellen Erscheinungsbild ist die Kontur unsichtbar."
- "Auf dem gelben Anmelde-Panel bleibt die Bildmarke ohne Kontur, weil dieses Panel unabhaengig vom Erscheinungsbild immer eine helle Flaeche ist."
- "Der Gelbwert der Marke ist im gesamten Web-Quellcode genau einmal notiert; Anmelde-Panel und Bildmarke lesen ihn aus dieser einen Konstante."
- "Das gelieferte Doppel favicon.svg / tessera-logo.svg wird nur einmal in das Repository uebernommen — der Byte-Vergleich hat beide als identisch nachgewiesen."
- "Es entsteht keine Hochlade-Maske, kein Einstellungsfeld, keine Datenbankspalte und keine Mandantenabfrage fuer das Logo."
artifacts:
- apps/web/src/app/icon.svg
- apps/web/src/app/apple-icon.png
- apps/web/src/components/brand/brand.ts
- apps/web/src/components/brand/tessera-logo.tsx
- apps/web/src/components/brand/tessera-logo.test.tsx
key_links:
- "BRAND_YELLOW in brand.ts -> Signalkachel der Bildmarke UND Hintergrund des Anmelde-Panels UND Vorgabewert der persoenlichen Akzentfarbe: eine Konstante, drei Nutzer"
- "TesseraLogo -> header.tsx, sidebar.tsx, login/page.tsx (zweimal), reset-password/page.tsx, reset-password/[token]/page.tsx: die eine Stelle, an der eine spaetere Mandanten-Marke ansetzt"
- "common.brand.logoAlt in de.json UND en.json -> aria-label der Bildmarke; fehlt der Schluessel in einer Sprache, liest ein Screenreader den Schluesselnamen vor"
- "apps/web/src/app/icon.svg -> dateibasierte Metadaten von Next.js: die Datei MUSS unter src/app/ liegen, in public/ waere sie wirkungslos, weil layout.tsx kein icons-Feld setzt"
---
Das gelieferte Tessera-Logo und das Favicon fest in die Oberflaeche einbauen — und
zwar so, dass die spaetere Wunschfunktion "Administrator gestaltet das Erscheinungsbild
je Mandant" genau eine Datei anfassen muss.
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 ``, 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 ``-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.
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
@CLAUDE.md
@.planning/STATE.md
@apps/web/src/app/layout.tsx
@apps/web/src/app/globals.css
@apps/web/src/components/layout/header.tsx
@apps/web/src/components/layout/sidebar.tsx
@apps/web/src/components/layout/sidebar.test.tsx
@apps/web/src/app/(auth)/login/page.tsx
@apps/web/src/app/(auth)/reset-password/page.tsx
@apps/web/src/components/settings/account-settings-form.tsx
@apps/web/src/messages/de.json
Task 1: Markenbausteine — Icon-Dateien, Farbkonstanten, TesseraLogo-Komponente, i18n-Schluesselapps/web/src/app/icon.svg, apps/web/src/app/apple-icon.png, apps/web/src/components/brand/brand.ts, apps/web/src/components/brand/tessera-logo.tsx, apps/web/src/components/brand/tessera-logo.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json
- Variante `mark`: es entsteht ein Element mit der Rolle `img`, dessen zugaenglicher Name aus dem Uebersetzungsschluessel `common.brand.logoAlt` stammt. Ein sichtbarer Schriftzug ist NICHT vorhanden.
- Variante `horizontal`: Rolle `img` vorhanden UND der Schriftzug aus `common.appName` ist als Text sichtbar.
- Variante `stacked`: Rolle `img` vorhanden UND der Schriftzug ist sichtbar.
- Die Platte traegt bei Vorgabewerten eine Klasse, die die Kontur nur im dunklen Erscheinungsbild einschaltet; mit `plateOutline={false}` fehlt diese Klasse.
- Die Grundflaeche der Bildmarke enthaelt fuenf Kacheln, davon genau eine mit dem Gelbwert der Marke und genau eine mit einer Drehung.
- Sowohl `de.json` als auch `en.json` definieren `common.brand.logoAlt` — der Test liest beide Dateien und vergleicht die Schluesselmenge unterhalb von `common.brand`, damit keine Sprache spaeter allein ergaenzt werden kann.
Kopiere `user-files/logo/favicon.svg` unveraendert nach `apps/web/src/app/icon.svg` und
`user-files/logo/apple-touch-icon.png` unveraendert nach
`apps/web/src/app/apple-icon.png`. Nur diese beiden Dateien — die uebrigen gelieferten
Dateien bleiben ausserhalb des Repositories, `tessera-logo.svg` ist nachweislich
byteweise identisch zu `favicon.svg` und wuerde eine zweite Kopie desselben Inhalts
erzeugen. Beide Zieldateien MUESSEN unter `src/app/` liegen, damit Next.js sie als
dateibasierte Metadaten erkennt; `layout.tsx` wird nicht angefasst.
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-checkDie 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.Task 2: Alle sechs Marken-Stellen auf die Komponente umstellen und den Gelbwert zusammenfuehrenapps/web/src/components/layout/header.tsx, apps/web/src/components/layout/sidebar.tsx, apps/web/src/components/layout/sidebar.test.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, apps/web/src/components/settings/account-settings-form.tsx
- `sidebar.test.tsx` bleibt gruen: die Uebersetzungs-Attrappe kennt zusaetzlich den Schluessel `brand.logoAlt` im Bereich `common`, sodass die Bildmarke in der mobilen Schublade einen echten zugaenglichen Namen traegt statt des Schluesselnamens.
- Die vorhandenen Aussagen von `sidebar.test.tsx` werden nicht abgeschwaecht und keine geloescht.
Ersetze in `header.tsx` das Schriftzug-Element neben der Menue-Schaltflaeche durch
``. Der umgebende Flex-Container mit
`gap-3` bleibt unveraendert.
Ersetze in `sidebar.tsx` das Schriftzug-Element in der Kopfzeile der mobilen Schublade
durch ``. 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 `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/loginDer 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.Task 3: Abnahme im Browser — beide Erscheinungsbilder, sieben Stellen
Das gelieferte Tessera-Logo ist an sechs Stellen der Oberflaeche fest eingebaut und die
Icon-Dateien fuer den Browser-Tab liegen im Repository. Alle Stellen ziehen aus einer
einzigen Komponente `apps/web/src/components/brand/tessera-logo.tsx`; die Markenfarben
stehen ausschliesslich in `apps/web/src/components/brand/brand.ts`. Der handgezeichnete
Vier-Quadrate-Platzhalter auf der Anmeldeseite ist entfernt. Der Schriftzug wird als
echter Text in der Hausschrift gesetzt, nicht als Bilddatei, damit er in beiden
Erscheinungsbildern lesbar bleibt. Task 2 hat das lokale Web-Abbild bereits neu gebaut
und den Container ersetzt — unter `http://localhost:3000` laeuft der neue Stand. Der
Testserver alpha.tessera.ctl.de wurde nicht angefasst.
`http://localhost:3000` oeffnen. Anmeldung mit `admin` / `admin123`; fuer die
Nutzersicht stehen `nutzer1` / `Test1234!` und `nutzer2` / `Test1234!` bereit. Das
Erscheinungsbild wird ueber den Umschalter rechts in der Kopfleiste gewechselt.
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:
1. **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.
2. **Kopfleiste** — Bildmarke links neben dem Schriftzug "Tessera", in einer Zeile,
senkrecht mittig zur Zeilenhoehe, nicht abgeschnitten.
3. **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.
4. **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.
5. **Anmeldeseite, schmale Ansicht** — Fenster schmal ziehen: das gelbe Panel entfaellt
wie bisher, ueber dem Anmeldeformular steht die Marke einzeilig und mittig.
6. **Passwort-vergessen-Seite** — `http://localhost:3000/reset-password`: Marke oben,
darunter unveraendert die Seitenueberschrift und das E-Mail-Feld.
7. **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.
## 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. |
- `pnpm --filter @tessera/web exec vitest run` ist vollstaendig gruen (der bestehende
Bestand plus der neue Komponententest).
- `pnpm --filter @tessera/web type-check` ist sauber.
- Der Gelbwert der Marke kommt im Web-Quellcode genau einmal vor, in `brand.ts`.
- `apps/web/src/app/icon.svg` und `apps/web/src/app/apple-icon.png` sind byteidentisch zu
den gelieferten Dateien.
- In `apps/web/src/app/(auth)/` steht kein von Hand gesetzter Marken-Schriftzug mehr.
- Die Abnahme aus Task 3 ist in beiden Erscheinungsbildern durchlaufen.
- 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.