Kalender-Widget, Tooltip beim Überfahren eines Tages: lange Termintitel werden auf mehrere Zeilen umbrochen statt mit „…“ abgeschnitten; der Tooltip ist 288 px breit und wird am rechten Fensterrand weiterhin so verschoben, dass er vollständig sichtbar bleibt (Klemmwert aus derselben Konstante wie die Breite). Uhrzeit-Spalte, Portal in document.body, Begrenzung auf 5 Einträge plus Hinweis bleiben unverändert.
Notiz-Widget: der Textbereich (MDEditor) folgt dem Hell/Dunkel-Schalter von Tessera (next-themes `resolvedTheme`), nicht mehr der Betriebssystem-Einstellung. Vor dem Mount ist der Wert 'light' (mounted-Guard wie in changelog-view.tsx), danach 'dark' genau dann, wenn `resolvedTheme === 'dark'`.
CHANGELOG.md hat exakt den Inhalt von CHANGELOG.soll.md: gleiche drei `## `-Überschriften (`## Unveröffentlicht`, `## 1.1.0 – 2026-09-16`, `## 1.0.0 – 2026-09-15`), 28 Stichpunkte, jeder Punkt eine kurze Zeile ohne Punkt am Ende, echte Umlaute. CHANGELOG.soll.md ist danach gelöscht (nicht committet, war nie im Git).
Gates: `pnpm --filter @tessera/web type-check` Exit 0; `pnpm --filter @tessera/web exec vitest run` komplett grün (Basislinie 52 Dateien / 344 Tests → danach 52 Dateien / 347 Tests: +1 Kalender, +2 Notiz); Umlaut-Wächter 3/3; changelog.test.ts 10/10; `sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0` Exit 0. KEIN `biome check`, kein Docker-Build, kein Deploy, kein Testserver, kein `git push`.
apps/web/src/components/dashboard/widgets/calendar-widget.tsx — Konstanten `TOOLTIP_WIDTH_PX = 288` und `TOOLTIP_EDGE_PX = 4`, Titel-Span im Tooltip mit `min-w-0 break-words`
apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx — neuer Test 4c (Titel-Span umbricht, Tooltip-Breite 288 px)
apps/web/src/components/dashboard/widgets/note-widget.tsx — `useTheme` aus next-themes, `mounted`-State, `data-color-mode={mode}`
apps/web/src/components/dashboard/widgets/note-widget.test.tsx — `vi.hoisted`-Themenzustand, next-themes-Mock, zwei neue Tests (dark/light)
CHANGELOG.md — Stichpunkt-Fassung
Tooltip-Klemmung: `left = max(4, min(rect.left, innerWidth − Breite − Rand))` — Breite und Klemmwert müssen aus EINER Konstante kommen, sonst driften sie (bisher `w-60` = 240 px und `− 244` hart nebeneinander).
Titel-Span steht in einem `flex`-Container neben der Uhrzeit-Spalte (`shrink-0`); ohne `min-w-0` darf ein Flex-Kind nicht unter seine Inhaltsbreite schrumpfen, dann greift `break-words` nicht und der Text ragt heraus — deshalb beide Klassen.
@uiw/react-md-editor wertet `[data-color-mode]` per CSS-Selektor am nächsten Vorfahren aus; das Attribut am Wurzel-Div des Widgets reicht (deshalb stand dort bisher der feste Wert). `ThemeProvider` in apps/web/src/app/layout.tsx (attribute="class", defaultTheme="system", enableSystem) liefert `resolvedTheme` = 'light' | 'dark'.
Nur note-widget.test.tsx rendert NoteWidget wirklich; page.test.tsx mockt `@/components/dashboard/widgets/note-widget` als `() => null`, die Registry verdrahtet das Widget erst über `wireNoteWidget()` in page.tsx — kein weiterer Test braucht einen next-themes-Mock (gemessen per grep).
changelog.ts schneidet an `^## ` und erkennt `## Unveröffentlicht` exakt (`UNRELEASED_RE`); publish-release.sh schneidet mit awk an `^## X.Y.Z( |$)` — die drei Überschriften müssen zeichengenau bleiben (Gedankenstrich „–“, Datum). umlaut-guard.spec.ts prüft nur de.json/en.json, nicht CHANGELOG.md.
Drei Nachträge zu den heutigen Dashboard-Arbeiten (260916-hiv/htc/iex), alle vom Anwender im Browser gemeldet: (1) Der Termin-Tooltip des Kalender-Widgets schneidet lange Titel ab („Deutscher Weltkindertag (…“) — er soll umbrechen. (2) Der Textbereich des Notiz-Widgets bleibt dunkel, wenn Tessera auf „Hell“ steht, weil er der Betriebssystem-Einstellung folgt — er soll dem Tessera-Schalter folgen, wie es die Seite „Was ist neu“ schon tut. (3) Die Änderungsliste CHANGELOG.md ist in Fließtext geraten — sie wird auf kurze Stichpunkte gestrafft (Vorlage liegt fertig im Auftragsordner).
Purpose: Sichtbare Bedienfehler vor der nächsten Beta beseitigen; Änderungsliste wieder lesbar.
Output: Zwei Komponentenkorrekturen mit Tests, eine neue CHANGELOG.md, drei Commits.
calendar-widget.tsx: Tooltip-Block Z. 290-315. Z. 296 className="pointer-events-none fixed z-50 w-60 rounded border border-border bg-card p-2 text-xs text-foreground shadow-lg", Z. 299 left: Math.max(4, Math.min(hover.rect.left, window.innerWidth - 244)), Z. 307 <span className="truncate">{event.title}</span> (OHNE min-w-0 — die Vorgabe „min-w-0 behalten“ trifft nicht zu, es muss ergänzt werden). Die Liste „Nächste Termine“ (Z. 270-281) nutzt ebenfalls truncate — die bleibt unverändert (nur der Tooltip ist Auftrag).
calendar-widget.test.tsx: 9 Tests; Test 4 (Z. 149-173) öffnet den Tooltip über fireEvent.mouseEnter auf [data-date="2026-07-20"] mit Terminen „Team Meeting“ und „Lunch“; within ist bereits importiert (Z. 1). Der Titel „Team Meeting“ steht auch in „Nächste Termine“ im DOM — Abfragen daher IMMER mit within(tooltip).
note-widget.tsx: Imports Z. 3-9 (kein next-themes), Komponente ab Z. 42, State-Block Z. 44-51, Cleanup-Effekt Z. 56-61, Wurzel-Div Z. 131 mit festem Farbmodus-Attribut. MDEditor ab Z. 175 ohne eigenes wrapperElement.
note-widget.test.tsx: 7 Tests; Mocks für next-intl (Z. 5-13) und @uiw/react-md-editor (Z. 22-78); statischer Import import { NoteWidget } from './note-widget' Z. 81 („Must import after mocks“); beforeEach mit vi.useFakeTimers() und fetch-Spy; KEIN next-themes-Mock.
changelog-page.test.tsx Z. 42-44 zeigt den Mock-Stil: vi.mock('next-themes', () => ({ useTheme: () => ({ resolvedTheme: 'light' }) })).
CHANGELOG.soll.md: 3 ## -Überschriften (Z. 5, 26, 47), 28 Stichpunkte, kein Punkt am Zeilenende, keine CRLF. Sachlich gegen CHANGELOG.md und STATE.md geprüft — kein Fehler gefunden („Link“ in der 1.0.0-Liste ist historisch korrekt; Kalender-Widget/Favoriten-Titel unter „Neu“ statt „Geändert“ ist die gewollte Neusortierung).
publish-release.sh: --dry-run --tag v1.1.0 läuft ohne Token und ohne Netz (Exit 0, druckt JSON); jq vorhanden. awk-Schnitt Z. 81-89 an ^## 1\.1\.0( |$).
docs/anleitung-anwender.md beschreibt nur Zweck und Gruppen der Liste, nicht den Stil — bleibt unangetastet.
Basislinie der vier betroffenen Testdateien: 4 Dateien / 29 Tests grün. Gesamt-Basislinie laut STATE.md: 52 Dateien / 344 Tests.
Task 1: Kalender-Tooltip — Titel umbrechen statt abschneiden, Breite und Klemmwert aus einer Konstante
apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx Z. 26-36 (Kopfkommentar Tooltip), Z. 55-60 (hover-State), Z. 288-316 (Tooltip-Portal)
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx Z. 1-12, Z. 149-201 (Tests 4 und 4b)
- Test 4c (neu, nach 4b): Termin mit langem Titel (z. B. `ev('Deutscher Weltkindertag (Aktionstag der Kinderrechte)', …)` am 2026-07-20), Tooltip öffnen wie in Test 4; `const tooltip = screen.getByTestId('calendar-day-tooltip')`; `const title = within(tooltip).getByText('Deutscher Weltkindertag (Aktionstag der Kinderrechte)')`; `expect(title).toHaveClass('break-words')`, `expect(title).toHaveClass('min-w-0')`, `expect(title).not.toHaveClass('truncate')`; `expect(tooltip).toHaveStyle({ width: '288px' })`. Deutscher Testname: „Test 4c: Tooltip bricht lange Termintitel um statt sie abzuschneiden“.
- Tests 4 und 4b bleiben unverändert grün (Portal, 5-Eintrag-Grenze, Hinweis).
RED zuerst: Test 4c in calendar-widget.test.tsx ergänzen, `pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/calendar-widget.test.tsx` — 4c muss ROT sein (Titel hat noch `truncate`, Breite kommt aus `w-60`).
GREEN in calendar-widget.tsx:
1. Zwei Modul-Konstanten oberhalb der Komponente (bei den anderen Konstanten/Imports) anlegen: `const TOOLTIP_WIDTH_PX = 288;` und `const TOOLTIP_EDGE_PX = 4;` mit kurzem deutschen Kommentar (quick-260916-j4f: Breite und Rand-Klemmung des Termin-Tooltips aus einer Quelle, damit Breite und Klemmwert nicht auseinanderlaufen; 288 px entspricht Tailwind w-72).
2. Tooltip-Div (Z. 296): die Klasse `w-60` aus `className` entfernen; alles andere in der Klassenliste bleibt. Im `style`-Objekt `width: TOOLTIP_WIDTH_PX` ergänzen und die `left`-Berechnung auf `Math.max(TOOLTIP_EDGE_PX, Math.min(hover.rect.left, window.innerWidth - TOOLTIP_WIDTH_PX - TOOLTIP_EDGE_PX))` umstellen (bisher hart 4 und 244). `top` bleibt `hover.rect.bottom + 4` — dort ebenfalls `TOOLTIP_EDGE_PX` verwenden.
3. Titel-Span (Z. 307): `className="truncate"` → `className="min-w-0 break-words"`. Die Uhrzeit-Spalte (`shrink-0 tabular-nums …`) bleibt.
4. Kopfkommentar Z. 31-33 um einen Satz ergänzen: Titel im Tooltip brechen um (kein truncate), Breite/Klemmung über `TOOLTIP_WIDTH_PX`/`TOOLTIP_EDGE_PX` (quick-260916-j4f).
Nichts an der Liste „Nächste Termine“, an `hoverEvents.slice(0, 5)`, am Portal oder an den Datenpfaden ändern.
Test 4c muss danach GRÜN sein, alle 10 Kalender-Tests grün. Commit: `fix(web): Kalender-Tooltip bricht lange Termintitel um (Breite/Klemmung aus einer Konstante)`.
cd /home/vicolab/projects/tessera-ctl && grep -q 'const TOOLTIP_WIDTH_PX = 288' apps/web/src/components/dashboard/widgets/calendar-widget.tsx && grep -q 'min-w-0 break-words' apps/web/src/components/dashboard/widgets/calendar-widget.tsx && grep -q 'Test 4c' apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx && pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/calendar-widget.test.tsx
Tooltip-Titel tragen `min-w-0 break-words` und kein `truncate`; Breite 288 px und Klemmung kommen aus `TOOLTIP_WIDTH_PX`/`TOOLTIP_EDGE_PX`; calendar-widget.test.tsx 10/10 grün (vorher 9), Test 4c belegt Klassen und Breite.
Task 2: Notiz-Widget folgt dem Tessera-Farbmodus (next-themes + mounted-Guard wie changelog-view)
apps/web/src/components/dashboard/widgets/note-widget.tsx, apps/web/src/components/dashboard/widgets/note-widget.test.tsx
- apps/web/src/components/changelog/changelog-view.tsx Z. 1-38 (Vorbild)
- apps/web/src/components/dashboard/widgets/note-widget.tsx Z. 1-12, Z. 42-62, Z. 128-135
- apps/web/src/components/dashboard/widgets/note-widget.test.tsx Z. 1-25, Z. 78-107
- apps/web/src/app/(portal)/changelog/changelog-page.test.tsx Z. 40-45 (Mock-Stil next-themes)
- Neuer Test „setzt data-color-mode auf dark, wenn Tessera auf Dunkel steht“: Themenzustand auf 'dark' stellen, `const { container } = render()`; `container.querySelector('[data-color-mode]')` hat Attribut `data-color-mode` = `'dark'`.
- Neuer Test „setzt data-color-mode auf light, wenn Tessera auf Hell steht“: Themenzustand 'light' → Attribut `'light'`.
- Die 7 bestehenden Tests bleiben grün (der Farbmodus ist für sie egal; Vorgabe im Mock: 'light').
RED zuerst in note-widget.test.tsx:
1. Vor den bestehenden `vi.mock`-Aufrufen (nach den Imports) einen gehobenen Themenzustand anlegen: `const themeMock = vi.hoisted(() => ({ resolvedTheme: 'light' as 'light' | 'dark' }));` und darunter `vi.mock('next-themes', () => ({ useTheme: () => ({ resolvedTheme: themeMock.resolvedTheme }) }));` — `vi.hoisted`, damit die Variable trotz Hoisting der Mocks und des statischen Imports in Z. 81 sicher initialisiert ist. Im `beforeEach` `themeMock.resolvedTheme = 'light'` zurücksetzen.
2. Die zwei Tests aus `` ans Ende des `describe` anhängen (Zustand jeweils VOR `render` setzen). Lauf: `pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/note-widget.test.tsx` — beide neuen Tests ROT (Attribut hat noch den festen Wert).
GREEN in note-widget.tsx:
3. Import `import { useTheme } from 'next-themes';` ergänzen (Paket ist installiert, 0.4.6). `useEffect`/`useState` sind schon importiert.
4. In `NoteWidget` direkt nach `const t = useTranslations('widgets');`: `const { resolvedTheme } = useTheme();` und `const [mounted, setMounted] = useState(false);`; einen eigenen Effekt `useEffect(() => { setMounted(true); }, []);` (getrennt vom Cleanup-Effekt Z. 56-61). Danach `const colorMode: 'light' | 'dark' = mounted && resolvedTheme === 'dark' ? 'dark' : 'light';` — exakt das Muster aus changelog-view.tsx Z. 20-27 (mounted-Guard, damit Server- und Client-Markup übereinstimmen).
5. Wurzel-Div Z. 131: den festen Attributwert durch `data-color-mode={colorMode}` ersetzen. Kurzer Kommentar darüber (quick-260916-j4f: folgt dem Tessera-Schalter statt der Betriebssystem-Einstellung, Muster changelog-view.tsx).
Keine Änderung an MDEditor-Props, Speichern, Abhaken oder Übersetzungen.
Danach alle 9 Notiz-Tests grün und `pnpm --filter @tessera/web type-check` Exit 0. Commit: `fix(web): Notiz-Widget folgt dem Hell/Dunkel-Schalter von Tessera statt der Systemeinstellung`.
cd /home/vicolab/projects/tessera-ctl && grep -q "from 'next-themes'" apps/web/src/components/dashboard/widgets/note-widget.tsx && grep -q 'data-color-mode={colorMode}' apps/web/src/components/dashboard/widgets/note-widget.tsx && ! grep -q 'data-color-mode="auto"' apps/web/src/components/dashboard/widgets/note-widget.tsx && grep -q "vi.mock('next-themes'" apps/web/src/components/dashboard/widgets/note-widget.test.tsx && pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/note-widget.test.tsx && pnpm --filter @tessera/web type-check
Wurzel-Div des Notiz-Widgets trägt `data-color-mode={colorMode}` aus `useTheme().resolvedTheme` mit mounted-Guard; note-widget.test.tsx 9/9 grün (vorher 7) mit next-themes-Mock über `vi.hoisted`; tsc Exit 0.
Task 3: CHANGELOG.md auf Stichpunkte straffen (Soll-Datei übernehmen, Vorlage löschen), Gesamtgates
CHANGELOG.md, .planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/CHANGELOG.soll.md
- .planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/CHANGELOG.soll.md (ganz, 63 Zeilen)
- CHANGELOG.md (ganz, zum Abgleich der Überschriften)
- apps/web/src/lib/changelog.ts Z. 26-40 (UNRELEASED_HEADING, SECTION_RE)
- .gitea/scripts/publish-release.sh Z. 78-95 (awk-Schnitt)
1. CHANGELOG.md vollständig durch den Inhalt von CHANGELOG.soll.md ersetzen: `cp .planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/CHANGELOG.soll.md CHANGELOG.md` (Byte-genau, keine Nacharbeit, keine Umlaut-Ersetzung — echte Umlaute bleiben; der Umlaut-Wächter prüft nur de.json/en.json). Ein sachlicher Fehler in der Vorlage wurde bei der Planung nicht gefunden; falls beim Lesen doch einer auffällt (Aussage, die dem Code oder STATE.md widerspricht), korrigieren und im SUMMARY unter „Abweichungen“ nennen.
2. Prüfen, dass die drei `## `-Überschriften zeichengenau erhalten sind (Gedankenstrich „–“, Datum) und kein Stichpunkt mit einem Punkt endet (siehe verify).
3. Vorlage löschen: `rm .planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/CHANGELOG.soll.md` (Scratch-Eingabe, nie im Git — daher `rm`, nicht `git rm`).
4. Gesamtgates laufen lassen und die Zahlen für das SUMMARY notieren: `pnpm --filter @tessera/web type-check`; `pnpm --filter @tessera/web exec vitest run` (Erwartung 52 Dateien / 347 Tests, darin umlaut-guard 3/3 und changelog.test.ts 10/10); `sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0` (Exit 0, JSON enthält „Seite „Was ist neu““ im body). docs/anleitung-anwender.md NICHT anfassen (beschreibt nur Zweck und Gruppen der Liste, nicht den Stil).
5. Commit nur mit CHANGELOG.md: `docs: Changelog auf Stichpunkte gestrafft (kein Fließtext)`.
cd /home/vicolab/projects/tessera-ctl && test ! -e .planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/CHANGELOG.soll.md && grep -qx '## Unveröffentlicht' CHANGELOG.md && grep -qx '## 1.1.0 – 2026-09-16' CHANGELOG.md && grep -qx '## 1.0.0 – 2026-09-15' CHANGELOG.md && ! grep -E '^## ' CHANGELOG.md | grep -vqE '^## (Unveröffentlicht|1\.1\.0 – 2026-09-16|1\.0\.0 – 2026-09-15)$' && test "$(grep -E '^- ' CHANGELOG.md | wc -l)" = 28 && ! grep -qE '^- .*\.$' CHANGELOG.md && grep -q 'Textbereich folgt dem Hell/Dunkel-Schalter' CHANGELOG.md && ! grep -q $'\r' CHANGELOG.md && sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0 >/dev/null && pnpm --filter @tessera/web type-check && pnpm --filter @tessera/web exec vitest run
CHANGELOG.md entspricht der Soll-Vorlage (3 Überschriften, 28 Stichpunkte, kein Satzpunkt am Zeilenende, echte Umlaute); CHANGELOG.soll.md gelöscht; Release-Skript findet den 1.1.0-Abschnitt im Probelauf; Web-tsc 0; Web-vitest komplett grün mit 52 Dateien / 347 Tests.
<threat_model>
Trust Boundaries
Boundary
Description
Kalenderdaten → DOM (Tooltip)
Termintitel aus externen Kalenderquellen werden im Tooltip gerendert.
next-themes (localStorage theme) → Notiz-Widget
Der Farbmodus kommt aus dem clientseitigen Themenzustand.
CHANGELOG.md → Build → Seite „Was ist neu“ / Gitea-Release
Markdown wird zur Bauzeit eingebettet und per rehypeSanitize gerendert; das Release-Skript liest Abschnitte.
STRIDE Threat Register
Threat ID
Category
Component
Severity
Disposition
Mitigation Plan
T-J4F-01
Tampering / XSS
calendar-widget.tsx Tooltip-Titel
low
mitigate
Titel bleibt React-Textknoten ({event.title}), nur CSS-Klassen ändern sich; kein dangerouslySetInnerHTML.
T-J4F-02
Denial of Service
Tooltip mit sehr langem Titel
low
mitigate
break-words bricht auch wortlose Zeichenketten; Breite fest 288 px, Portal pointer-events-none, weiterhin maximal 5 Einträge.
T-J4F-03
Information Disclosure
Notiz-Widget Farbmodus
low
accept
resolvedTheme ist nur 'light'/'dark'; jeder andere Wert fällt auf 'light' zurück. Kein Datenabfluss.
T-J4F-04
Tampering
CHANGELOG.md-Ersatz
low
mitigate
Byte-genaues Kopieren der geprüften Vorlage; Gates prüfen Überschriften, Punktzahl und Release-Schnitt; rehypeSanitize in changelog-view.tsx bleibt.
T-J4F-SC
Tampering
npm/pip/cargo installs
low
accept
Keine Paketinstallation — next-themes 0.4.6 ist bereits im Lockfile und in changelog-view.tsx in Gebrauch.
</threat_model>
- `pnpm --filter @tessera/web type-check` → Exit 0.
- `pnpm --filter @tessera/web exec vitest run` → komplett grün, 52 Dateien / 347 Tests (Basislinie 52 / 344; +1 Kalender, +2 Notiz); umlaut-guard 3/3 und changelog.test.ts 10/10 im Gesamtlauf enthalten.
- `sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0` → Exit 0.
- CHANGELOG.soll.md existiert nicht mehr; `git status` zeigt sie nicht (war nie getrackt).
- Kein `biome check` als Gate (bekannter Konfigurationsfehler, biome.json unverändert); kein Docker-Build, kein Deploy, kein Testserver, kein `git push`.
- Drei Commits, einer je Task (Konvention der heutigen Quick-Tasks): `fix(web): …`, `fix(web): …`, `docs: …`.
<success_criteria>
Kalender-Tooltip: lange Titel umbrechen (Test 4c), Breite und Klemmung aus einer Konstante.
Notiz-Widget: data-color-mode folgt resolvedTheme von next-themes mit mounted-Guard (zwei neue Tests).
CHANGELOG.md in Stichpunkt-Fassung, Überschriften intakt, Vorlage gelöscht.
Alle Gates aus <verification> grün; SUMMARY nennt die gemessenen Zahlen (vorher 52/344).
</success_criteria>
Create `.planning/quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/260916-j4f-SUMMARY.md` when done (Muster: die SUMMARY von 260916-iex — Abschnitte Was gebaut wurde / Entscheidungen / Gemessene Zahlen / Commits / Abweichungen vom Plan).