From a5f30d432d705f45c3df6d693bace994323d2948 Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 16 Sep 2026 12:44:50 +0200 Subject: [PATCH] =?UTF-8?q?docs(quick-260916-hiv):=20Plan=20=E2=80=94=20Ka?= =?UTF-8?q?lenderquellen-Formular:=20URL-Platzhalter=20je=20Typ=20(EWS/Gra?= =?UTF-8?q?ph/CalDAV/ICS)=20+=20grauer=20EWS-Hinweis,=20i18n=20de/en,=20Ko?= =?UTF-8?q?mponententest,=20Changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../260916-hiv-PLAN.md | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 .planning/quick/260916-hiv-kalenderquellen-formular-url-platzhalter/260916-hiv-PLAN.md diff --git a/.planning/quick/260916-hiv-kalenderquellen-formular-url-platzhalter/260916-hiv-PLAN.md b/.planning/quick/260916-hiv-kalenderquellen-formular-url-platzhalter/260916-hiv-PLAN.md new file mode 100644 index 0000000..7bcd836 --- /dev/null +++ b/.planning/quick/260916-hiv-kalenderquellen-formular-url-platzhalter/260916-hiv-PLAN.md @@ -0,0 +1,179 @@ +--- +phase: quick-260916-hiv +plan: 01 +type: execute +wave: 1 +depends_on: [] +autonomous: true +requirements: [QUICK-260916-HIV] + +files_modified: + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json + - apps/web/src/components/settings/calendar-source-form.tsx + - apps/web/src/components/settings/calendar-source-form.test.tsx + - CHANGELOG.md + +estimate: + tokens: 45000 + raw_tokens: 45000 + tasks: 3 + confidence: low + +must_haves: + truths: + - "Im Formular fuer Kalenderquellen zeigt das Feld „Adresse (URL)“ je nach gewaehltem Typ ein passendes Beispiel als Platzhalter: Exchange + EWS → `https://mail.firma.de/EWS/Exchange.asmx`, Exchange + Graph → `https://graph.microsoft.com/v1.0`, CalDAV → `https://caldav.firma.de/dav/`, ICS → `https://…/kalender.ics`; solange kein Typ gewaehlt ist, weiterhin `https://`." + - "Bei Exchange + EWS steht unter dem Adressfeld ein kleiner grauer Hinweis (`mt-1 text-xs text-muted-foreground`, de/en), dass die vollstaendige EWS-Adresse inkl. /EWS/Exchange.asmx noetig ist; bei Graph, CalDAV und ICS erscheint er nicht. Zeigt das Feld einen Fehler, steht der Hinweis unterhalb des Fehlers." + - "Beide Sprachdateien tragen dieselben fuenf neuen Schluessel unter `widgets.calendar` (Namespace von `useTranslations('widgets')`), Platzhalter-Werte in de und en identisch, Beispiel-Domain `firma.de`, keine kundenspezifische Domain." + - "`CHANGELOG.md` nennt die Aenderung unter `## Unveröffentlicht` → `### Geändert` in Alltagssprache." + - "Type-Check und alle Web-Tests bleiben gruen (Basislinie: 49 Testdateien / 309 Tests, plus die neue Testdatei)." + artifacts: + - "apps/web/src/messages/de.json — 5 neue Schluessel `widgets.calendar.formFieldUrlPlaceholderEws|Graph|Caldav|Ics` + `formFieldUrlHintEws`" + - "apps/web/src/messages/en.json — dieselben 5 Schluessel" + - "apps/web/src/components/settings/calendar-source-form.tsx — typabhaengiger Platzhalter + EWS-Hinweis" + - "apps/web/src/components/settings/calendar-source-form.test.tsx — neuer Komponententest" + - "CHANGELOG.md — Eintrag unter Unveröffentlicht / Geändert" + key_links: + - "`t('calendar.formFieldUrlPlaceholder*')` / `t('calendar.formFieldUrlHintEws')` in der Form ↔ `widgets.calendar.*` in de.json/en.json (Namespace `widgets` kommt aus `useTranslations('widgets')`, Zeile 63)" + - "Hinweis-Sichtbarkeit haengt an `isExchange && exchangeMode === 'ews'` — derselbe Zustand, der auch den EWS-Platzhalter waehlt" + - "`umlaut-guard.spec.ts` erzwingt identische Schluesselmengen in de.json und en.json — fehlt ein Schluessel in einer Datei, wird der Test rot" +--- + + +Das Formular fuer Kalenderquellen (`apps/web/src/components/settings/calendar-source-form.tsx`) zeigt im Adressfeld heute nur den festen Platzhalter `https://`. Kuenftig zeigt es je nach gewaehltem Typ (CalDAV / ICS / Exchange-Graph / Exchange-EWS) eine passende Beispieladresse und blendet bei Exchange-EWS einen grauen Hinweis ein, dass die vollstaendige Adresse inkl. `/EWS/Exchange.asmx` noetig ist — der Servername allein reicht nicht. + +Purpose: Bei EWS scheiterte die Verbindung, wenn Anwender nur den Servernamen eintrugen. Ein sprechendes Beispiel und ein Hinweis verhindern das, ohne dass jemand die Anleitung lesen muss. +Output: fuenf neue Uebersetzungsschluessel (de/en), die angepasste Komponente, ein neuer Komponententest, ein Changelog-Eintrag. + + + +@~/.claude/gsd-core/workflows/execute-plan.md +@~/.claude/gsd-core/templates/summary.md + + + +@.planning/STATE.md +@apps/web/src/components/settings/calendar-source-form.tsx +@apps/web/src/components/settings/widget-settings-panel.test.tsx +@apps/web/src/messages/umlaut-guard.spec.ts + +Gemessene Fakten zur Planungszeit (2026-09-16, Arbeitsbaum sauber auf `main` @ 2a820b6): +- `t` in der Form ist `useTranslations('widgets')` (Zeile 63); alle `calendar.formField*`-Schluessel liegen im JSON unter `widgets.calendar` (de.json/en.json Zeilen 205-236). `"formFieldUrl"` steht in beiden Dateien in Zeile 220, danach folgt `"formFieldUsername"`. +- Muster fuer Beispiel-URLs: `emailAlerts.hostPlaceholderExchange` (de.json:983 `https://mail.firma.de/EWS/Exchange.asmx`; en.json:983 weicht dort mit `company.com` ab — fuer DIESEN Auftrag sind die Platzhalter laut Vorgabe in beiden Sprachen identisch). +- Es gibt keinen Test fuer `calendar-source-form.tsx`; `widget-settings-panel.test.tsx` liefert das Mock-Muster (echte `de.json` ueber `vi.mock('next-intl', …)`, Namespace-Verkettung `ns.key`). Vitest: jsdom, `globals: true`, Setup `src/test/setup.ts`, Alias `@` → `src`. +- `umlaut-guard.spec.ts` prueft (a) keine Ersatzschreibung aus `UMLAUT_REPLACEMENTS`, (b) jedes `ae/oe/ue/ss`-Wort in de.json muss auf `UMLAUT_ALLOWLIST` stehen, (c) identische Schluesselmengen de/en. Von den neuen Texten ist nur `Adresse` verdaechtig und bereits allowlisted — `umlaut-dictionary.ts` bleibt unangetastet. +- `## Unveröffentlicht` in `CHANGELOG.md` (Zeile 5) ist leer; direkt darunter folgt `## 1.1.0 – 2026-09-16`. Bestehende Eintraege beginnen mit einem Bereichsnamen wie „Kalender-Einstellungen: …“. +- Basislinie: `pnpm --filter @tessera/web type-check` Exit 0 (3 s); `pnpm --filter @tessera/web exec vitest run` → 49 Testdateien / 309 Tests gruen; Umlaut-Waechter 3/3 gruen. +- `biome check` ist KEIN Gate: die Wurzel-`biome.json` scheitert unabhaengig von dieser Datei am unbekannten Schluessel `organizeImports` (vorbestehend, nicht Teil dieses Auftrags — `biome.json` nicht anfassen). +- Paketname ist `@tessera/web` (nicht `web`) — Filter immer `--filter @tessera/web`. +- Kein Docker-Bau, kein Deploy, kein Testserver in diesem Auftrag (Deploy macht der User selbst). + + + + + + Task 1: Fuenf Uebersetzungsschluessel in de.json und en.json + apps/web/src/messages/de.json, apps/web/src/messages/en.json + +In BEIDEN Dateien direkt nach der Zeile `"formFieldUrl": …` (Zeile 220, Block `widgets.calendar`) fuenf neue Zeilen einfuegen — gleiche Reihenfolge, gleiche Einrueckung (6 Leerzeichen), jede Zeile mit Komma, weil `"formFieldUsername"` folgt: + +1. `formFieldUrlPlaceholderEws` — Wert `https://mail.firma.de/EWS/Exchange.asmx` +2. `formFieldUrlPlaceholderGraph` — Wert `https://graph.microsoft.com/v1.0` +3. `formFieldUrlPlaceholderCaldav` — Wert `https://caldav.firma.de/dav/` +4. `formFieldUrlPlaceholderIcs` — Wert `https://…/kalender.ics` (echtes Auslassungszeichen U+2026, wie bei `formSaving` im selben Block) +5. `formFieldUrlHintEws` — de: `Vollständige EWS-Adresse inkl. /EWS/Exchange.asmx eintragen – nur der Servername reicht nicht.` / en: `Enter the full EWS address including /EWS/Exchange.asmx – the server name alone is not enough.` (Gedankenstrich U+2013 wie in `formFieldDomainHint`). + +Die vier Platzhalter sind in de.json und en.json IDENTISCH (Beispiel-Adressen, Vorgabe des Users). Nur der Hinweis ist uebersetzt. Beispiel-Domain ist ausschliesslich `firma.de` bzw. `graph.microsoft.com` — keine kundenspezifische Domain (Tessera ist ein Mehrfirmen-Produkt). Keine anderen Schluessel anfassen, `umlaut-dictionary.ts` nicht aendern (`Adresse` ist bereits allowlisted, sonst enthalten die Texte kein `ae/oe/ue/ss`-Wort). JSON muss gueltig bleiben (echte Umlaute direkt als UTF-8, wie im Bestand). + + + cd /home/vicolab/projects/tessera-ctl; set -e; for f in apps/web/src/messages/de.json apps/web/src/messages/en.json; do for k in formFieldUrlPlaceholderEws formFieldUrlPlaceholderGraph formFieldUrlPlaceholderCaldav formFieldUrlPlaceholderIcs formFieldUrlHintEws; do test "$(grep -c "\"$k\"" "$f")" -eq 1; done; done; node -e "const de=require('./apps/web/src/messages/de.json').widgets.calendar, en=require('./apps/web/src/messages/en.json').widgets.calendar; for (const k of ['formFieldUrlPlaceholderEws','formFieldUrlPlaceholderGraph','formFieldUrlPlaceholderCaldav','formFieldUrlPlaceholderIcs']) { if (de[k]!==en[k]) throw new Error('de/en differ: '+k); if (!/^https:\/\//.test(de[k])) throw new Error('not https: '+k); } if (de.formFieldUrlPlaceholderEws!=='https://mail.firma.de/EWS/Exchange.asmx') throw new Error('EWS placeholder'); if (de.formFieldUrlPlaceholderGraph!=='https://graph.microsoft.com/v1.0') throw new Error('Graph placeholder'); if (de.formFieldUrlPlaceholderCaldav!=='https://caldav.firma.de/dav/') throw new Error('CalDAV placeholder'); if (!de.formFieldUrlPlaceholderIcs.endsWith('/kalender.ics')) throw new Error('ICS placeholder'); if (!de.formFieldUrlHintEws.includes('/EWS/Exchange.asmx') || !en.formFieldUrlHintEws.includes('/EWS/Exchange.asmx')) throw new Error('hint'); if (de.formFieldUrlHintEws===en.formFieldUrlHintEws) throw new Error('hint not translated'); const keys=Object.keys(de); const i=keys.indexOf('formFieldUrl'); if (keys[i+1]!=='formFieldUrlPlaceholderEws' || keys[i+5]!=='formFieldUrlHintEws') throw new Error('order'); console.log('I18N_OK')"; ! grep -q 'ctl\.de' apps/web/src/messages/de.json apps/web/src/messages/en.json; pnpm --filter @tessera/web exec vitest run src/messages/umlaut-guard.spec.ts + + Beide Sprachdateien enthalten die fuenf Schluessel genau einmal, direkt hinter `formFieldUrl`, mit den vorgegebenen Werten (Platzhalter identisch, Hinweis uebersetzt, keine kundenspezifische Domain); `umlaut-guard.spec.ts` bleibt 3/3 gruen (Schluesselparitaet de/en, keine Ersatzschreibung). + + + + + + + + Task 2: Typabhaengiger Platzhalter + EWS-Hinweis in der Komponente, mit Komponententest + apps/web/src/components/settings/calendar-source-form.tsx, apps/web/src/components/settings/calendar-source-form.test.tsx + + Neue Testdatei `calendar-source-form.test.tsx` (Muster: `widget-settings-panel.test.tsx` — `vi.mock('next-intl', …)` mit Lookup in der echten `de.json` und Namespace-Verkettung `ns.key`; zusaetzlich `vi.mock('@/lib/calendar-api', () => ({ testSourceConfig: vi.fn(), testSource: vi.fn() }))`, damit kein echter Aufruf passiert; `afterEach(cleanup)`; Erwartungstexte aus `de.widgets.calendar`). Render ``; Elemente: URL-Feld `screen.getByLabelText(/Adresse \(URL\)/)`, Typ `screen.getByLabelText(/^Typ/)`, Exchange-Anbindung `screen.getByLabelText(/Exchange-Anbindung/)`; Umschalten per `fireEvent.change(el, { target: { value } })`. + - Test 1: Ohne gewaehlten Typ hat das URL-Feld den Platzhalter `https://` und es gibt kein Element mit `data-testid="source-url-hint-ews"`. + - Test 2: Typ `caldav` → Platzhalter = `formFieldUrlPlaceholderCaldav`; kein Hinweis. + - Test 3: Typ `ics` → Platzhalter = `formFieldUrlPlaceholderIcs`; kein Hinweis. + - Test 4: Typ `exchange` (Standardmodus `graph`) → Platzhalter = `formFieldUrlPlaceholderGraph`; kein Hinweis. + - Test 5: Typ `exchange` + Modus `ews` → Platzhalter = `formFieldUrlPlaceholderEws`; Hinweis vorhanden, Text = `formFieldUrlHintEws`, `className` enthaelt `text-muted-foreground`. Zurueck auf `graph` → Hinweis weg, Platzhalter wieder Graph. + - Test 6: Typ `exchange` + Modus `ews` + Eingabe `http://mail.firma.de/EWS/Exchange.asmx` (http statt https) → Fehlertext `formUrlErrorHttps` UND Hinweis sind beide sichtbar, und der Hinweis steht im DOM NACH dem Fehler (`fehler.compareDocumentPosition(hinweis) & Node.DOCUMENT_POSITION_FOLLOWING` ist truthy). + + +Erst die Testdatei schreiben und rot sehen (Tests 2-6 schlagen fehl, weil Platzhalter fest und Hinweis nicht vorhanden), dann die Komponente anpassen: + +1. Nach `const isExchange = type === 'exchange';` (Zeile 81) zwei reine Ableitungen ohne State ergaenzen (kein `setState` im Render — siehe Kommentar Zeile 100-102): `const isEws = isExchange && exchangeMode === 'ews';` und `const urlPlaceholder`, das per Verzweigung liefert: bei `isExchange` → `isEws ? t('calendar.formFieldUrlPlaceholderEws') : t('calendar.formFieldUrlPlaceholderGraph')`; bei `type === 'caldav'` → `t('calendar.formFieldUrlPlaceholderCaldav')`; bei `type === 'ics'` → `t('calendar.formFieldUrlPlaceholderIcs')`; sonst (kein Typ gewaehlt) der bisherige Festwert `https://`. Kein `useMemo` noetig. +2. Im URL-Input (`id="source-url"`, Zeile 247-264) das feste `placeholder`-Attribut (Zeile 251) auf `placeholder={urlPlaceholder}` umstellen. Sonst nichts am Input aendern (Validierung, Klassen, Handler bleiben). +3. Direkt NACH dem bestehenden Fehlerabsatz `{urlError && (

…

)}` (Zeile 265-267) einen zweiten bedingten Absatz einfuegen: `{isEws && (

{t('calendar.formFieldUrlHintEws')}

)}`. Entscheidung (von den zwei erlaubten Varianten): der Hinweis ist bei EWS IMMER sichtbar und steht bei einem Fehler UNTER dem Fehler — so hilft er auch dann, wenn die Eingabe gerade abgelehnt wird. +4. Den Doku-Kommentar der Komponente (Zeile 48-56) um einen Satz ergaenzen: Platzhalter des URL-Feldes typabhaengig, EWS-Hinweis unter dem Feld (Quick 260916-hiv). Keine Schluesselnamen im Kommentar aufzaehlen und den alten Attributwert nicht im Kommentar zitieren. + +Keine weiteren Aenderungen: `EXCHANGE_MODES`, `SOURCE_TYPES`, `handleTest`, `handleSubmit`, Payload bleiben unveraendert. Keine neuen Pakete. +
+ + cd /home/vicolab/projects/tessera-ctl; set -e; F=apps/web/src/components/settings/calendar-source-form.tsx; test "$(grep -c 'placeholder="https://"' "$F")" -eq 0; test "$(grep -c 'placeholder={urlPlaceholder}' "$F")" -eq 1; for k in formFieldUrlPlaceholderEws formFieldUrlPlaceholderGraph formFieldUrlPlaceholderCaldav formFieldUrlPlaceholderIcs formFieldUrlHintEws; do test "$(grep -c "calendar.$k" "$F")" -ge 1; done; test "$(grep -c 'data-testid="source-url-hint-ews"' "$F")" -eq 1; test "$(grep -c 'text-muted-foreground' "$F")" -ge 1; test -f apps/web/src/components/settings/calendar-source-form.test.tsx; pnpm --filter @tessera/web exec vitest run src/components/settings/calendar-source-form.test.tsx; pnpm --filter @tessera/web type-check + + Der neue Test (6 Faelle) ist gruen, Type-Check Exit 0; die Komponente liest den Platzhalter aus `urlPlaceholder` (kein fester Wert mehr im Attribut), rendert den grauen Hinweis nur bei Exchange + EWS und dort unterhalb eines eventuellen Fehlers. +
+ + + Task 3: Changelog-Eintrag unter Unveröffentlicht / Geändert + CHANGELOG.md + +Unter `## Unveröffentlicht` (Zeile 5, derzeit leer — direkt darunter folgt `## 1.1.0 – 2026-09-16`) einfuegen: Leerzeile, `### Geändert`, Leerzeile, genau einen Listenpunkt, Leerzeile vor `## 1.1.0`. Listenpunkt wortgleich: + +`- Kalender-Einstellungen: Das Feld „Adresse (URL)“ im Formular für Kalenderquellen zeigt jetzt je nach Typ ein passendes Beispiel (z. B. `https://mail.firma.de/EWS/Exchange.asmx` für Exchange EWS) und bei Exchange EWS einen Hinweis, dass die vollständige Adresse nötig ist – der Servername allein reicht nicht.` + +Stil wie die Bestandseintraege: Alltagssprache, echte Umlaute, typografische Anfuehrungszeichen „…“, keine Dateinamen, keine Commit-Kuerzel. Abschnitte `## 1.1.0` und `## 1.0.0` unveraendert lassen. Die Seite „Was ist neu“ zeigt diesen Abschnitt auf der Beta automatisch, sobald er einen Listenpunkt hat (`filterChangelogForChannel` blendet nur leere Abschnitte aus) — dort ist nichts zu tun. + + + cd /home/vicolab/projects/tessera-ctl; set -e; SEC="$(awk '/^## Unveröffentlicht$/{f=1;next} /^## /{if(f)exit} f' CHANGELOG.md)"; test "$(printf '%s\n' "$SEC" | grep -c '^### Geändert$')" -eq 1; test "$(printf '%s\n' "$SEC" | grep -c '^- Kalender-Einstellungen: Das Feld „Adresse (URL)“')" -eq 1; test "$(printf '%s\n' "$SEC" | grep -c 'Exchange.asmx')" -eq 1; test "$(printf '%s\n' "$SEC" | grep -c '^- ')" -eq 1; test "$(grep -c '^## Unveröffentlicht$' CHANGELOG.md)" -eq 1; test "$(grep -c '^## 1.1.0 – 2026-09-16$' CHANGELOG.md)" -eq 1; test "$(grep -c '^## 1.0.0 – 2026-09-15$' CHANGELOG.md)" -eq 1; pnpm --filter @tessera/web exec vitest run src/lib/changelog.test.ts + + `## Unveröffentlicht` enthaelt genau eine Untergruppe `### Geändert` mit genau einem Listenpunkt zum Kalenderquellen-Formular; die Versionsabschnitte 1.1.0 und 1.0.0 sind unveraendert; `changelog.test.ts` bleibt gruen. + + +
+ + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Browser-UI → i18n-Text | Platzhalter und Hinweis sind statische Uebersetzungsstrings; sie werden als React-Textknoten gerendert (automatisch escaped), nicht als HTML. Keine Nutzereingabe fliesst in Platzhalter oder Hinweis. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-HIV-01 | Information Disclosure | Platzhalter-Werte in de.json/en.json | low | mitigate | Nur neutrale Beispiel-Domains (`firma.de`, `graph.microsoft.com`); Gate in Task 1 verbietet eine kundenspezifische Domain in beiden Sprachdateien. | +| T-HIV-02 | Tampering | Hinweis-Text im DOM | low | accept | Reiner Uebersetzungsstring ueber `t()`, als Textknoten gerendert — kein `dangerouslySetInnerHTML`, keine Interpolation von Nutzereingaben. | +| T-HIV-SC | Tampering | npm-Installationen | low | accept | Dieser Plan installiert keine Pakete (kein `pnpm add`); Lockfile bleibt unveraendert. | + + + +Nach allen drei Tasks, vom Repo-Wurzelverzeichnis: + +1. `pnpm --filter @tessera/web type-check` → Exit 0. +2. `pnpm --filter @tessera/web exec vitest run` → 50 Testdateien gruen (49 Bestand + `calendar-source-form.test.tsx`), mindestens 315 Tests (309 + 6), keine Fehlschlaege. +3. `git diff --stat` beruehrt genau die fuenf Dateien aus `files_modified` (plus SUMMARY/Planungsdateien) — kein `biome.json`, kein `umlaut-dictionary.ts`, kein `pnpm-lock.yaml`. +4. Kein Docker-Bau und kein Deploy in diesem Auftrag; die Browser-Pruefung auf der Beta macht der User nach dem naechsten Pull. + + + +- Adressfeld zeigt je Typ das vorgegebene Beispiel als Platzhalter (EWS / Graph / CalDAV / ICS), ohne Typ weiterhin `https://`. +- Grauer EWS-Hinweis erscheint nur bei Exchange + EWS, unterhalb eines eventuellen Fehlers, Text aus `widgets.calendar.formFieldUrlHintEws` (de/en). +- de.json und en.json tragen dieselben fuenf Schluessel; Umlaut-Waechter gruen; keine kundenspezifische Domain. +- Neuer Komponententest mit 6 Faellen gruen; Type-Check gruen; Gesamt-Testlauf gruen. +- CHANGELOG.md: `## Unveröffentlicht` → `### Geändert` mit genau einem Eintrag zum Kalenderquellen-Formular. + + + +Create `.planning/quick/260916-hiv-kalenderquellen-formular-url-platzhalter/260916-hiv-SUMMARY.md` when done +