From f6eda20fcb2b33b51dacd7b6399db547b9b175e6 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 17 Sep 2026 12:12:18 +0200 Subject: [PATCH] =?UTF-8?q?docs(quick-260917-gsh):=20Plan=20=E2=80=94=20Ak?= =?UTF-8?q?zentfarbe=20als=20Hex-Code=20eingebbar,=20Bildmarke=20uebernimm?= =?UTF-8?q?t=20Akzentfarbe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../260917-gsh-PLAN.md | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 .planning/quick/260917-gsh-akzentfarbe-in-einstellungen-konto-zusae/260917-gsh-PLAN.md diff --git a/.planning/quick/260917-gsh-akzentfarbe-in-einstellungen-konto-zusae/260917-gsh-PLAN.md b/.planning/quick/260917-gsh-akzentfarbe-in-einstellungen-konto-zusae/260917-gsh-PLAN.md new file mode 100644 index 0000000..b1a157f --- /dev/null +++ b/.planning/quick/260917-gsh-akzentfarbe-in-einstellungen-konto-zusae/260917-gsh-PLAN.md @@ -0,0 +1,220 @@ +--- +phase: quick-260917-gsh +plan: 01 +type: execute +wave: 1 +depends_on: [] +autonomous: true +requirements: [QUICK-260917-GSH] + +files_modified: + - apps/web/src/lib/color.ts + - apps/web/src/lib/color.test.ts + - apps/web/src/components/settings/account-settings-form.tsx + - apps/web/src/components/settings/account-settings-form.test.tsx + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json + - apps/web/src/components/brand/tessera-logo.tsx + - apps/web/src/components/brand/tessera-logo.test.tsx + - apps/web/src/components/brand/brand.ts + - CHANGELOG.md + +estimate: + tokens: 32000 + raw_tokens: 32000 + tasks: 3 + confidence: low + +must_haves: + truths: + - "Einstellungen → Konto zeigt neben dem Farbwaehler ein Textfeld (``, monospace, `maxLength={7}`, `aria-label` = settings.account.accentColorHex), vorbelegt mit dem gespeicherten Wert des Nutzers; der bisherige reine Anzeige-`` (Z. 222 im Bestand) existiert nicht mehr." + - "Tippt der Nutzer in das Textfeld `FFED00`, `#FFED00` oder `#fe0`, wird der Wert per `normalizeHexColor()` (apps/web/src/lib/color.ts) auf `#ffed00` bzw. `#ffee00` normalisiert; `accentColor` und damit der Farbwaehler folgen sofort. Beim Verlassen des Feldes (onBlur) steht die kanonische Form im Textfeld." + - "Aendert der Nutzer den Farbwaehler, uebernimmt das Textfeld denselben Wert (`#rrggbb`, Kleinbuchstaben). Zuruecksetzen setzt Farbwaehler UND Textfeld auf `#ffed00`." + - "Ist der Text kein gueltiger Farbwert (`#ggg`, `#12345`, leer), traegt das Textfeld `aria-invalid=\"true\"` und die Klasse `border-destructive`, unter der Zeile steht settings.account.accentColorHexInvalid, und der Knopf „Farbe speichern“ ist `disabled`. Bei gueltigem Wert: `aria-invalid=\"false\"`, `border-input`, Knopf aktiv." + - "`normalizeHexColor(input: string): string | null` ist eine reine Funktion mit vitest-Test (apps/web/src/lib/color.test.ts): gueltig `#ffed00`→`#ffed00`, `FFED00`→`#ffed00`, `#fe0`→`#ffee00`, ` #FfEd00 `→`#ffed00`; ungueltig (`null`): `#ggg`, `#12345`, ``, `#`, `#1234567`." + - "de.json und en.json enthalten unter settings.account die neuen Schluessel `accentColorHex` und `accentColorHexInvalid` (deutsch in Sie-Form, englische Entsprechung); beide Dateien bleiben gueltiges JSON." + - "In `LogoMark` (tessera-logo.tsx) traegt die gedrehte Kachel (`transform=\"rotate(12 51 21)\"`) kein `fill`-Praesentationsattribut mehr, sondern den Inline-Style `fill: var(--primary, #ffed00)` (Vorlage-String mit BRAND_YELLOW als Rueckfall). Die vier Olivkacheln und die Grundplatte sind unveraendert. Damit nimmt die Bildmarke in Kopfzeile, Seitenleiste und leerem Dashboard die per auth-store.ts gesetzte Akzentfarbe an; auf der Anmeldeseite (kein Nutzer, `--primary` = CSS-Standard Markengelb) bleibt sie gelb." + - "tessera-logo.test.tsx prueft: fuenf Kacheln, genau eine mit Inline-Style-Fuellung `var(--primary, #ffed00)` (aus BRAND_YELLOW gebildet), diese ist die einzige gedrehte, keine Kachel traegt ein `fill`-Attribut gleich BRAND_YELLOW, vier Kacheln tragen `fill` = BRAND_OLIVE. jsdom 29.1.1 behaelt `var()`-Werte in `element.style.fill` (vom Planer geprueft)." + - "brand.ts dokumentiert BRAND_YELLOW als Standard-Gelb der Signalkachel und Rueckfall ohne Akzentfarbe; der Kommentar an der Kachel in tessera-logo.tsx erklaert, warum Inline-Style statt Praesentationsattribut." + - "`pnpm --filter @tessera/web exec vitest run` und `pnpm --filter @tessera/web type-check` enden gruen. Kein Docker-Build, kein `git push`, keine Dateien ausserhalb der autorisierten Liste (files_modified + .planning/)." + - "CHANGELOG.md, `## Unveröffentlicht`: je genau ein neuer Stichpunkt unter `### Neu` (Hex-Eingabe) und `### Geändert` (Bildmarke in Akzentfarbe), Stil wie im Bestand (typografische Anfuehrungszeichen, `→`, kein Punkt am Ende, kein Fliesstext)." + - "Drei Commits: `feat(settings): …` (Task 1), `feat(brand): …` (Task 2), `docs: …` (Task 3)." + artifacts: + - "apps/web/src/lib/color.ts — `normalizeHexColor` (neu)" + - "apps/web/src/lib/color.test.ts — Unit-Test der Normalisierung (neu)" + - "apps/web/src/components/settings/account-settings-form.tsx — Hex-Textfeld, Zustand `hexInput`, Sync mit Farbwaehler, Speichern-Sperre" + - "apps/web/src/components/settings/account-settings-form.test.tsx — Komponententest Sync/Sperre/Reset (neu)" + - "apps/web/src/messages/de.json, en.json — settings.account.accentColorHex, accentColorHexInvalid" + - "apps/web/src/components/brand/tessera-logo.tsx — gedrehte Kachel mit Inline-Style `var(--primary, …)`" + - "apps/web/src/components/brand/tessera-logo.test.tsx — angepasster Kacheltest" + - "apps/web/src/components/brand/brand.ts — Kommentar zu BRAND_YELLOW" + - "CHANGELOG.md — zwei Stichpunkte" + key_links: + - "auth-store.ts `applyAccentColor` (Z. 21-41) setzt `--primary` inline auf `document.documentElement`; `LogoMark` liest denselben Token per `var(--primary, …)`. Das ist die einzige Verdrahtung zwischen Akzentfarbe und Bildmarke — kein Prop, kein Store-Zugriff im Logo." + - "`` akzeptiert nur `#rrggbb`; deshalb wird NUR der normalisierte Wert in `accentColor` geschrieben, der Rohtext lebt getrennt in `hexInput`. Ein ungueltiger Rohtext darf nie in `accentColor` landen, sonst faellt jsdom/Browser auf `#000000` zurueck." + - "`updateAccentColorAction` (auth-actions.ts Z. 220) leitet an `PATCH /users/me/accent-color` weiter, Server-Regex `/^#[0-9a-fA-F]{6}$/`; die Client-Normalisierung liefert immer diese Form, Speichern ist bei ungueltigem Text zusaetzlich gesperrt." + - "tessera-logo.test.tsx Z. 97-109 filtert Kacheln ueber `getAttribute('fill') === BRAND_YELLOW` — nach Task 2 sind das null Treffer, der Test MUSS umgeschrieben werden (Inline-Style pruefen), sonst rot." +--- + + +Zwei Ergaenzungen an der persoenlichen Akzentfarbe (Einstellungen → Konto): + +1. **Hex-Eingabe.** Neben dem Farbwaehler ein Textfeld fuer den Hex-Code (`#rrggbb`), vorbelegt mit dem aktuellen Wert. Eingabe wird ueber eine reine Funktion `normalizeHexColor()` normalisiert (fuehrendes `#` optional, 3-stellige Kurzform wird expandiert, Ausgabe klein). Farbwaehler und Textfeld bleiben in beide Richtungen synchron; ungueltiger Text zeigt einen Fehlerzustand und sperrt „Farbe speichern“. Der reine Anzeige-`` entfaellt. +2. **Bildmarke in Akzentfarbe.** Die gedrehte Signalkachel in `LogoMark` bekommt statt der festen gelben Fuellung den Inline-Style `fill: var(--primary, #ffed00)` — dadurch folgt die Bildmarke ueberall (Kopfzeile, Seitenleiste, leeres Dashboard) der per `applyAccentColor` gesetzten Akzentfarbe; vor der Anmeldung gilt der CSS-Standard (Markengelb). + +Dazu Unit-Tests (Normalisierung, Formular-Sync, Logo) und zwei CHANGELOG-Stichpunkte. Browser-Nachweis macht der Orchestrator selbst — kein Docker-Build, kein Push. + +Purpose: Nutzer koennen eine exakte Firmenfarbe eintippen statt sie im Farbwaehler zu treffen; die Bildmarke wirkt dann nicht mehr wie ein Fremdkoerper in der gewaehlten Farbe. +Output: `color.ts` + Test, angepasstes Kontoformular + Test, i18n-Schluessel de/en, angepasstes Logo + Test + Kommentar, CHANGELOG, drei Commits. + + + +@~/.claude/gsd-core/workflows/execute-plan.md +@~/.claude/gsd-core/templates/summary.md + + + +@/home/vicolab/projects/tessera-ctl/CLAUDE.md +@/home/vicolab/projects/tessera-ctl/apps/web/src/components/settings/account-settings-form.tsx +@/home/vicolab/projects/tessera-ctl/apps/web/src/components/brand/tessera-logo.tsx +@/home/vicolab/projects/tessera-ctl/apps/web/src/components/brand/tessera-logo.test.tsx +@/home/vicolab/projects/tessera-ctl/apps/web/src/components/brand/brand.ts +@/home/vicolab/projects/tessera-ctl/apps/web/src/lib/stores/auth-store.ts +@/home/vicolab/projects/tessera-ctl/apps/web/src/components/settings/desktop-app-settings.test.tsx + + + + + + Task 1: Hex-Eingabe — Normalisierung, Formular-Sync, i18n, Tests + apps/web/src/lib/color.ts, apps/web/src/lib/color.test.ts, apps/web/src/components/settings/account-settings-form.tsx, apps/web/src/components/settings/account-settings-form.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json + + - apps/web/src/components/settings/account-settings-form.tsx (Z. 39-54 Zustand, Z. 115-140 Speichern/Zuruecksetzen, Z. 209-250 Markup) + - apps/web/src/components/settings/desktop-app-settings.test.tsx Z. 1-40 (Muster: de.json-gestuetzter next-intl-Mock, `vi.hoisted` + `vi.mock` fuer ein lib-Modul) + - apps/web/src/lib/app-version.test.ts Z. 1-10 (Kopfkommentar-Stil fuer lib-Tests) + - apps/web/src/messages/de.json Z. 146-151 und en.json Z. 146-151 (settings.account.accentColor*) + + + color.test.ts (`describe('normalizeHexColor')`): + - `'#ffed00'` → `'#ffed00'` + - `'FFED00'` → `'#ffed00'` (ohne `#`, Grossbuchstaben) + - `'#fe0'` → `'#ffee00'` (Kurzform expandiert) + - `' #FfEd00 '` → `'#ffed00'` (Leerraum getrimmt) + - `'#ggg'` → `null`; `'#12345'` → `null`; `''` → `null`; `'#'` → `null`; `'#1234567'` → `null` + account-settings-form.test.tsx (`describe('AccountSettingsForm — Akzentfarbe Hex-Eingabe')`): + - Nach dem Laden (fetchCurrentUser liefert accentColor `#123456`): Textfeld `getByRole('textbox', { name: 'Hex-Code' })` hat Wert `#123456`, `input[type="color"]` hat Wert `#123456`. + - `fireEvent.change(textfeld, 'FFED00')` → Farbwaehler-Wert `#ffed00`, Knopf „Farbe speichern“ nicht disabled, Textfeld `aria-invalid="false"`. + - `fireEvent.change(textfeld, '#ggg')` → Textfeld `aria-invalid="true"`, Klasse enthaelt `border-destructive`, Fehlertext (de.json settings.account.accentColorHexInvalid) sichtbar, Knopf „Farbe speichern“ disabled, Farbwaehler behaelt letzten gueltigen Wert. + - `fireEvent.change(farbwaehler, '#00ff00')` → Textfeld-Wert `#00ff00`. + - `fireEvent.blur(textfeld)` nach Eingabe `#fe0` → Textfeld-Wert `#ffee00`. + - Klick „Zurücksetzen“ → Textfeld und Farbwaehler `#ffed00`, `updateAccentColorAction` mit `null` aufgerufen. + - Klick „Farbe speichern“ bei gueltigem `#ffed00` → `updateAccentColorAction` mit `'#ffed00'` aufgerufen, Erfolgsmeldung (settings.account.accentColorSuccess) erscheint (`findByText`). + + + RED zuerst: beide Testdateien anlegen und rot sehen (`color.test.ts` scheitert mit fehlendem Modul, Formulartest mit fehlendem Textfeld), dann GREEN. + + **1. `apps/web/src/lib/color.ts` (neu).** Exportiere `normalizeHexColor(input: string): string | null`: trimmen, ein optionales fuehrendes `#` abschneiden, dann pruefen — genau 3 oder genau 6 Hex-Zeichen (`/^[0-9a-f]{3}$|^[0-9a-f]{6}$/i`); bei 3 Zeichen jedes Zeichen verdoppeln; Ergebnis kleingeschrieben mit `#` davor zurueckgeben; alles andere `null`. Keine Abhaengigkeiten, kein `'use client'`. Deutscher Kopfkommentar (Zweck: Hex-Eingabe der Akzentfarbe, quick-260917-gsh; Server-Regex in `PATCH /users/me/accent-color` verlangt `#rrggbb`). + + **2. `apps/web/src/lib/color.test.ts` (neu).** Faelle aus ``; Kopfkommentar im Stil von app-version.test.ts. + + **3. i18n.** In `apps/web/src/messages/de.json` und `en.json` unter `settings.account` direkt nach `accentColorError` (Z. 151) zwei Schluessel einfuegen: `accentColorHex` = „Hex-Code“ / „Hex code“; `accentColorHexInvalid` = „Ungültiger Farbwert. Bitte geben Sie sechs Hexadezimalzeichen ein, z. B. #ffed00.“ / „Invalid color value. Please enter six hexadecimal characters, e.g. #ffed00.“ Kommasetzung im JSON beachten. + + **4. `account-settings-form.tsx`.** + - Import `normalizeHexColor` aus `@/lib/color`. + - Neuer Zustand `const [hexInput, setHexInput] = useState(DEFAULT_ACCENT);` neben `accentColor`; abgeleitet `const isHexValid = normalizeHexColor(hexInput) !== null;`. `accentColor` bleibt die einzige Quelle fuer Farbwaehler und Speichern und enthaelt IMMER einen gueltigen `#rrggbb`-Wert (ein ungueltiger Rohtext darf dort nie landen — `` faellt sonst auf `#000000`). + - `useEffect` (Z. 45-54): nach `setAccentColor(c)` zusaetzlich `setHexInput(c)` mit demselben Wert. + - Farbwaehler `onChange`: `setAccentColor(v)` UND `setHexInput(v)`. + - Textfeld `onChange`: `setHexInput(raw)`; `const n = normalizeHexColor(raw); if (n) setAccentColor(n);`. Textfeld `onBlur`: falls gueltig, `setHexInput(normalisiert)` (kanonische Form — Ermessensentscheidung des Planers, damit `FFED00` nicht dauerhaft in Grossbuchstaben stehen bleibt). + - `handleResetAccentColor`: zusaetzlich `setHexInput(DEFAULT_ACCENT)`. + - `handleSaveAccentColor`: am Anfang `if (!isHexValid) return;` als Sicherheitsnetz; sendet weiterhin `accentColor`. + - Markup (Z. 215-231): den reinen Anzeige-`` mit `font-mono`, der nur den Wert als Text wiederholt (Z. 222), entfernen und an seiner Stelle das Textfeld setzen: ``; Klassen `h-10 w-28 rounded-md border bg-background px-3 py-2 font-mono text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring` plus bedingt `border-destructive` (ungueltig) bzw. `border-input` (gueltig). Direkt unter der Zeile (vor den Erfolgs-/Fehlermeldungen) bei `!isHexValid` ein `

{t('account.accentColorHexInvalid')}

`. + - Knopf „Farbe speichern“: `disabled={isAccentPending || !isHexValid}`. + + **5. `account-settings-form.test.tsx` (neu).** next-intl-Mock de.json-gestuetzt wie desktop-app-settings.test.tsx (die Namespaces `settings` und `auth` muessen beide aufloesen — der Mock ist namespace-generisch). `@/lib/auth-actions` per `vi.hoisted` + `vi.mock` komplett ersetzen: `fetchCurrentUser` → `mockResolvedValue({ id: 'u1', username: 'max', displayName: 'Max', role: 'USER', tenantId: 't1', isLocalUser: true, hasAvatar: false, accentColor: '#123456' })`; `updateAccentColorAction` → `mockResolvedValue({ success: true })`; `changePasswordAction`, `uploadAvatarAction`, `deleteAvatarAction` als `vi.fn()`. `useAuthStore` NICHT mocken (echter zustand-Store, `user` ist null, `setUser` wird nicht erreicht). Farbwaehler ueber `container.querySelector('input[type="color"]')` greifen (kein ARIA-Rollenname). Nach `render` mit `await screen.findByDisplayValue('#123456')` auf das Laden warten; nach Klicks auf Speichern/Zuruecksetzen mit `waitFor`/`findByText` warten (useTransition ist asynchron). `afterEach`: `cleanup()` + `vi.clearAllMocks()`. + + Nicht anfassen: Passwort- und Avatar-Abschnitte, auth-actions.ts, auth-store.ts, API. +
+ + cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/lib/color.test.ts src/components/settings/account-settings-form.test.tsx && pnpm --filter @tessera/web type-check && node -e "for (const l of ['de','en']) { const m = JSON.parse(require('fs').readFileSync('apps/web/src/messages/'+l+'.json','utf8')); for (const k of ['accentColorHex','accentColorHexInvalid']) { const v = m.settings.account[k]; if (typeof v !== 'string' || !v) { console.error('fehlt: '+l+' settings.account.'+k); process.exit(1); } } } console.log('i18n ok')" && ! grep -q '">{accentColor}
' apps/web/src/components/settings/account-settings-form.tsx && grep -c 'normalizeHexColor' apps/web/src/components/settings/account-settings-form.tsx | grep -qv '^0$' && echo TASK1-OK + + Beide neuen Testdateien gruen (Normalisierung 9 Faelle, Formular 7 Faelle), Typpruefung gruen, beide Sprachdateien enthalten die zwei neuen Schluessel als nichtleere Strings, der Anzeige-`` ist aus dem Formular verschwunden, das Formular importiert und nutzt `normalizeHexColor`. Commit `feat(settings): Akzentfarbe zusätzlich als Hex-Code eingebbar` (nur die sechs Dateien dieses Tasks). + + + + Task 2: Bildmarke — gedrehte Kachel in Akzentfarbe, Test und Kommentare + apps/web/src/components/brand/tessera-logo.tsx, apps/web/src/components/brand/tessera-logo.test.tsx, apps/web/src/components/brand/brand.ts + + tessera-logo.test.tsx — der Test „renders exactly five tiles, exactly one in the brand yellow and exactly one rotated“ (Z. 97-109) wird ersetzt durch „renders exactly five tiles; only the rotated one is filled from the accent token with the brand yellow as fallback“: + - `mark.querySelectorAll('g rect')` hat Laenge 5. + - Genau eine Kachel hat `(tile as SVGRectElement).style.fill === \`var(--primary, ${BRAND_YELLOW})\`` (Erwartung aus der Konstante gebildet); diese Kachel hat das Attribut `transform`; keine andere Kachel hat `transform`. + - Keine Kachel hat `getAttribute('fill') === BRAND_YELLOW`. + - Genau vier Kacheln haben `getAttribute('fill') === BRAND_OLIVE` (Import aus `./brand` ergaenzen) und keinen `style`-Attributwert. + Alle uebrigen Tests der Datei bleiben unveraendert und gruen. + + + RED zuerst: Test wie in `` umschreiben, rot sehen (Inline-Style fehlt), dann GREEN. + + **`tessera-logo.tsx`, `LogoMark`, gedrehte Kachel (Z. 72-80):** Das `fill`-Praesentationsattribut dieser einen `` (aktuell an `BRAND_YELLOW` gebunden) entfernen und stattdessen `style={{ fill: \`var(--primary, ${BRAND_YELLOW})\` }}` setzen. Grund als deutscher Kommentar direkt ueber der ``: `var()` ist in SVG-Praesentationsattributen nicht zuverlaessig, im Inline-Style schon; `--primary` wird von `applyAccentColor` in `auth-store.ts` gesetzt, ohne Nutzer (Anmeldeseite) gilt der CSS-Standard aus `globals.css` (Markengelb), der Rueckfall in `var()` greift nur, wenn der Token gar nicht definiert ist. Der Import von `BRAND_YELLOW` bleibt (fuer den Rueckfall). Die vier Olivkacheln, die Grundplatte, `plateOutline`, Props und die `horizontal`-Variante nicht anfassen. Im JSDoc von `TesseraLogo` (Z. 88-93) einen Satz ergaenzen: die gedrehte Signalkachel folgt der persoenlichen Akzentfarbe (`--primary`). + + **`brand.ts`:** Kommentar ueber `BRAND_YELLOW` (Z. 11) aendern zu: Standard-Gelb der gedrehten Signalkachel und Rueckfall, wenn keine Akzentfarbe (`--primary`) gesetzt ist; die Kachel selbst wird in `tessera-logo.tsx` per `var(--primary, BRAND_YELLOW)` gefuellt. Werte der drei Konstanten unveraendert (login/page.tsx und account-settings-form.tsx importieren sie weiterhin). + + **`tessera-logo.test.tsx`:** Test Z. 97-109 gemaess `` ersetzen, `BRAND_OLIVE` importieren. Hinweis: jsdom 29.1.1 (aktuelle Aufloesung unter apps/web) haelt `var(--primary, #ffed00)` in `element.style.fill` und im `style`-Attribut — vom Planer per JSDOM-Probe bestaetigt; `getAttribute('style')` waere die Alternative, `style.fill` ist die klarere Zusicherung. + + + cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/brand/tessera-logo.test.tsx && ! grep -q 'fill={BRAND_YELLOW}' apps/web/src/components/brand/tessera-logo.tsx && test "$(grep -cF 'var(--primary, ${BRAND_YELLOW})' apps/web/src/components/brand/tessera-logo.tsx)" = "1" && test "$(grep -c 'fill={BRAND_OLIVE}' apps/web/src/components/brand/tessera-logo.tsx)" = "4" && grep -q 'primary' apps/web/src/components/brand/brand.ts && pnpm --filter @tessera/web type-check && echo TASK2-OK + + tessera-logo.test.tsx gruen (alle Tests inkl. des umgeschriebenen Kacheltests), in tessera-logo.tsx gibt es kein gelbes `fill`-Praesentationsattribut mehr, genau eine Vorlage-Fuellung `var(--primary, …)` und weiterhin vier Olivkacheln, brand.ts erwaehnt den `--primary`-Rueckfall, Typpruefung gruen. Commit `feat(brand): gedrehte Kachel der Bildmarke übernimmt die Akzentfarbe` (nur die drei Dateien dieses Tasks). + + + + Task 3: CHANGELOG — zwei Stichpunkte, Gesamtlauf + CHANGELOG.md + + In `CHANGELOG.md` unter `## Unveröffentlicht`: + - `### Neu`, direkt nach dem Stichpunkt `- Favoriten-Widget: optionaler Titel (ohne Titel keine Kopfzeile)` (Z. 12): `- Einstellungen → Konto: Akzentfarbe zusätzlich als Hex-Code eingebbar (z. B. #ffed00)` + - `### Geändert`, direkt nach dem Stichpunkt `- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; …` (Z. 17): `- Tessera-Bildmarke: die gedrehte Kachel übernimmt die persönliche Akzentfarbe` + Stil wie im Bestand: echte Umlaute, `→`, kein Punkt am Ende, kein Fliesstext, keine weiteren Aenderungen an der Datei. Danach den vollstaendigen Web-Testlauf und die Typpruefung als Abschlussgate ausfuehren. Das `` VOR dem Commit laufen lassen — der Diff-Zaehler misst den Arbeitsbaum gegen HEAD (Tasks 1 und 2 sind zu diesem Zeitpunkt bereits committet, CHANGELOG.md noch nicht). + + + cd /home/vicolab/projects/tessera-ctl && test "$(sed -n '/^## Unveröffentlicht/,/^## 1\.1\.0/p' CHANGELOG.md | grep -c 'Akzentfarbe zusätzlich als Hex-Code')" = "1" && test "$(sed -n '/^## Unveröffentlicht/,/^## 1\.1\.0/p' CHANGELOG.md | grep -c 'gedrehte Kachel übernimmt die persönliche Akzentfarbe')" = "1" && NUMSTAT=$(git diff --numstat HEAD -- CHANGELOG.md) && test "$(printf '%s' "$NUMSTAT" | awk '{print $1"/"$2}')" = "2/0" && pnpm --filter @tessera/web exec vitest run && pnpm --filter @tessera/web type-check && echo TASK3-OK + + Beide Stichpunkte stehen je genau einmal im Abschnitt „Unveröffentlicht“ (Neu bzw. Geändert), CHANGELOG-Diff = genau zwei eingefuegte Zeilen, kompletter Web-Testlauf (bisher 55 Dateien / 365 Tests plus die drei neuen bzw. geaenderten Dateien) und Typpruefung gruen. Commit `docs: CHANGELOG — Hex-Eingabe der Akzentfarbe, Bildmarke in Akzentfarbe` (nur CHANGELOG.md). + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Browser → API (`PATCH /users/me/accent-color`) | Vom Nutzer getippter Hex-Text verlaesst den Client; Server prueft bereits `/^#[0-9a-fA-F]{6}$/` | +| Nutzertext → `` / `--primary` | Freitext darf nicht ungeprueft in den Farbwaehler-Wert oder in `style.setProperty('--primary', …)` gelangen | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-gsh-01 | Tampering | `normalizeHexColor` / Speichern-Pfad | low | mitigate | Nur der normalisierte `#rrggbb`-Wert erreicht `accentColor` und die Server-Action; Speichern bei ungueltigem Text gesperrt; Server-Regex bleibt die letzte Instanz (unveraendert) | +| T-gsh-02 | Tampering | `applyAccentColor` (`--primary` inline) | low | accept | Wert stammt aus der API-Antwort desselben Nutzers, ist serverseitig auf `#rrggbb` beschraenkt; kein neuer Pfad in diesem Task | +| T-gsh-03 | Information Disclosure | Fehlertext `accentColorHexInvalid` | low | accept | Statischer i18n-Text, gibt keine Eingabe wieder | +| T-gsh-SC | Tampering | npm/pnpm installs | low | accept | Keine Paketinstallationen in diesem Plan (nur bestehende Abhaengigkeiten: vitest, testing-library, jsdom) | + + + +- `pnpm --filter @tessera/web exec vitest run` gruen (inkl. `src/lib/color.test.ts`, `src/components/settings/account-settings-form.test.tsx`, `src/components/brand/tessera-logo.test.tsx`). +- `pnpm --filter @tessera/web type-check` gruen. +- `git status --porcelain` nach den drei Commits: nur `.planning/`-Dateien (SUMMARY) offen; keine Datei ausserhalb von `files_modified` veraendert. +- Browser-Nachweis (Orchestrator, nicht Teil dieses Plans): Einstellungen → Konto, `FFED00` bzw. `#fe0` tippen → Farbwaehler folgt; `#ggg` → roter Rand, Speichern gesperrt; Farbe speichern → Kachel der Bildmarke in Kopfzeile/Seitenleiste wechselt sofort in die gewaehlte Farbe; Zuruecksetzen → gelb; Anmeldeseite nach Abmelden → Kachel gelb. + + + +- Hex-Textfeld vorhanden, synchron mit dem Farbwaehler in beide Richtungen, Fehlerzustand + Speichersperre bei ungueltigem Wert, Reset setzt beides zurueck. +- `normalizeHexColor` als reine Funktion mit den neun Testfaellen aus Task 1. +- Bildmarke: gedrehte Kachel per Inline-Style an `--primary` gebunden, Rueckfall Markengelb; Logo-Tests angepasst und gruen; Kommentare in tessera-logo.tsx und brand.ts aktualisiert. +- Zwei CHANGELOG-Stichpunkte, i18n de/en vollstaendig, drei Commits, alle Gates gruen. + + + +Create `.planning/quick/260917-gsh-akzentfarbe-in-einstellungen-konto-zusae/260917-gsh-SUMMARY.md` when done +