Compare commits
8 Commits
6003f21431
...
7110512d83
| Author | SHA1 | Date | |
|---|---|---|---|
| 7110512d83 | |||
| 7429c5bd8d | |||
| 4ddadc63ab | |||
| 45b20a9fd4 | |||
| f79c6bbe8c | |||
| c85cf9a47b | |||
| 1e4ec30190 | |||
| 5313fbc019 |
+3
-1
@@ -414,6 +414,8 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
|
|||||||
| 260916-htc | **Kalender-Widget nach Vorbild personal-dashboard.** Monatsraster (Zurueck/Monat/Weiter, Mo-So, 42 Zellen ab Montag, heute hervorgehoben, Zaehler-Plakette je Tag, Termine beim Ueberfahren als Tooltip per `createPortal`/`position: fixed`, weil die Kachel `overflow-hidden` ist) + Block "Naechste Termine" (Datum/Uhrzeit, Titel, Ort, Farbpunkt). Drei Einstellungen unter Einstellungen -> Dashboard -> Widgets (`CalendarConfig`, Muster ClockConfig): `showMonth` (Standard an), `maxEvents` 0..10 (Standard 3, 0 = ausblenden), `lookaheadDays` 7/14/30/60/90 (Standard 30). Ein `fetchEvents(from,to)`-Aufruf je Ladevorgang mit lokalen Tagesgrenzen (Backend-Cache-Schluessel bleibt stabil), Neuladen bei Monatswechsel, 5-Minuten-Intervall bleibt. Neues reines Modul `calendar-month.ts` (resolveCalendarConfig, buildCalendarDays, groupEventsByDate, computeFetchWindow, selectUpcomingEvents). Mindestgroesse calendar 6x8 (Registry-Test mitgezogen). Keine Quellenauswahl pro Widget (User-Entscheidung: nur Optik). 16 i18n-Schluessel de/en, Changelog "Geändert", Anwenderhandbuch. Tests Web 315 -> 332 / 51 Dateien, tsc 0, 12 Dateien. | 2026-09-16 | 0858102,61996dc,6d8c7c4 | [260916-htc-kalender-widget-nach-vorbild-personal-da](./quick/260916-htc-kalender-widget-nach-vorbild-personal-da/) |
|
| 260916-htc | **Kalender-Widget nach Vorbild personal-dashboard.** Monatsraster (Zurueck/Monat/Weiter, Mo-So, 42 Zellen ab Montag, heute hervorgehoben, Zaehler-Plakette je Tag, Termine beim Ueberfahren als Tooltip per `createPortal`/`position: fixed`, weil die Kachel `overflow-hidden` ist) + Block "Naechste Termine" (Datum/Uhrzeit, Titel, Ort, Farbpunkt). Drei Einstellungen unter Einstellungen -> Dashboard -> Widgets (`CalendarConfig`, Muster ClockConfig): `showMonth` (Standard an), `maxEvents` 0..10 (Standard 3, 0 = ausblenden), `lookaheadDays` 7/14/30/60/90 (Standard 30). Ein `fetchEvents(from,to)`-Aufruf je Ladevorgang mit lokalen Tagesgrenzen (Backend-Cache-Schluessel bleibt stabil), Neuladen bei Monatswechsel, 5-Minuten-Intervall bleibt. Neues reines Modul `calendar-month.ts` (resolveCalendarConfig, buildCalendarDays, groupEventsByDate, computeFetchWindow, selectUpcomingEvents). Mindestgroesse calendar 6x8 (Registry-Test mitgezogen). Keine Quellenauswahl pro Widget (User-Entscheidung: nur Optik). 16 i18n-Schluessel de/en, Changelog "Geändert", Anwenderhandbuch. Tests Web 315 -> 332 / 51 Dateien, tsc 0, 12 Dateien. | 2026-09-16 | 0858102,61996dc,6d8c7c4 | [260916-htc-kalender-widget-nach-vorbild-personal-da](./quick/260916-htc-kalender-widget-nach-vorbild-personal-da/) |
|
||||||
| 260916-iex | **Dashboard-Widgets: Notiz-Haekchen, Favoriten-Titel, Link-Widget entfernt.** (1) Notiz-Widget: Aufgabenlisten (`- [ ]`/`- [x]`) in der Ansicht direkt abhakbar — `previewOptions.components.input` ersetzt das von rehypeSanitize erzwungene `disabled`-Kaestchen durch `NoteCheckbox` (greift NACH Sanitize, per Spike bestaetigt), delegierter Klick auf dem Vorschau-Container, Index = Reihenfolge der Kaestchen, `toggleTaskLine` kippt genau diese Zeile (strenge Regex, Code-Zaeune uebersprungen), Sofort-Speichern. (2) Favoriten-Widget: `config.title` optional — Kopfzeile im Notiz-Look nur bei Titel, im Bearbeitungsmodus Textfeld (`widgetNoDrag`, 1500 ms entprellt), `FavoritesConfig` + "— {title}" im Einstellungsfeld, NoteConfig-Beschriftung uebersetzt. (3) Link-Widget restlos entfernt: Registry/Union/Constraints/Katalog/page.tsx, `widgets.link` de/en, API-DTO, Migration `20260916120000_remove_link_widget` (`DELETE FROM "WidgetInstance" WHERE "widgetType" = 'link'`, FavoriteLink kaskadiert, Migrationsrolle tessera = Superuser/BYPASSRLS), `widget-wrapper.test.tsx` (unbekannter Typ -> grauer Text), Handbuch, Changelog. Tests Web 332 -> 344 / 52 Dateien, API dashboard 31 gruen, tsc Web+API 0. | 2026-09-16 | 684f063,7f1ee3b,39ea147 | [260916-iex-dashboard-widgets-notiz-haekchen-in-der-](./quick/260916-iex-dashboard-widgets-notiz-haekchen-in-der-/) |
|
| 260916-iex | **Dashboard-Widgets: Notiz-Haekchen, Favoriten-Titel, Link-Widget entfernt.** (1) Notiz-Widget: Aufgabenlisten (`- [ ]`/`- [x]`) in der Ansicht direkt abhakbar — `previewOptions.components.input` ersetzt das von rehypeSanitize erzwungene `disabled`-Kaestchen durch `NoteCheckbox` (greift NACH Sanitize, per Spike bestaetigt), delegierter Klick auf dem Vorschau-Container, Index = Reihenfolge der Kaestchen, `toggleTaskLine` kippt genau diese Zeile (strenge Regex, Code-Zaeune uebersprungen), Sofort-Speichern. (2) Favoriten-Widget: `config.title` optional — Kopfzeile im Notiz-Look nur bei Titel, im Bearbeitungsmodus Textfeld (`widgetNoDrag`, 1500 ms entprellt), `FavoritesConfig` + "— {title}" im Einstellungsfeld, NoteConfig-Beschriftung uebersetzt. (3) Link-Widget restlos entfernt: Registry/Union/Constraints/Katalog/page.tsx, `widgets.link` de/en, API-DTO, Migration `20260916120000_remove_link_widget` (`DELETE FROM "WidgetInstance" WHERE "widgetType" = 'link'`, FavoriteLink kaskadiert, Migrationsrolle tessera = Superuser/BYPASSRLS), `widget-wrapper.test.tsx` (unbekannter Typ -> grauer Text), Handbuch, Changelog. Tests Web 332 -> 344 / 52 Dateien, API dashboard 31 gruen, tsc Web+API 0. | 2026-09-16 | 684f063,7f1ee3b,39ea147 | [260916-iex-dashboard-widgets-notiz-haekchen-in-der-](./quick/260916-iex-dashboard-widgets-notiz-haekchen-in-der-/) |
|
||||||
| 260916-j4f | **Nachtraege nach Browser-Pruefung.** Kalender-Tooltip bricht lange Termintitel um (`break-words` + `min-w-0`, Breite/Klemmung aus `TOOLTIP_WIDTH_PX = 288`); Notiz-Widget nimmt `data-color-mode` aus `useTheme().resolvedTheme` mit mounted-Guard (Muster changelog-view) statt `auto` — Textbereich blieb bei OS-dunkel/Tessera-hell dunkel (User-Meldung); CHANGELOG.md auf kurze Stichpunkte gestrafft (User: "Kein Fliesstext"), alle drei Abschnitte, Ueberschriften unveraendert, `publish-release.sh --dry-run --tag v1.1.0` Exit 0. Tests Web 344 -> 347 / 52 Dateien, tsc 0. | 2026-09-16 | b16e4b8,4c2495b,a6bb7aa | [260916-j4f-nachtraege-kalender-tooltip-umbrechen-no](./quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/) |
|
| 260916-j4f | **Nachtraege nach Browser-Pruefung.** Kalender-Tooltip bricht lange Termintitel um (`break-words` + `min-w-0`, Breite/Klemmung aus `TOOLTIP_WIDTH_PX = 288`); Notiz-Widget nimmt `data-color-mode` aus `useTheme().resolvedTheme` mit mounted-Guard (Muster changelog-view) statt `auto` — Textbereich blieb bei OS-dunkel/Tessera-hell dunkel (User-Meldung); CHANGELOG.md auf kurze Stichpunkte gestrafft (User: "Kein Fliesstext"), alle drei Abschnitte, Ueberschriften unveraendert, `publish-release.sh --dry-run --tag v1.1.0` Exit 0. Tests Web 344 -> 347 / 52 Dateien, tsc 0. | 2026-09-16 | b16e4b8,4c2495b,a6bb7aa | [260916-j4f-nachtraege-kalender-tooltip-umbrechen-no](./quick/260916-j4f-nachtraege-kalender-tooltip-umbrechen-no/) |
|
||||||
|
| 260916-jvj | **Kalender-Plaketten in Kalenderfarbe + Aufzaehlungspunkte in Markdown-Ansichten.** Tages-Plakette nimmt `day.events[0].color` (groupEventsByDate sortiert jetzt je Tag nach Start) als Inline-Hintergrund mit weisser Schrift, ohne Farbe unveraendert `bg-primary`; Farbpunkt je Tooltip-Zeile. Tailwind-v4-Preflight entfernt `list-style` global, markdown.css setzt es nicht zurueck -> Vier-Regeln-Block am Ende von `globals.css` (`.wmde-markdown ul/ol`, Abhak-Listen bleiben ohne Punkt) fuer "Was ist neu" und Notiz-Ansicht. Changelog. Tests Web 347 -> 350 / 52 Dateien, tsc 0. | 2026-09-16 | 1e4ec30,c85cf9a | [260916-jvj-kalender-plaketten-in-kalenderfarbe-stat](./quick/260916-jvj-kalender-plaketten-in-kalenderfarbe-stat/) |
|
||||||
|
| 260916-k2z | **Kalender-Widget: mehrere Kalender am selben Tag als kleine Kreise.** Neue reine Helfer `groupDayBySource` (nach `sourceId`, Reihenfolge des ersten Auftretens, Farbe = erster Termin der Gruppe) und `buildDayBadges(events, max=3)`: 1 Quelle = bisherige Einzelplakette (unveraendert, Tests 3/3b/3c gruen), 2-3 Quellen = kleine Kreise je Farbe mit eigener Anzahl, >3 = zwei Kreise + grauer Restkreis (`bg-muted-foreground text-background`, Summe; testid `calendar-day-count-rest`), Wrapper `calendar-day-badges`. Changelog-Stichpunkt erweitert. Tests Web 350 -> 354 / 52 Dateien, tsc 0. | 2026-09-16 | 4ddadc6,7429c5b | [260916-k2z-kalender-widget-mehrere-kalender-am-selb](./quick/260916-k2z-kalender-widget-mehrere-kalender-am-selb/) |
|
||||||
|
|
||||||
## Deferred Items
|
## Deferred Items
|
||||||
|
|
||||||
@@ -459,4 +461,4 @@ Last session: 2026-09-16T10:15:09.000Z
|
|||||||
Resumed: 2026-09-14 — Sitzung ueber /gsd-resume-work fortgesetzt; #29 und 3c als /gsd-quick --validate mit voller Kette durchgefuehrt.
|
Resumed: 2026-09-14 — Sitzung ueber /gsd-resume-work fortgesetzt; #29 und 3c als /gsd-quick --validate mit voller Kette durchgefuehrt.
|
||||||
Stopped at: **VERSION 1.1.0 FREIGEGEBEN — 2026-09-16.** CHANGELOG `Unveröffentlicht` -> `1.1.0 – 2026-09-16` (e0d4532), `live` per ff-merge auf e0d4532, Tag `v1.1.0` gepusht; CI (Beta, live-Pruefung, Tag) gruen; Registry traegt `live`/`v1.1.0` (Stempel `v1.1.0 live e0d4532`); **erster echter CI-Beweis des Release-Wegs: Gitea-Release "Tessera 1.1.0" von der Pipeline angelegt** (Body 2146 Zeichen). Der User spielt 1.1.0 auf tessera.ctl.de ein (pull + up -d --force-recreate api web). Inhalt: Dashboard-Umbau + Nachbesserung, Seite "Was ist neu", Uebersetzungs-Fix. Offen ohne Dringlichkeit: Desktop-Client-Todo, Ship Phase 17, Ledger #35/#36/#37. Mandantenfaehigkeit RUHT. Schalter AUS. Einstieg: `/gsd-resume-work`.
|
Stopped at: **VERSION 1.1.0 FREIGEGEBEN — 2026-09-16.** CHANGELOG `Unveröffentlicht` -> `1.1.0 – 2026-09-16` (e0d4532), `live` per ff-merge auf e0d4532, Tag `v1.1.0` gepusht; CI (Beta, live-Pruefung, Tag) gruen; Registry traegt `live`/`v1.1.0` (Stempel `v1.1.0 live e0d4532`); **erster echter CI-Beweis des Release-Wegs: Gitea-Release "Tessera 1.1.0" von der Pipeline angelegt** (Body 2146 Zeichen). Der User spielt 1.1.0 auf tessera.ctl.de ein (pull + up -d --force-recreate api web). Inhalt: Dashboard-Umbau + Nachbesserung, Seite "Was ist neu", Uebersetzungs-Fix. Offen ohne Dringlichkeit: Desktop-Client-Todo, Ship Phase 17, Ledger #35/#36/#37. Mandantenfaehigkeit RUHT. Schalter AUS. Einstieg: `/gsd-resume-work`.
|
||||||
Resume file: .planning/HANDOFF.json + .planning/.continue-here.md (2026-09-16T10:28Z)
|
Resume file: .planning/HANDOFF.json + .planning/.continue-here.md (2026-09-16T10:28Z)
|
||||||
Last activity: 2026-09-16 - Completed quick task 260916-j4f: Nachtraege (Tooltip-Umbruch, Notiz-Farbmodus, Changelog-Stichpunkte); Browser-Pruefung + Push ausstehend
|
Last activity: 2026-09-16 - Completed quick task 260916-k2z: Mehrfach-Kreise je Kalender am Tag; Browser-Pruefung + Push ausstehend
|
||||||
|
|||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260916-jvj
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QUICK-260916-JVJ]
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
- apps/web/src/app/globals.css
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
estimate:
|
||||||
|
tokens: 25000
|
||||||
|
raw_tokens: 25000
|
||||||
|
tasks: 2
|
||||||
|
confidence: low
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Kalender-Widget, Monatsraster: die Zähl-Plakette eines Tages trägt als Hintergrund die Farbe des Kalenders (`event.color`) des FRÜHESTEN Termins dieses Tages (Termine je Tag nach Start sortiert) mit weißer Schrift; hat dieser Termin keine Farbe, sieht die Plakette aus wie bisher (`bg-primary text-primary-foreground`, kein Inline-Stil). Bei mehreren Quellen an einem Tag zählt allein der früheste Termin (bewusst einfach gehalten, in der SUMMARY vermerken)."
|
||||||
|
- "Kalender-Widget, Tooltip beim Überfahren: jede Terminzeile beginnt mit einem kleinen Farbpunkt (`h-2 w-2 rounded-full shrink-0`) in `event.color`, Rückfall `var(--muted-foreground)` — dieselbe Regel wie der Punkt in „Nächste Termine“; die Zeilen stehen in Startzeit-Reihenfolge."
|
||||||
|
- "Seite „Was ist neu“ (/changelog) und Notiz-Widget-Vorschau: Aufzählungslisten zeigen wieder Punkte (disc, verschachtelt circle), nummerierte Listen Ziffern; Aufgabenlisten mit Kästchen (`- [ ]`) bleiben ohne Punkt."
|
||||||
|
- "CHANGELOG.md, Abschnitt `## Unveröffentlicht`: unter `### Geändert` steht „Kalender-Widget: Plakette am Tag in der Farbe des Kalenders“, unter `### Behoben` steht „„Was ist neu“ und Notiz-Ansicht: Aufzählungspunkte wieder sichtbar“ — kurze Stichpunkte ohne Punkt am Ende, echte Umlaute."
|
||||||
|
- "Gates: `pnpm --filter @tessera/web type-check` Exit 0; `pnpm --filter @tessera/web exec vitest run` komplett grün (Basislinie 52 Dateien / 347 Tests → danach 52 Dateien / 350 Tests: +2 calendar-widget, +1 calendar-month); Umlaut-Wächter 3/3; changelog.test.ts grün. KEIN `biome check` (biome.json nicht anfassen), kein Docker-Build, kein Deploy, kein Testserver, kein `git push`."
|
||||||
|
artifacts:
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-month.ts — `groupEventsByDate` sortiert jede Tagesgruppe nach `start` aufsteigend (stabil)"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-month.test.ts — neuer Test 2b (unsortierte Eingabe → Tagesgruppe sortiert)"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-widget.tsx — Plakette mit `style={{ backgroundColor }}` + `text-white` bei Farbe, sonst `bg-primary text-primary-foreground`; Tooltip-Zeile mit `data-testid=\"tooltip-color-dot\"`"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx — neue Tests 3b (Farbe des frühesten Termins, Tooltip-Punkte) und 3c (ohne Farbe bleibt bg-primary)"
|
||||||
|
- "apps/web/src/app/globals.css — Block `.wmde-markdown`-Listen am Dateiende mit deutschem Kommentar (quick-260916-jvj)"
|
||||||
|
- "CHANGELOG.md — zwei neue Stichpunkte unter Unveröffentlicht"
|
||||||
|
key_links:
|
||||||
|
- "`groupEventsByDate` (calendar-month.ts Z. 106-114) übernimmt heute die API-Reihenfolge unsortiert — die Regel „Farbe des ERSTEN Termins“ ist nur dann deterministisch, wenn die Gruppe nach Start sortiert ist. Sortierung gehört in `groupEventsByDate` (eine Stelle), dann stimmen Plakette UND Tooltip-Reihenfolge überein."
|
||||||
|
- "Tailwind v4 Preflight liegt in `@layer base` und setzt `ul, ol { list-style: none }`; markdown.css (`@uiw/react-markdown-preview` 5.2.1, Z. 477-481) setzt für `.wmde-markdown ul/ol` nur `padding-left: 2em`, KEIN `list-style`. Ungeschichtetes CSS in globals.css schlägt jede `@layer`-Regel unabhängig von Spezifität — deshalb reicht ein normaler Block nach dem `@import`, globals.css hat keine eigene `@layer`-Struktur (gemessen)."
|
||||||
|
- "Aufgabenlisten: remark-gfm setzt `contains-task-list` auf das `ul` und `task-list-item` auf das `li`; markdown.css Z. 878 `.wmde-markdown .task-list-item { list-style-type: none }` (Spezifität 0,2,0) schlägt `.wmde-markdown ul` (0,1,1) bereits — die zusätzliche Regel `.wmde-markdown ul.contains-task-list, .wmde-markdown li.task-list-item { list-style: none }` (0,2,1) macht das unabhängig von der Ladereihenfolge der beiden Stylesheets."
|
||||||
|
- "`toHaveStyle({ backgroundColor: '#c44040' })` normalisiert hex→rgb auf beiden Seiten (jest-dom); Muster im Bestand: Test 4c `toHaveStyle({ width: '288px' })`. `var(--muted-foreground)` NICHT per toHaveStyle prüfen (jsdom löst keine Custom Properties auf) — Rückfall nur über Vorhandensein des Punkts prüfen, wie Test 5 es schon tut."
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Zwei Nachträge nach der Browser-Prüfung der heutigen Dashboard-Arbeiten (260916-htc/iex/j4f): (1) Die Zähl-Plakette an einem Tag im Monatsraster des Kalender-Widgets ist immer gelb (Akzentfarbe) — sie soll die Farbe des Kalenders tragen, aus dem der Termin stammt, so wie es der Farbpunkt in „Nächste Termine“ schon tut; damit gemischte Tage lesbar bleiben, bekommt zusätzlich jede Tooltip-Zeile denselben Farbpunkt. (2) Auf „Was ist neu“ und in der Notiz-Vorschau fehlen die Aufzählungspunkte, weil Tailwinds Grundstil `list-style` entfernt und das Markdown-Stylesheet es nicht wiederherstellt — ein kleiner CSS-Block in globals.css behebt das, Aufgabenlisten mit Kästchen bleiben ohne Punkt.
|
||||||
|
|
||||||
|
Purpose: Sichtbare Bedienfehler vor der nächsten Beta beseitigen; Kalenderfarben im Widget durchgängig nutzen.
|
||||||
|
Output: Sortierung in calendar-month.ts, Plakette/Tooltip-Punkt in calendar-widget.tsx, CSS-Block in globals.css, drei neue Tests, zwei CHANGELOG-Stichpunkte, zwei Commits.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.claude/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@/home/vicolab/projects/tessera-ctl/CLAUDE.md
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/app/globals.css
|
||||||
|
@/home/vicolab/projects/tessera-ctl/CHANGELOG.md
|
||||||
|
|
||||||
|
Live gemessen am 2026-09-16 (Planer):
|
||||||
|
- calendar-month.ts: `groupEventsByDate` Z. 106-114 sammelt per `[...existingEvents, event]` in API-Reihenfolge, KEINE Sortierung (Vorgabe „bereits sortiert“ trifft nicht zu). `selectUpcomingEvents` Z. 179-186 zeigt den Sortier-Komparator, der zu übernehmen ist: `new Date(a.start).getTime() - new Date(b.start).getTime()`. Kopfkommentar Z. 14-17 beschreibt die Starttag-Regel.
|
||||||
|
- calendar-month.test.ts: Test 2 Z. 50-64 nutzt einen `ev(id, start, end)`-Helfer und `grouped.get('2026-07-20')`; dort Test 2b anhängen.
|
||||||
|
- calendar-widget.tsx: Plakette Z. 244-251 (`data-testid="calendar-day-count"`, Klassen enthalten `rounded-full bg-primary px-0.5 ... text-primary-foreground`); Listen-Farbpunkt Z. 272-277 (`data-testid="event-color-dot"`, `className="mt-1 h-2 w-2 shrink-0 rounded-full"`, `style={{ backgroundColor: event.color || 'var(--muted-foreground)' }}`, `aria-hidden="true"`); Tooltip-Zeilen Z. 314-321 (`<div key={event.id} className="flex gap-2">`, dann Uhrzeit-Span `shrink-0 tabular-nums text-muted-foreground`, dann Titel-Span `min-w-0 break-words`). `cellClass` Z. 220-227 zeigt das Muster Array → `.filter(Boolean).join(' ')` für zusammengesetzte Klassen. Doc-Kommentar Z. 27-55 erwähnt „Zaehl-Plakette“ (Z. 33).
|
||||||
|
- calendar-widget.test.tsx: 10 Tests; `ev(id, start, end, extra?)` Z. 44-54 nimmt `Partial<CalendarEvent>` (also `{ color: '#c44040' }`); `within`, `fireEvent` importiert; Test 3 Z. 121-141 (Plakette zählt), Test 4 Z. 143-166 (Tooltip per `fireEvent.mouseEnter` auf `[data-date="2026-07-20"]`, danach `screen.getByTestId('calendar-day-tooltip')`). Termintitel stehen auch in „Nächste Termine“ im DOM — Tooltip-Abfragen IMMER mit `within(tooltip)`.
|
||||||
|
- CalendarEvent (apps/web/src/lib/calendar-api.ts Z. 41-51): `color?: string`.
|
||||||
|
- SOURCE_COLOR_PALETTE (calendar-source-form.tsx Z. 12-21): #c44040, #40a060, #4060c4, #8040c4, #c49040, #409090, #c44080, #808080 — alle mittlere Töne, weiße Schrift lesbar.
|
||||||
|
- globals.css: 127 Zeilen, `@import "tailwindcss"` Z. 1, `@custom-variant dark` Z. 22, `@theme inline` Z. 24-50, Tokens, `body` Z. 115-119, `.app-shell-main`-Media-Block Z. 121-126 (Dateiende). Kein `@layer`, kein `.wmde-markdown`.
|
||||||
|
- markdown.css (node_modules/.pnpm/@uiw+react-markdown-preview@5.2.1_*/node_modules/@uiw/react-markdown-preview/markdown.css): Z. 477-481 `.wmde-markdown ul, .wmde-markdown ol { margin 0; padding-left: 2em }` ohne list-style; Z. 483-485 `ol ol, ul ol → lower-roman`; Z. 636-638 `.wmde-markdown div > ol:not([type]) → decimal` (nur ol, ul hat nichts); Z. 878-880 `.wmde-markdown .task-list-item { list-style-type: none }`; Z. 893-895 `.wmde-markdown .contains-task-list input[type='checkbox']` (Klassennamen bestätigt: `contains-task-list` am ul, `task-list-item` am li).
|
||||||
|
- CHANGELOG.md `## Unveröffentlicht`: `### Geändert` hat einen Punkt (Kalenderquellen Adressfeld), `### Behoben` hat zwei (Notiz-Widget Listen abhaken, Textbereich Hell/Dunkel). Neue Punkte jeweils als letzte Zeile des Abschnitts anhängen.
|
||||||
|
- Testbasis: `pnpm --filter @tessera/web exec vitest run` = 52 Dateien / 347 Tests; Skripte `test`/`type-check` in apps/web/package.json vorhanden.
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Kalender-Plakette in Kalenderfarbe, Farbpunkt im Tooltip, Sortierung je Tag + Tests</name>
|
||||||
|
<files>apps/web/src/components/dashboard/widgets/calendar-month.ts, apps/web/src/components/dashboard/widgets/calendar-month.test.ts, apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- calendar-month.test.ts Test 2b: `groupEventsByDate` mit e2 (20.07. 14:00) VOR e1 (20.07. 09:00) in der Eingabe → `grouped.get('2026-07-20')?.map((e) => e.id)` ist `['e1', 'e2']` (Sortierung nach Start; Test 2 bleibt unverändert grün).
|
||||||
|
- calendar-widget.test.tsx Test 3b „Plakette traegt die Kalenderfarbe des fruehesten Termins, Tooltip-Zeilen mit Farbpunkt“: fetchEvents liefert `ev('Lunch', 20.07. 14:00-15:00, { color: '#4060c4' })` ZUERST und `ev('Team Meeting', 20.07. 09:00-10:00, { color: '#c44040' })` danach. Plakette `within(day20).getByTestId('calendar-day-count')`: `toHaveTextContent('2')`, `toHaveStyle({ backgroundColor: '#c44040' })`, `toHaveClass('text-white')`, `not.toHaveClass('bg-primary')`. Dann `fireEvent.mouseEnter(day20)`; im Tooltip `within(tooltip).getAllByTestId('tooltip-color-dot')` hat Länge 2, Punkt [0] `toHaveStyle({ backgroundColor: '#c44040' })`, Punkt [1] `toHaveStyle({ backgroundColor: '#4060c4' })`; `tooltip.textContent.indexOf('Team Meeting')` ist kleiner als `indexOf('Lunch')` (Reihenfolge nach Startzeit).
|
||||||
|
- calendar-widget.test.tsx Test 3c „Plakette ohne Kalenderfarbe behaelt bg-primary“: ein Termin am 21.07. ohne `color`. Plakette `toHaveClass('bg-primary')`, `toHaveClass('text-primary-foreground')`, `not.toHaveClass('text-white')`, `badge.style.backgroundColor` ist `''`. Nach `mouseEnter`: genau ein `tooltip-color-dot` vorhanden (Rückfallfarbe `var(--muted-foreground)` NICHT per toHaveStyle prüfen — jsdom löst Custom Properties nicht auf).
|
||||||
|
- Sollte `toHaveStyle({ backgroundColor: '#c44040' })` in jsdom wider Erwarten nicht greifen, ersatzweise `expect(badge.style.backgroundColor).toBe('rgb(196, 64, 64)')` (jsdom normalisiert hex zu rgb) — Erwartung ändern, nicht die Implementierung.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
Reihenfolge RED → GREEN: erst die drei Tests aus `<behavior>` schreiben und laufen lassen (müssen fehlschlagen), dann implementieren.
|
||||||
|
|
||||||
|
1. calendar-month.ts, `groupEventsByDate` (Z. 106-114): nach dem Sammeln jede Tagesgruppe nach Start aufsteigend sortieren — Komparator wie in `selectUpcomingEvents` (`new Date(a.start).getTime() - new Date(b.start).getTime()`); `Array.prototype.sort` ist stabil, Termine mit gleichem Start behalten die API-Reihenfolge. Doc-Kommentar der Funktion und Kopfkommentar (Starttag-Regel Z. 14-17) um einen Satz ergänzen: Gruppen sind nach Start sortiert, damit Plakettenfarbe (erster Termin) und Tooltip-Reihenfolge deterministisch sind (quick-260916-jvj).
|
||||||
|
|
||||||
|
2. calendar-widget.tsx, Plakette (Z. 244-251): vor dem `return` der Zelle `const badgeColor = day.events[0]?.color;` bestimmen (Gruppe ist jetzt sortiert, [0] = frühester Termin). Klassenstring nach dem `cellClass`-Muster zusammensetzen: unveränderter Basisteil (`absolute bottom-px right-px flex h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] items-center justify-center rounded-full px-0.5 text-[clamp(7px,1.8cqw,10px)] font-semibold leading-none`) plus bei `badgeColor` `text-white`, sonst `bg-primary text-primary-foreground`. `style={badgeColor ? { backgroundColor: badgeColor } : undefined}` — ohne Farbe darf KEIN style-Attribut entstehen (Test 3c prüft `''`). `data-testid` bleibt `calendar-day-count`.
|
||||||
|
|
||||||
|
3. calendar-widget.tsx, Tooltip-Zeile (Z. 314-321): als erstes Kind der `flex gap-2`-Zeile einen Span einfügen mit `data-testid="tooltip-color-dot"`, `className="mt-1 h-2 w-2 shrink-0 rounded-full"`, `style={{ backgroundColor: event.color || 'var(--muted-foreground)' }}`, `aria-hidden="true"` — exakt dieselbe Rückfallregel wie der Listen-Punkt Z. 272-277 (`mt-1` zentriert den 8-px-Punkt in der 16-px-Zeile von `text-xs`). Uhrzeit- und Titel-Span unverändert dahinter.
|
||||||
|
|
||||||
|
4. Doc-Kommentar calendar-widget.tsx Z. 33 („Zaehl-Plakette an Tagen mit Terminen“) ergänzen: Plakette in der Farbe des Kalenders des fruehesten Termins, sonst Akzentfarbe; Tooltip-Zeilen mit Farbpunkt (quick-260916-jvj). ASCII-Umlaute wie im umgebenden Kommentar (ae/oe/ue), das ist dort Konvention.
|
||||||
|
|
||||||
|
Keine weiteren Änderungen: Liste „Nächste Termine“, Portal, Klemmung, Ladefenster bleiben unangetastet. Commit: `feat(web): Kalender-Plakette in der Farbe des Kalenders, Farbpunkt je Tooltip-Zeile`.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && grep -q "tooltip-color-dot" apps/web/src/components/dashboard/widgets/calendar-widget.tsx && grep -q "day.events\[0\]?.color" apps/web/src/components/dashboard/widgets/calendar-widget.tsx && grep -q "Test 3b" apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx && grep -q "Test 3c" apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx && grep -q "Test 2b" apps/web/src/components/dashboard/widgets/calendar-month.test.ts && pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/calendar-month.test.ts src/components/dashboard/widgets/calendar-widget.test.tsx && pnpm --filter @tessera/web type-check</automated>
|
||||||
|
</verify>
|
||||||
|
<done>calendar-month.test.ts und calendar-widget.test.tsx komplett grün (10 → 12 Widget-Tests, +1 Month-Test); Plakette bekommt bei `color` Inline-Hintergrund + `text-white`, ohne `color` unverändert `bg-primary text-primary-foreground` ohne style-Attribut; Tooltip-Zeilen mit Farbpunkt in Startzeit-Reihenfolge; tsc 0 Fehler.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Aufzählungspunkte in Markdown-Ansichten (globals.css), CHANGELOG, voller Testlauf</name>
|
||||||
|
<files>apps/web/src/app/globals.css, CHANGELOG.md</files>
|
||||||
|
<action>
|
||||||
|
1. globals.css: ans Dateiende (nach dem `.app-shell-main`-Media-Block Z. 121-126) einen Block anhängen, eingeleitet von einem kurzen deutschen Kommentar (Muster der bestehenden Kommentare, ASCII-Umlaute wie dort): Tailwind-Preflight setzt `ul, ol { list-style: none }` in `@layer base`, markdown.css von @uiw/react-markdown-preview stellt es nicht wieder her — deshalb fehlten auf „Was ist neu“ und in der Notiz-Vorschau die Punkte; ungeschichtete Regel hier schlägt die Layer-Regel; Aufgabenlisten bleiben ohne Punkt (quick-260916-jvj). Danach genau diese vier Regeln, je eine Zeile: `.wmde-markdown ul { list-style: disc; }` — `.wmde-markdown ul ul { list-style: circle; }` — `.wmde-markdown ol { list-style: decimal; }` — `.wmde-markdown ul.contains-task-list, .wmde-markdown li.task-list-item { list-style: none; }`. Kein `@layer`, kein `!important`, keine weiteren Selektoren. Klassennamen `contains-task-list`/`task-list-item` sind gegen markdown.css Z. 878/894 bestätigt.
|
||||||
|
|
||||||
|
2. CHANGELOG.md, Abschnitt `## Unveröffentlicht`: unter `### Geändert` als letzte Zeile `- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders` anhängen; unter `### Behoben` als letzte Zeile `- „Was ist neu“ und Notiz-Ansicht: Aufzählungspunkte wieder sichtbar` anhängen. Typografische Anführungszeichen „…“ wie im Bestand, kein Punkt am Zeilenende, Überschriften zeichengenau unverändert, kein CRLF.
|
||||||
|
|
||||||
|
3. Volle Gates laufen lassen (siehe verify). Kein `biome check` (bekannter Konfigurationsfehler, biome.json nicht anfassen), kein Docker-Build, kein Deploy, kein Testserver, kein `git push`. Commit: `fix(web): Aufzählungspunkte in Markdown-Ansichten (Was ist neu, Notiz) wieder sichtbar; Changelog`.
|
||||||
|
|
||||||
|
In der SUMMARY vermerken: Plakettenfarbe = frühester Termin des Tages (bei mehreren Quellen an einem Tag keine Mischung, bewusst einfach); `groupEventsByDate` sortiert jetzt (war vorher API-Reihenfolge); CSS-Block ist bewusst ungeschichtet, weil Preflight in `@layer base` liegt.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && grep -q '^\.wmde-markdown ul { list-style: disc; }' apps/web/src/app/globals.css && grep -q '^\.wmde-markdown ul ul { list-style: circle; }' apps/web/src/app/globals.css && grep -q '^\.wmde-markdown ol { list-style: decimal; }' apps/web/src/app/globals.css && grep -q '^\.wmde-markdown ul\.contains-task-list, \.wmde-markdown li\.task-list-item { list-style: none; }' apps/web/src/app/globals.css && grep -q 'quick-260916-jvj' apps/web/src/app/globals.css && grep -qx -- '- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders' CHANGELOG.md && grep -qx -- '- „Was ist neu“ und Notiz-Ansicht: Aufzählungspunkte wieder sichtbar' CHANGELOG.md && grep -qx '## Unveröffentlicht' CHANGELOG.md && ! grep -q $'\r' CHANGELOG.md && pnpm --filter @tessera/web type-check && pnpm --filter @tessera/web exec vitest run</automated>
|
||||||
|
</verify>
|
||||||
|
<done>globals.css enthält die vier Listen-Regeln mit Kommentar am Dateiende; CHANGELOG.md hat beide neuen Stichpunkte in den richtigen Abschnitten; `type-check` Exit 0; `vitest run` komplett grün mit 52 Dateien / 350 Tests (Umlaut-Wächter 3/3, changelog.test.ts grün eingeschlossen).</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| API → Browser (event.color) | Farbwert stammt aus der gespeicherten Kalenderquelle (Formular-Palette, im Backend als String abgelegt) und landet als Inline-`backgroundColor` im DOM |
|
||||||
|
| Markdown → DOM | Bereits durch `rehypeSanitize` abgedeckt (260916-dcz/iex); dieser Auftrag ändert nur CSS, keine Sanitizer-Konfiguration |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||||
|
| T-JVJ-01 | Tampering | calendar-widget.tsx Inline-Stil aus `event.color` | low | accept | React setzt `style.backgroundColor` als Eigenschaftswert (kein HTML, kein `url()`-Kontext); ungültige Werte verwirft der Browser. Identische Nutzung besteht bereits beim Listen-Farbpunkt (Z. 275). Keine neue Angriffsfläche. |
|
||||||
|
| T-JVJ-02 | Information Disclosure | globals.css `.wmde-markdown`-Regeln | low | accept | Reines Styling ohne Datenfluss; Selektoren wirken nur innerhalb des Markdown-Containers. |
|
||||||
|
| T-JVJ-SC | Tampering | npm/pnpm installs | low | accept | Keine Paketinstallation in diesem Auftrag (nur Quell- und CSS-Änderungen). |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `pnpm --filter @tessera/web type-check` → Exit 0
|
||||||
|
- `pnpm --filter @tessera/web exec vitest run` → 52 Dateien / 350 Tests grün (Basis 347: +2 calendar-widget, +1 calendar-month); darin Umlaut-Wächter 3/3 und changelog.test.ts
|
||||||
|
- grep-Gates aus beiden Tasks (Plakette `day.events[0]?.color`, `tooltip-color-dot`, vier CSS-Regeln, zwei CHANGELOG-Zeilen, kein CRLF)
|
||||||
|
- Kein biome, kein Docker, kein Deploy, kein Push
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Plakette im Monatsraster zeigt die Kalenderfarbe des frühesten Termins des Tages (weiße Schrift), ohne Farbe unverändert Akzentfarbe
|
||||||
|
- Tooltip-Zeilen mit Farbpunkt (gleiche Rückfallregel wie die Liste), Reihenfolge nach Startzeit
|
||||||
|
- „Was ist neu“ und Notiz-Vorschau zeigen Aufzählungspunkte; Aufgabenlisten mit Kästchen bleiben ohne Punkt
|
||||||
|
- CHANGELOG.md um zwei kurze Stichpunkte ergänzt
|
||||||
|
- Alle Gates grün, zwei Commits, kein Push
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/quick/260916-jvj-kalender-plaketten-in-kalenderfarbe-stat/260916-jvj-SUMMARY.md` when done
|
||||||
|
</output>
|
||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260916-jvj
|
||||||
|
plan: 01
|
||||||
|
subsystem: ui
|
||||||
|
tags: [react, tailwind, vitest, calendar, markdown]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: quick-260916-htc
|
||||||
|
provides: Kalender-Widget Monatsraster mit Zähl-Plakette und Farbpunkt in „Nächste Termine“
|
||||||
|
- phase: quick-260916-j4f
|
||||||
|
provides: Termin-Tooltip per createPortal (Breite/Klemmung aus TOOLTIP_WIDTH_PX/TOOLTIP_EDGE_PX)
|
||||||
|
provides:
|
||||||
|
- Kalender-Plakette im Monatsraster trägt die Kalenderfarbe des frühesten Termins des Tages
|
||||||
|
- Tooltip-Zeilen mit Farbpunkt in derselben Reihenfolge/Rückfallregel wie „Nächste Termine“
|
||||||
|
- groupEventsByDate sortiert jede Tagesgruppe deterministisch nach Startzeit
|
||||||
|
- Aufzählungspunkte in .wmde-markdown-Ansichten (Was ist neu, Notiz-Vorschau) wieder sichtbar
|
||||||
|
affects: [dashboard-calendar, changelog-page, note-widget]
|
||||||
|
|
||||||
|
actuals:
|
||||||
|
tokens: 2855
|
||||||
|
tasks: 2
|
||||||
|
commits: 2
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Klassenstring-Array + .filter(Boolean).join(' ') für bedingte Tailwind-Klassen (bereits durch cellClass etabliert, jetzt auch für die Plakette)"
|
||||||
|
- "Ungeschichtetes CSS in globals.css zum gezielten Überschreiben von Tailwind-Preflight-Regeln aus @layer base"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
- apps/web/src/app/globals.css
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Plakettenfarbe = Farbe des frühesten Termins des Tages (Gruppe sortiert nach Start); bei mehreren Kalenderquellen an einem Tag keine Mischfarbe — bewusst einfach gehalten, vom Plan so vorgegeben"
|
||||||
|
- "groupEventsByDate sortiert jetzt selbst (vorher API-Reihenfolge unsortiert); Sortierung an einer Stelle hält Plakettenfarbe und Tooltip-Reihenfolge konsistent"
|
||||||
|
- "CSS-Block für .wmde-markdown-Listen bewusst ohne @layer, weil Preflight in @layer base liegt und ungeschichtetes CSS jede @layer-Regel unabhängig von Spezifität schlägt"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Bedingte Badge-Farbe: style nur setzen wenn event.color vorhanden, sonst kein style-Attribut (Testbarkeit über style.backgroundColor === '')"
|
||||||
|
|
||||||
|
requirements-completed: [QUICK-260916-JVJ]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "Kalender-Plakette im Monatsraster zeigt die Kalenderfarbe des frühesten Termins (weiße Schrift), ohne Farbe unverändert bg-primary"
|
||||||
|
requirement: "QUICK-260916-JVJ"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3b: Plakette traegt die Kalenderfarbe des fruehesten Termins, Tooltip-Zeilen mit Farbpunkt"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3c: Plakette ohne Kalenderfarbe behaelt bg-primary"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "Tooltip-Zeilen zeigen einen Farbpunkt je Termin (gleiche Rückfallregel wie die Liste), Reihenfolge nach Startzeit"
|
||||||
|
requirement: "QUICK-260916-JVJ"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3b: Plakette traegt die Kalenderfarbe des fruehesten Termins, Tooltip-Zeilen mit Farbpunkt"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "groupEventsByDate sortiert jede Tagesgruppe nach Startzeit aufsteigend"
|
||||||
|
requirement: "QUICK-260916-JVJ"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-month.test.ts#Test 2b: groupEventsByDate sortiert jede Tagesgruppe nach Start aufsteigend"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "Aufzählungspunkte in .wmde-markdown-Ansichten (Was ist neu, Notiz-Vorschau) wieder sichtbar, Aufgabenlisten mit Kästchen bleiben ohne Punkt"
|
||||||
|
requirement: "QUICK-260916-JVJ"
|
||||||
|
verification: []
|
||||||
|
human_judgment: true
|
||||||
|
rationale: "CSS-Regeln sind über grep-Gates auf Vorhandensein geprüft, aber die visuelle Wirkung (Punkte sichtbar, Aufgabenlisten ohne Punkt) hat kein automatisiertes Browser-Rendering-Assert in diesem Auftrag — erfordert einen kurzen Blick in den Browser."
|
||||||
|
- id: D5
|
||||||
|
description: "CHANGELOG.md um zwei Stichpunkte ergänzt (Geändert: Plakette, Behoben: Aufzählungspunkte)"
|
||||||
|
requirement: "QUICK-260916-JVJ"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "grep -qx -- '- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders' CHANGELOG.md && grep -qx -- '- „Was ist neu“ und Notiz-Ansicht: Aufzählungspunkte wieder sichtbar' CHANGELOG.md"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
|
||||||
|
duration: 3min
|
||||||
|
completed: 2026-09-16
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick 260916-jvj: Kalender-Plaketten in Kalenderfarbe, Aufzählungspunkte in Markdown-Ansichten Summary
|
||||||
|
|
||||||
|
**Kalender-Widget: Tages-Plakette und Tooltip-Zeilen tragen jetzt die Kalenderfarbe des frühesten Termins; Markdown-Listen (Was ist neu, Notiz-Vorschau) zeigen wieder Aufzählungspunkte**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 3 min (14:23:30 – 14:26:12)
|
||||||
|
- **Tasks:** 2/2
|
||||||
|
- **Files modified:** 6
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- Zähl-Plakette im Monatsraster des Kalender-Widgets trägt die Farbe des Kalenders des frühesten Termins des Tages (weiße Schrift), ohne Farbe unverändert `bg-primary text-primary-foreground`
|
||||||
|
- Tooltip-Zeilen beim Überfahren eines Tages zeigen denselben Farbpunkt wie die Liste „Nächste Termine“ (Rückfall `var(--muted-foreground)`), in Startzeit-Reihenfolge
|
||||||
|
- `groupEventsByDate` sortiert jede Tagesgruppe jetzt selbst nach Start aufsteigend (vorher API-Reihenfolge unsortiert) — eine Stelle, an der Plakettenfarbe und Tooltip-Reihenfolge konsistent bleiben
|
||||||
|
- Aufzählungspunkte auf „Was ist neu“ und in der Notiz-Vorschau wieder sichtbar (Tailwind-Preflight `list-style: none` in `@layer base` wurde durch markdown.css nicht wiederhergestellt); Aufgabenlisten mit Kästchen bleiben ohne Punkt
|
||||||
|
- CHANGELOG.md um zwei Stichpunkte ergänzt
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Kalender-Plakette in Kalenderfarbe, Farbpunkt im Tooltip, Sortierung je Tag + Tests** - `1e4ec30` (feat)
|
||||||
|
2. **Task 2: Aufzählungspunkte in Markdown-Ansichten (globals.css), CHANGELOG, voller Testlauf** - `c85cf9a` (fix)
|
||||||
|
|
||||||
|
_TDD-Task 1: RED (drei Tests geschrieben, liefen fehlschlagend) → GREEN (Implementierung, alle drei plus Bestand grün) in einem Commit — der Plan verlangte keine separaten RED/GREEN-Commits._
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-month.ts` - `groupEventsByDate` sortiert jede Tagesgruppe nach Start aufsteigend; Kopf-/Funktionskommentar ergänzt
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-month.test.ts` - Test 2b (unsortierte Eingabe → Tagesgruppe sortiert)
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-widget.tsx` - Plakette mit bedingtem `style={{ backgroundColor }}` + `text-white` bei Farbe, sonst `bg-primary text-primary-foreground` ohne style; Tooltip-Zeile mit Farbpunkt (`data-testid="tooltip-color-dot"`); Doc-Kommentar ergänzt
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx` - Test 3b (Farbe des frühesten Termins, Tooltip-Punkte, Reihenfolge) und Test 3c (ohne Farbe bleibt bg-primary)
|
||||||
|
- `apps/web/src/app/globals.css` - Block `.wmde-markdown`-Listenregeln am Dateiende mit deutschem Kommentar
|
||||||
|
- `CHANGELOG.md` - zwei neue Stichpunkte unter „Unveröffentlicht“ (Geändert, Behoben)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- Plakettenfarbe = Farbe des frühesten Termins des Tages; bei mehreren Kalenderquellen an einem Tag keine Mischfarbe (bewusst einfach, so im Plan vorgegeben)
|
||||||
|
- CSS-Block für die Markdown-Listen bewusst ungeschichtet (kein `@layer`), weil Tailwind-Preflight in `@layer base` liegt — ungeschichtetes CSS schlägt jede `@layer`-Regel unabhängig von Spezifität
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None - plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
None
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
- Kein Folgeauftrag angelegt; beide Nachträge aus der heutigen Browser-Prüfung (260916-htc/iex/j4f) sind damit geschlossen
|
||||||
|
- Optional: kurzer Blick in den Browser auf „Was ist neu“ und die Notiz-Vorschau, um D4 (visuelle Wirkung der CSS-Regeln) zu bestätigen
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: quick-260916-jvj*
|
||||||
|
*Completed: 2026-09-16*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260916-k2z
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
autonomous: true
|
||||||
|
requirements: [QUICK-260916-K2Z]
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
estimate:
|
||||||
|
tokens: 22000
|
||||||
|
raw_tokens: 22000
|
||||||
|
tasks: 2
|
||||||
|
confidence: low
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Kalender-Widget, Monatsraster: liegen an einem Tag Termine aus GENAU EINEM Kalender (eine `sourceId`), sieht der Tag aus wie heute — eine Plakette `data-testid=\"calendar-day-count\"` in Größe `h-[clamp(10px,3cqw,16px)]`, Inline-Hintergrund in der Kalenderfarbe mit `text-white`, ohne Farbe `bg-primary text-primary-foreground` (Tests 3, 3b, 3c bleiben unverändert grün)."
|
||||||
|
- "Liegen an einem Tag Termine aus ZWEI oder DREI Kalendern, stehen unten rechts in der Zelle zwei bzw. drei kleine Kreise nebeneinander (`h-[clamp(8px,2.4cqw,12px)]`, Schrift `text-[clamp(6px,1.5cqw,8px)]`), jeder in der Farbe seines Kalenders mit der Anzahl der Termine dieses Kalenders; Reihenfolge = erstes Auftreten in den nach Start sortierten Tagesterminen."
|
||||||
|
- "Liegen an einem Tag Termine aus MEHR ALS DREI Kalendern, zeigen die ersten zwei Kalender je einen kleinen farbigen Kreis und ein dritter grauer Kreis (`bg-muted-foreground text-background`, `data-testid=\"calendar-day-count-rest\"`) die SUMME der Termine aller übrigen Kalender."
|
||||||
|
- "Gruppierung erfolgt nach `sourceId`, NICHT nach Farbe (zwei Kalender mit gleicher Farbe bleiben zwei Kreise); die sichtbare Farbe eines Kreises ist die `color` des ersten Termins seiner Gruppe, Rückfall wie bisher Akzentfarbe."
|
||||||
|
- "Tooltip beim Überfahren bleibt unverändert (Zeilen mit Farbpunkt in Startreihenfolge)."
|
||||||
|
- "CHANGELOG.md, `## Unveröffentlicht` → `### Geändert`: der vorhandene Stichpunkt lautet jetzt „Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; mehrere Kalender am selben Tag zeigen je einen kleinen Kreis“ — kein neuer Stichpunkt, kein Punkt am Ende, echte Umlaute."
|
||||||
|
- "Gates: `pnpm --filter @tessera/web type-check` Exit 0; `pnpm --filter @tessera/web exec vitest run` komplett grün (Basislinie 52 Dateien / 350 Tests → danach 52 Dateien / 354 Tests: +2 calendar-month, +2 calendar-widget); changelog.test.ts grün. KEIN `biome check`, kein Docker-Build, kein Deploy, kein Testserver, kein `git push`."
|
||||||
|
artifacts:
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-month.ts — neue Exporte `DaySourceGroup`, `DayBadge`, `CALENDAR_DAY_BADGE_MAX = 3`, `groupDayBySource(events)`, `buildDayBadges(events, max = CALENDAR_DAY_BADGE_MAX)`"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-month.test.ts — neue Tests 8 (groupDayBySource) und 9 (buildDayBadges)"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-widget.tsx — Plaketten-IIFE (Z. 246-264) ersetzt durch Wrapper `data-testid=\"calendar-day-badges\"` mit `buildDayBadges(day.events).map(...)`"
|
||||||
|
- "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx — neue Tests 3d (zwei Kalender → zwei Kreise) und 3e (vier Kalender → zwei Kreise + grauer Rest)"
|
||||||
|
- "CHANGELOG.md — Stichpunkt Z. 16 erweitert"
|
||||||
|
key_links:
|
||||||
|
- "`groupEventsByDate` (calendar-month.ts Z. 110-121) sortiert jede Tagesgruppe bereits nach Start (quick-260916-jvj) — `groupDayBySource` darf NICHT selbst sortieren, sondern übernimmt die Reihenfolge von `day.events`, damit Kreis-Reihenfolge und Tooltip-Reihenfolge übereinstimmen."
|
||||||
|
- "Ein DOM-Element trägt nur EIN `data-testid`. Deshalb: farbige Kreise `calendar-day-count`, grauer Restkreis `calendar-day-count-rest`, Wrapper `calendar-day-badges` (Tests zählen Kinder des Wrappers für „drei Kreise“)."
|
||||||
|
- "Die Positionierung `absolute bottom-px right-px` wandert von der Plakette auf den Wrapper; Tests 3b/3c prüfen nur Text, Inline-Hintergrund und die Klassen `text-white`/`bg-primary`/`text-primary-foreground` — diese Klassen bleiben auf der Plakette selbst."
|
||||||
|
- "Grau-Wahl: `--muted-foreground` ist hell oklch 0.55, dunkel oklch 0.65 (globals.css Z. 71/98). `text-white` wäre im Dunkelmodus auf 0.65 schwach; `text-background` (hell = weiß, dunkel = dunkel) ist auf beiden Themes lesbar. `bg-muted` (0.96/0.30) wäre auf der Zelle `bg-muted/50` unsichtbar."
|
||||||
|
- "`toHaveStyle({ backgroundColor: '#c44040' })` normalisiert hex→rgb (Muster Test 3b); Rückfall-Fall über `style.backgroundColor === ''` prüfen (Muster Test 3c)."
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Folgeaufgabe zu 260916-jvj (Plakette in Kalenderfarbe): Liegen an einem Tag Termine aus mehreren Kalendern, zeigt die Plakette bisher nur die Farbe des frühesten Termins und die Gesamtzahl. Ab jetzt bekommt jeder Kalender einen eigenen kleinen Kreis in seiner Farbe mit seiner Anzahl (bis drei Kalender); ab dem vierten Kalender fassen zwei farbige Kreise plus ein grauer Restkreis mit der Summe die Übrigen zusammen. Ein einzelner Kalender sieht weiter aus wie heute.
|
||||||
|
|
||||||
|
Purpose: Gemischte Tage im Monatsraster auf einen Blick lesbar machen (User-Entscheidung „ja, mach so“).
|
||||||
|
Output: Zwei reine Hilfsfunktionen in calendar-month.ts mit Unit-Tests, Kreis-Rendering im Widget mit Komponententests, erweiterter CHANGELOG-Stichpunkt, zwei Commits.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.claude/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@/home/vicolab/projects/tessera-ctl/CLAUDE.md
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
@/home/vicolab/projects/tessera-ctl/apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
@/home/vicolab/projects/tessera-ctl/CHANGELOG.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Hilfsfunktionen groupDayBySource / buildDayBadges in calendar-month.ts mit Unit-Tests</name>
|
||||||
|
<files>apps/web/src/components/dashboard/widgets/calendar-month.ts, apps/web/src/components/dashboard/widgets/calendar-month.test.ts</files>
|
||||||
|
<read_first>
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.ts (Kopfkommentar Z. 1-20, `groupEventsByDate` Z. 104-121, Export-Stil)
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts (Fabrik `ev(id, start, end, extra)` Z. 19-29 setzt `sourceId: 's1'` als Vorgabe; `extra` überschreibt `sourceId`/`color`; Testnummerierung endet bei Test 7 + „Zusatz“)
|
||||||
|
- apps/web/src/lib/calendar-api.ts Z. 41-51 (`CalendarEvent`: `sourceId: string`, `color?: string`)
|
||||||
|
</read_first>
|
||||||
|
<behavior>
|
||||||
|
- Test 8 (groupDayBySource): (a) drei Termine, alle `s1` mit `color: '#c44040'` → genau eine Gruppe `{ sourceId: 's1', color: '#c44040', count: 3 }`. (b) Termine in der Reihenfolge s2, s1, s2 (Eingabereihenfolge wird NICHT umsortiert) → zwei Gruppen in der Reihenfolge s2 (count 2), s1 (count 1). (c) Termine ohne `color` → `color` der Gruppe ist `undefined` (nicht `''`, nicht `null`). (d) zwei Kalender mit derselben Farbe `#123456` → trotzdem zwei Gruppen (Gruppierung nach `sourceId`, nicht nach Farbe). (e) Farbe der Gruppe ist die `color` des ERSTEN Termins der Gruppe, auch wenn ein späterer Termin derselben Quelle eine andere Farbe trägt. (f) leeres Array → `[]`.
|
||||||
|
- Test 9 (buildDayBadges): (a) 1 Quelle → genau ein Eintrag `{ key: 's1', color, count, rest: false }`. (b) 2 Quellen → zwei Einträge, beide `rest: false`, Reihenfolge wie Gruppen. (c) 3 Quellen → drei Einträge, alle `rest: false`, KEIN Restkreis. (d) 4 Quellen s1(1 Termin), s2(2), s3(1), s4(3) → genau drei Einträge: [0] key `s1` count 1, [1] key `s2` count 2, [2] `{ key: '__rest__', color: undefined, count: 4, rest: true }` (1+3 = Summe von s3 und s4). (e) `buildDayBadges(vierQuellen, 2)` → zwei Einträge: s1 und Rest mit count 6 (2+1+3). (f) leeres Array → `[]`.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
In `calendar-month.ts` nach `groupEventsByDate` (Z. 121) zwei reine Funktionen plus Typen/Konstante ergänzen; kein React, keine DOM-Zugriffe (Modulregel aus dem Kopfkommentar).
|
||||||
|
|
||||||
|
1. `export interface DaySourceGroup { sourceId: string; color?: string; count: number }` und `export function groupDayBySource(events: CalendarEvent[]): DaySourceGroup[]`: über `events` in gegebener Reihenfolge laufen, `Map<string, DaySourceGroup>` nach `event.sourceId`; beim ersten Auftreten Gruppe mit `color: event.color` (kann `undefined` sein) und `count: 0` anlegen, dann `count` erhöhen; Rückgabe `Array.from(map.values())` (Map-Einfügereihenfolge = erstes Auftreten). NICHT sortieren — die Reihenfolge kommt aus `groupEventsByDate` (dort bereits nach Start sortiert), damit Kreise und Tooltip dieselbe Reihenfolge haben.
|
||||||
|
|
||||||
|
2. `export const CALENDAR_DAY_BADGE_MAX = 3;`, `export interface DayBadge { key: string; color?: string; count: number; rest: boolean }` und `export function buildDayBadges(events: CalendarEvent[], max: number = CALENDAR_DAY_BADGE_MAX): DayBadge[]`: `groups = groupDayBySource(events)`. Wenn `groups.length <= max` → jede Gruppe zu `{ key: group.sourceId, color: group.color, count: group.count, rest: false }`. Sonst → die ersten `max - 1` Gruppen wie eben, danach genau ein Eintrag `{ key: '__rest__', color: undefined, count: Summe der count aller Gruppen ab Index max - 1, rest: true }`. Leere Eingabe ergibt `[]`.
|
||||||
|
|
||||||
|
3. Kopfkommentar der Datei (Z. 14-19) um einen Satz ergänzen: Kreise je Kalender werden nach `sourceId` gruppiert, Reihenfolge = erstes Auftreten in der start-sortierten Tagesgruppe, ab dem vierten Kalender grauer Restkreis mit Summe (quick-260916-k2z). Deutsche Kommentare, ASCII-Umlaute wie im Bestand (ue/ae/oe).
|
||||||
|
|
||||||
|
4. In `calendar-month.test.ts` die Importliste um `buildDayBadges` und `groupDayBySource` erweitern und nach Test 7 die Tests 8 und 9 gemäß `<behavior>` anlegen (deutsche Testnamen im Stil „Test 8: groupDayBySource …“). Termine mit der vorhandenen Fabrik `ev(...)` erzeugen und `sourceId`/`color` über den `extra`-Parameter setzen; alle Termine eines Tests auf denselben Tag legen, das ist für die Helfer aber unerheblich (sie kennen keine Tage).
|
||||||
|
|
||||||
|
Reihenfolge TDD: Tests zuerst schreiben, `vitest run calendar-month` rot sehen, dann Implementierung, grün.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/dashboard/widgets/calendar-month.test.ts && grep -c "export function groupDayBySource\|export function buildDayBadges\|export const CALENDAR_DAY_BADGE_MAX" apps/web/src/components/dashboard/widgets/calendar-month.ts | grep -qx 3</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Tests 8 und 9 grün, alle bisherigen calendar-month-Tests weiter grün (Datei 11 Tests); `groupDayBySource` gruppiert nach `sourceId` in Reihenfolge des ersten Auftretens mit `color` des ersten Gruppentermins (undefined ohne Farbe); `buildDayBadges` liefert 1/2/3 Einträge ohne Rest bzw. bei mehr als `max` Gruppen `max - 1` Einträge plus einen `rest: true`-Eintrag mit summierter Anzahl. Commit: `feat(web): Kalender-Tag nach Kalender gruppieren, Kreise je Kalender berechnen (calendar-month)`.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 2: Kreise je Kalender im Widget rendern, Komponententests, CHANGELOG, Gesamtlauf</name>
|
||||||
|
<files>apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx, CHANGELOG.md</files>
|
||||||
|
<read_first>
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx Z. 9-19 (Importliste aus `./calendar-month`), Z. 27-37 (Kopfkommentar, Satz zur Plakette), Z. 246-264 (Plaketten-IIFE: `badgeColor = day.events[0]?.color`, `badgeClass`, `data-testid="calendar-day-count"`)
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx Z. 44-54 (Fabrik `ev`, Vorgabe `sourceId: 's1'`), Z. 125-208 (Tests 3, 3b, 3c — Erwartungen an die Einzelplakette, die unverändert grün bleiben müssen)
|
||||||
|
- CHANGELOG.md Z. 13-16 (`### Geändert`, vorhandener Stichpunkt „Kalender-Widget: Plakette am Tag in der Farbe des Kalenders“)
|
||||||
|
</read_first>
|
||||||
|
<behavior>
|
||||||
|
- Test 3d (zwei Kalender am selben Tag): Mock-Termine am 2026-07-20 in dieser Array-Reihenfolge: s2 `#4060c4` 10:00, s2 `#4060c4` 14:00, s1 `#c44040` 09:00 (s1 steht im Array zuletzt, ist aber der früheste Termin). Erwartung in der Zelle `[data-date="2026-07-20"]`: `getAllByTestId('calendar-day-count')` hat Länge 2; [0] Text „1“, `toHaveStyle({ backgroundColor: '#c44040' })`; [1] Text „2“, `toHaveStyle({ backgroundColor: '#4060c4' })`; beide `toHaveClass('text-white')`, beide `not.toHaveClass('bg-primary')`, beide `toHaveClass('h-[clamp(8px,2.4cqw,12px)]')`; `queryByTestId('calendar-day-count-rest')` ist null; Wrapper `getByTestId('calendar-day-badges')` hat `childElementCount` 2.
|
||||||
|
- Test 3e (vier Kalender am selben Tag): Mock-Termine am 2026-07-21: s1 `#111111` 08:00 (1 Termin), s2 `#222222` 09:00 und 09:30 (2 Termine), s3 ohne Farbe 10:00 (1 Termin), s4 `#444444` 11:00, 12:00, 13:00 (3 Termine). Erwartung in `[data-date="2026-07-21"]`: Wrapper `calendar-day-badges` hat `childElementCount` 3; `getAllByTestId('calendar-day-count')` Länge 2 mit [0] „1“ + Hintergrund `#111111`, [1] „2“ + Hintergrund `#222222`; `getByTestId('calendar-day-count-rest')` hat Text „4“, `toHaveClass('bg-muted-foreground')`, `toHaveClass('text-background')`, `style.backgroundColor === ''`, `not.toHaveClass('text-white')`.
|
||||||
|
- Bestehende Tests 3, 3b, 3c laufen unverändert grün (Einzelplakette: eine `calendar-day-count`, Farbe/`text-white` bzw. `bg-primary text-primary-foreground`).
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. `calendar-widget.tsx`: Import `buildDayBadges` aus `./calendar-month` in die bestehende alphabetische Importliste (Z. 9-19) aufnehmen. Die IIFE Z. 246-264 komplett durch einen Wrapper ersetzen, der nur bei `hasEvents` gerendert wird: `<span data-testid="calendar-day-badges" className="absolute bottom-px right-px flex items-end gap-px">` mit `buildDayBadges(day.events).map((badge) => ...)`. Die Positionierung (`absolute bottom-px right-px`) liegt damit NUR auf dem Wrapper, nicht mehr auf den Kreisen. Vor dem `return` des Zellen-Callbacks `const badges = buildDayBadges(day.events);` und `const single = badges.length === 1;` berechnen (kein IIFE mehr).
|
||||||
|
|
||||||
|
Je Kreis ein `<span key={badge.key}>` mit:
|
||||||
|
- `data-testid`: `badge.rest ? 'calendar-day-count-rest' : 'calendar-day-count'` — ein Element kann nur EIN `data-testid` tragen, deshalb keine Doppelvergabe.
|
||||||
|
- Klassen aus einem Array, `.filter(Boolean).join(' ')` wie bisher: immer `flex items-center justify-center rounded-full px-0.5 font-semibold leading-none`; bei `single` zusätzlich `h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] text-[clamp(7px,1.8cqw,10px)]` (heutige Größe), sonst `h-[clamp(8px,2.4cqw,12px)] min-w-[clamp(8px,2.4cqw,12px)] text-[clamp(6px,1.5cqw,8px)]`; Farbklassen: `badge.rest` → `bg-muted-foreground text-background`, sonst `badge.color` → `text-white`, sonst `bg-primary text-primary-foreground`.
|
||||||
|
- `style`: `badge.color && !badge.rest ? { backgroundColor: badge.color } : undefined`.
|
||||||
|
- Inhalt: `{badge.count}`.
|
||||||
|
|
||||||
|
Grau-Wahl bewusst `text-background` statt `text-white`: `--muted-foreground` ist im Dunkelmodus ein helles Grau (oklch 0.65), weiße Schrift wäre dort schwach; `text-background` ist hell weiß und dunkel dunkel, also auf beiden Themes lesbar (Begründung als kurzen deutschen Kommentar über den Wrapper schreiben, ASCII-Umlaute).
|
||||||
|
|
||||||
|
Kopfkommentar Z. 33-35 anpassen: Zaehl-Plakette in der Farbe des Kalenders; bei mehreren Kalendern am selben Tag je ein kleiner Kreis pro Kalender (max. drei, danach zwei plus grauer Restkreis mit Summe, Logik in `buildDayBadges`, quick-260916-k2z). Tooltip-Block (Z. 311-346) NICHT anfassen.
|
||||||
|
|
||||||
|
2. `calendar-widget.test.tsx`: nach Test 3c die Tests 3d und 3e gemäß `<behavior>` anlegen (Muster von 3b/3c: `mockFetchEvents.mockResolvedValue([...])`, `await import('./calendar-widget')`, `render` mit eigener `instanceId` `cal-3d`/`cal-3e`, `waitFor` auf „Juli 2026“, Zelle über `document.querySelector('[data-date="…"]')`, `within(...)`). `sourceId` und `color` je Termin über den `extra`-Parameter der Fabrik `ev` setzen. Reihenfolge-Prüfung in 3d ergibt sich daraus, dass s1 im Mock-Array zuletzt steht, aber als frühester Termin den ersten Kreis bekommt.
|
||||||
|
|
||||||
|
3. `CHANGELOG.md` Z. 16: den vorhandenen Stichpunkt ersetzen durch `- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; mehrere Kalender am selben Tag zeigen je einen kleinen Kreis` — KEINEN neuen Stichpunkt anlegen, kein Punkt am Ende, echte Umlaute, sonst nichts im CHANGELOG ändern.
|
||||||
|
|
||||||
|
4. Gesamtlauf: `pnpm --filter @tessera/web type-check` (Exit 0) und `pnpm --filter @tessera/web exec vitest run` (alle grün, erwartet 52 Dateien / 354 Tests; Basislinie 350 + 2 aus Task 1 + 2 aus diesem Task). Weicht die Zahl ab, Ursache benennen, nicht schönreden. Kein `biome check`, kein Docker-Build, kein Deploy, kein Testserver, kein `git push`.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web type-check && pnpm --filter @tessera/web exec vitest run && grep -qF "Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; mehrere Kalender am selben Tag zeigen je einen kleinen Kreis" CHANGELOG.md && grep -q 'data-testid="calendar-day-count-rest"\|calendar-day-count-rest' apps/web/src/components/dashboard/widgets/calendar-widget.tsx && grep -q "buildDayBadges" apps/web/src/components/dashboard/widgets/calendar-widget.tsx</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Einzelplakette sieht aus wie bisher (Tests 3, 3b, 3c unverändert grün); Tests 3d und 3e grün (zwei Kalender → zwei kleine Kreise in je eigener Farbe mit eigener Anzahl in Startreihenfolge; vier Kalender → zwei farbige Kreise plus grauer Restkreis `bg-muted-foreground text-background` mit Summe 4); Tooltip unverändert; CHANGELOG-Stichpunkt erweitert; `type-check` Exit 0; Vitest komplett grün mit 52 Dateien / 354 Tests. Commit: `feat(web): Kalender-Widget zeigt je Kalender einen kleinen Kreis am Tag; Changelog`.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| API → Widget | `CalendarEvent[]` vom Backend (`fetchEvents`), Felder `sourceId`/`color` werden im DOM als Schlüssel bzw. Inline-Stil verwendet |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||||
|
| T-K2Z-01 | Tampering | `style={{ backgroundColor: badge.color }}` | low | accept | Unverändert gegenüber 260916-jvj: React setzt Inline-Stile über die CSSOM-Eigenschaft, kein `dangerouslySetInnerHTML`; `color` stammt aus der eigenen Quellen-Tabelle (Admin/Anwender pflegt sie selbst) |
|
||||||
|
| T-K2Z-02 | Denial of Service | `groupDayBySource` | low | accept | Lineare Laufzeit über die Tagestermine, Eingabe ist bereits auf das 42-Tage-Fenster begrenzt |
|
||||||
|
| T-K2Z-SC | Tampering | npm-Installs | low | accept | Keine neuen Pakete in diesem Auftrag |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `pnpm --filter @tessera/web type-check` Exit 0
|
||||||
|
- `pnpm --filter @tessera/web exec vitest run` 52 Dateien / 354 Tests grün (calendar-month 11, calendar-widget 15, changelog.test.ts grün)
|
||||||
|
- Einzelplakette: Tests 3/3b/3c ohne Änderung grün
|
||||||
|
- Zwei Commits (Task 1: Helfer + Unit-Tests; Task 2: Widget + Komponententests + CHANGELOG), kein Push
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Tage mit einem Kalender: unverändert eine Plakette in Kalenderfarbe (oder Akzent)
|
||||||
|
- Tage mit zwei/drei Kalendern: zwei/drei kleine Kreise, je Kalenderfarbe und eigene Anzahl, Reihenfolge nach frühestem Termin
|
||||||
|
- Tage mit vier oder mehr Kalendern: zwei farbige Kreise plus grauer Restkreis mit Summe der übrigen
|
||||||
|
- Gruppierung nach `sourceId`, nicht nach Farbe
|
||||||
|
- CHANGELOG-Stichpunkt erweitert, alle Gates grün
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/quick/260916-k2z-kalender-widget-mehrere-kalender-am-selb/260916-k2z-SUMMARY.md` when done
|
||||||
|
</output>
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260916-k2z
|
||||||
|
plan: 01
|
||||||
|
subsystem: ui
|
||||||
|
tags: [react, next.js, vitest, calendar-widget, dashboard]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: quick-260916-jvj
|
||||||
|
provides: "Plakette in Kalenderfarbe (badgeColor = day.events[0]?.color), Tooltip mit Farbpunkt je Zeile, sortierte Tagesgruppen in groupEventsByDate"
|
||||||
|
provides:
|
||||||
|
- "groupDayBySource(events) — gruppiert Tagestermine nach sourceId (nicht Farbe), Reihenfolge = erstes Auftreten"
|
||||||
|
- "buildDayBadges(events, max) — baut bis zu drei Plaketten-Kreise, ab dem vierten Kalender einen grauen Restkreis mit Summe"
|
||||||
|
- "Kalender-Widget rendert je Kalender am selben Tag einen kleinen farbigen Kreis statt einer einzigen Gesamt-Plakette"
|
||||||
|
affects: [dashboard-calendar-widget, calendar-month-helpers]
|
||||||
|
|
||||||
|
# Actuals (#2632)
|
||||||
|
actuals:
|
||||||
|
tokens: 4700
|
||||||
|
tasks: 2
|
||||||
|
commits: 2
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Reine Hilfsfunktionen ohne React/DOM in calendar-month.ts, vom Widget importiert (Muster aus resolveCalendarConfig/groupEventsByDate fortgeschrieben)"
|
||||||
|
- "Ein DOM-Element traegt genau ein data-testid; mehrere gleichartige Elemente unterscheiden sich per rest-Flag im data-testid (calendar-day-count vs. calendar-day-count-rest), Positionierung wandert auf einen gemeinsamen Wrapper"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||||||
|
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Grau-Wahl fuer den Restkreis: text-background statt text-white, weil --muted-foreground im Dunkelmodus hell ist (oklch 0.65) und weisse Schrift dort schwach waere; text-background ist auf beiden Themes lesbar (aus PLAN uebernommen, keine eigene Abweichung)"
|
||||||
|
- "Gruppierung nach sourceId, nicht nach Farbe — zwei Kalender mit identischer Farbe bleiben zwei Kreise (aus PLAN uebernommen)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "buildDayBadges(events, max = CALENDAR_DAY_BADGE_MAX) als generische Kappungslogik: erste max-1 Gruppen sichtbar, Rest zu einem Summen-Eintrag gebuendelt — wiederverwendbar fuer aehnliche Kappungsfaelle"
|
||||||
|
|
||||||
|
requirements-completed: [QUICK-260916-K2Z]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "groupDayBySource gruppiert Tagestermine nach sourceId (nicht Farbe) in Reihenfolge des ersten Auftretens, Farbe = Farbe des ersten Termins der Gruppe"
|
||||||
|
requirement: "QUICK-260916-K2Z"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-month.test.ts#Test 8: groupDayBySource gruppiert nach sourceId in Reihenfolge des ersten Auftretens"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "buildDayBadges liefert 1-3 Eintraege ohne Rest, ab dem vierten Kalender max-1 Eintraege plus einen rest:true-Eintrag mit summierter Anzahl"
|
||||||
|
requirement: "QUICK-260916-K2Z"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-month.test.ts#Test 9: buildDayBadges liefert je Kalender einen Eintrag, ab dem vierten einen grauen Restkreis"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "Einzelplakette (ein Kalender am Tag) sieht unveraendert aus wie vor diesem Auftrag (Kalenderfarbe bzw. Akzentfarbe)"
|
||||||
|
requirement: "QUICK-260916-K2Z"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3: Zaehl-Plakette zeigt die korrekte Terminanzahl je Tag"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3b: Plakette traegt die Kalenderfarbe des fruehesten Termins, Tooltip-Zeilen mit Farbpunkt"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3c: Plakette ohne Kalenderfarbe behaelt bg-primary"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "Zwei Kalender am selben Tag zeigen zwei kleine Kreise, je eigene Farbe und Anzahl, in Startreihenfolge"
|
||||||
|
requirement: "QUICK-260916-K2Z"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3d: zwei Kalender am selben Tag zeigen zwei kleine Kreise in Startreihenfolge"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D5
|
||||||
|
description: "Vier oder mehr Kalender am selben Tag zeigen zwei farbige Kreise plus einen grauen Restkreis mit der Summe der uebrigen"
|
||||||
|
requirement: "QUICK-260916-K2Z"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx#Test 3e: vier Kalender am selben Tag zeigen zwei Kreise plus grauen Restkreis mit Summe"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D6
|
||||||
|
description: "CHANGELOG.md-Stichpunkt erweitert; type-check und komplette Vitest-Suite gruen (52 Dateien / 354 Tests)"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "pnpm --filter @tessera/web type-check && pnpm --filter @tessera/web exec vitest run"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
|
||||||
|
duration: 10min
|
||||||
|
completed: 2026-09-16
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick Task 260916-k2z: Kalender-Widget mehrere Kalender am selben Tag als kleine Kreise Summary
|
||||||
|
|
||||||
|
**Kalender-Widget zeigt bei mehreren Kalendern am selben Tag je einen kleinen farbigen Kreis pro Kalender (bis drei), ab dem vierten Kalender zwei Kreise plus einen grauen Restkreis mit der Summe — via neuen reinen Hilfsfunktionen `groupDayBySource`/`buildDayBadges` in calendar-month.ts.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~10 min (zwei Task-Commits 14:34:49 und 14:37:01 Uhr; Zeiterfassung nicht exakt ab Sitzungsbeginn protokolliert)
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 5
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- `groupDayBySource(events)`: gruppiert Tagestermine nach `sourceId` (nicht Farbe) in Reihenfolge des ersten Auftretens, Gruppenfarbe = Farbe des ersten Termins der Gruppe
|
||||||
|
- `buildDayBadges(events, max = 3)`: liefert 1-3 Einträge ohne Rest bzw. ab dem vierten Kalender `max - 1` Einträge plus einen `rest: true`-Eintrag mit summierter Anzahl
|
||||||
|
- Kalender-Widget: Plaketten-Rendering ersetzt (Wrapper `calendar-day-badges`, Kreise `calendar-day-count`/`calendar-day-count-rest`), Einzelplakette sieht unverändert aus wie zuvor
|
||||||
|
- CHANGELOG-Stichpunkt erweitert
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Hilfsfunktionen groupDayBySource / buildDayBadges in calendar-month.ts mit Unit-Tests** - `4ddadc6` (feat, TDD: RED vor Implementierung bestätigt)
|
||||||
|
2. **Task 2: Kreise je Kalender im Widget rendern, Komponententests, CHANGELOG, Gesamtlauf** - `7429c5b` (feat, TDD: RED vor Implementierung bestätigt)
|
||||||
|
|
||||||
|
**Plan metadata:** committed separately by the orchestrator (per constraints, this executor did not commit SUMMARY.md/STATE.md/PLAN.md)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-month.ts` - neue Exporte `DaySourceGroup`, `DayBadge`, `CALENDAR_DAY_BADGE_MAX`, `groupDayBySource`, `buildDayBadges`; Kopfkommentar ergänzt
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-month.test.ts` - Tests 8 (groupDayBySource) und 9 (buildDayBadges)
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-widget.tsx` - Plaketten-IIFE durch Wrapper `calendar-day-badges` mit `buildDayBadges(day.events).map(...)` ersetzt; Import und Kopfkommentar angepasst
|
||||||
|
- `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx` - Tests 3d (zwei Kalender) und 3e (vier Kalender) ergänzt
|
||||||
|
- `CHANGELOG.md` - Stichpunkt unter „Geändert“ um „mehrere Kalender am selben Tag zeigen je einen kleinen Kreis“ erweitert
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- Grau-Wahl für den Restkreis: `text-background` statt `text-white` — `--muted-foreground` ist im Dunkelmodus hell (oklch 0.65), `text-background` ist auf beiden Themes lesbar (aus dem Plan übernommen, keine eigene Abweichung nötig)
|
||||||
|
- Gruppierung strikt nach `sourceId`, nicht nach Farbe (aus dem Plan übernommen)
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None - plan executed exactly as written. Die einzige Zahlenabweichung ist rein buchhalterisch: Der Plan nannte für `calendar-widget.test.tsx` 15 Tests, tatsächlich sind es 14 (12 bestehende + 2 neue); die im Plan als Gesamtgate genannte Summe von 354 Tests über alle 52 Dateien stimmt exakt — die einzelne Datei-Teilzahl im Plantext war ungenau, das Verhalten selbst ist unverändert zum Plan.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
None.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
None - no external service configuration required.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
- Kalender-Widget-Feature ist vollständig, kein Folgeauftrag angelegt
|
||||||
|
- Keine Blocker
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: quick-260916-k2z*
|
||||||
|
*Completed: 2026-09-16*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All 5 modified files and the SUMMARY.md file confirmed present on disk; both task commits (`4ddadc6`, `7429c5b`) confirmed present in `git log --oneline --all`.
|
||||||
@@ -13,6 +13,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
### Geändert
|
### Geändert
|
||||||
|
|
||||||
- Kalenderquellen: Adressfeld zeigt je Typ ein Beispiel; bei Exchange EWS Hinweis auf `/EWS/Exchange.asmx`
|
- Kalenderquellen: Adressfeld zeigt je Typ ein Beispiel; bei Exchange EWS Hinweis auf `/EWS/Exchange.asmx`
|
||||||
|
- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; mehrere Kalender am selben Tag zeigen je einen kleinen Kreis
|
||||||
|
|
||||||
### Entfernt
|
### Entfernt
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
|
|
||||||
- Notiz-Widget: Listen lassen sich in der Ansicht direkt abhaken
|
- Notiz-Widget: Listen lassen sich in der Ansicht direkt abhaken
|
||||||
- Notiz-Widget: Textbereich folgt dem Hell/Dunkel-Schalter von Tessera
|
- Notiz-Widget: Textbereich folgt dem Hell/Dunkel-Schalter von Tessera
|
||||||
|
- „Was ist neu“ und Notiz-Ansicht: Aufzählungspunkte wieder sichtbar
|
||||||
|
|
||||||
## 1.1.0 – 2026-09-16
|
## 1.1.0 – 2026-09-16
|
||||||
|
|
||||||
|
|||||||
@@ -124,3 +124,15 @@ body {
|
|||||||
margin-left: var(--current-sidebar-width, var(--sidebar-width));
|
margin-left: var(--current-sidebar-width, var(--sidebar-width));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tailwind-Preflight setzt in @layer base `ul, ol { list-style: none }`; das
|
||||||
|
Markdown-Stylesheet von @uiw/react-markdown-preview stellt es nicht wieder
|
||||||
|
her — deshalb fehlten auf „Was ist neu“ und in der Notiz-Vorschau die
|
||||||
|
Aufzaehlungspunkte. Diese Regel liegt bewusst NICHT in einem @layer, damit
|
||||||
|
sie die Layer-Regel unabhaengig von Spezifitaet schlaegt. Aufgabenlisten
|
||||||
|
mit Kaestchen (`- [ ]`) bleiben ohne Punkt (quick-260916-jvj).
|
||||||
|
*/
|
||||||
|
.wmde-markdown ul { list-style: disc; }
|
||||||
|
.wmde-markdown ul ul { list-style: circle; }
|
||||||
|
.wmde-markdown ol { list-style: decimal; }
|
||||||
|
.wmde-markdown ul.contains-task-list, .wmde-markdown li.task-list-item { list-style: none; }
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import type { CalendarEvent } from '@/lib/calendar-api';
|
|||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
buildCalendarDays,
|
buildCalendarDays,
|
||||||
|
buildDayBadges,
|
||||||
computeFetchWindow,
|
computeFetchWindow,
|
||||||
dateKey,
|
dateKey,
|
||||||
formatEventDate,
|
formatEventDate,
|
||||||
formatEventTime,
|
formatEventTime,
|
||||||
formatMonthLabel,
|
formatMonthLabel,
|
||||||
gridStartFor,
|
gridStartFor,
|
||||||
|
groupDayBySource,
|
||||||
groupEventsByDate,
|
groupEventsByDate,
|
||||||
resolveCalendarConfig,
|
resolveCalendarConfig,
|
||||||
selectUpcomingEvents,
|
selectUpcomingEvents,
|
||||||
@@ -63,6 +65,16 @@ describe('calendar-month', () => {
|
|||||||
expect(day20?.events.length).toBe(2);
|
expect(day20?.events.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Test 2b: groupEventsByDate sortiert jede Tagesgruppe nach Start aufsteigend', () => {
|
||||||
|
const events = [
|
||||||
|
ev('e2', new Date(2026, 6, 20, 14, 0), new Date(2026, 6, 20, 15, 0)),
|
||||||
|
ev('e1', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0)),
|
||||||
|
];
|
||||||
|
|
||||||
|
const grouped = groupEventsByDate(events);
|
||||||
|
expect(grouped.get('2026-07-20')?.map((e) => e.id)).toEqual(['e1', 'e2']);
|
||||||
|
});
|
||||||
|
|
||||||
it('Test 3: resolveCalendarConfig klemmt ungueltige Werte auf die Vorgaben/Grenzen zurueck', () => {
|
it('Test 3: resolveCalendarConfig klemmt ungueltige Werte auf die Vorgaben/Grenzen zurueck', () => {
|
||||||
expect(resolveCalendarConfig({})).toEqual({ showMonth: true, maxEvents: 3, lookaheadDays: 30 });
|
expect(resolveCalendarConfig({})).toEqual({ showMonth: true, maxEvents: 3, lookaheadDays: 30 });
|
||||||
expect(resolveCalendarConfig({ showMonth: false }).showMonth).toBe(false);
|
expect(resolveCalendarConfig({ showMonth: false }).showMonth).toBe(false);
|
||||||
@@ -139,4 +151,99 @@ describe('calendar-month', () => {
|
|||||||
expect(start.getHours()).toBe(0);
|
expect(start.getHours()).toBe(0);
|
||||||
expect(addDays(new Date(2026, 6, 30), 2).getDate()).toBe(1);
|
expect(addDays(new Date(2026, 6, 30), 2).getDate()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Test 8: groupDayBySource gruppiert nach sourceId in Reihenfolge des ersten Auftretens', () => {
|
||||||
|
const day = new Date(2026, 6, 20, 9, 0);
|
||||||
|
const hourLater = new Date(2026, 6, 20, 10, 0);
|
||||||
|
|
||||||
|
// (a) drei Termine, alle s1 mit derselben Farbe
|
||||||
|
const sameSource = [
|
||||||
|
ev('e1', day, hourLater, { sourceId: 's1', color: '#c44040' }),
|
||||||
|
ev('e2', day, hourLater, { sourceId: 's1', color: '#c44040' }),
|
||||||
|
ev('e3', day, hourLater, { sourceId: 's1', color: '#c44040' }),
|
||||||
|
];
|
||||||
|
expect(groupDayBySource(sameSource)).toEqual([{ sourceId: 's1', color: '#c44040', count: 3 }]);
|
||||||
|
|
||||||
|
// (b) Reihenfolge s2, s1, s2 wird NICHT umsortiert
|
||||||
|
const mixedOrder = [
|
||||||
|
ev('e1', day, hourLater, { sourceId: 's2' }),
|
||||||
|
ev('e2', day, hourLater, { sourceId: 's1' }),
|
||||||
|
ev('e3', day, hourLater, { sourceId: 's2' }),
|
||||||
|
];
|
||||||
|
const groupedMixed = groupDayBySource(mixedOrder);
|
||||||
|
expect(groupedMixed.map((g) => g.sourceId)).toEqual(['s2', 's1']);
|
||||||
|
expect(groupedMixed[0].count).toBe(2);
|
||||||
|
expect(groupedMixed[1].count).toBe(1);
|
||||||
|
|
||||||
|
// (c) Termine ohne color -> color der Gruppe ist undefined
|
||||||
|
const noColor = [ev('e1', day, hourLater, { sourceId: 's1', color: undefined })];
|
||||||
|
expect(groupDayBySource(noColor)[0].color).toBeUndefined();
|
||||||
|
|
||||||
|
// (d) zwei Kalender mit derselben Farbe bleiben zwei Gruppen (Gruppierung nach sourceId)
|
||||||
|
const sameColorDifferentSource = [
|
||||||
|
ev('e1', day, hourLater, { sourceId: 's1', color: '#123456' }),
|
||||||
|
ev('e2', day, hourLater, { sourceId: 's2', color: '#123456' }),
|
||||||
|
];
|
||||||
|
expect(groupDayBySource(sameColorDifferentSource)).toHaveLength(2);
|
||||||
|
|
||||||
|
// (e) Farbe der Gruppe ist die Farbe des ERSTEN Termins der Gruppe
|
||||||
|
const laterColorChange = [
|
||||||
|
ev('e1', day, hourLater, { sourceId: 's1', color: '#111111' }),
|
||||||
|
ev('e2', day, hourLater, { sourceId: 's1', color: '#222222' }),
|
||||||
|
];
|
||||||
|
expect(groupDayBySource(laterColorChange)).toEqual([
|
||||||
|
{ sourceId: 's1', color: '#111111', count: 2 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// (f) leeres Array
|
||||||
|
expect(groupDayBySource([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 9: buildDayBadges liefert je Kalender einen Eintrag, ab dem vierten einen grauen Restkreis', () => {
|
||||||
|
const day = new Date(2026, 6, 20);
|
||||||
|
const single = [ev('e1', day, day, { sourceId: 's1', color: '#111111' })];
|
||||||
|
expect(buildDayBadges(single)).toEqual([
|
||||||
|
{ key: 's1', color: '#111111', count: 1, rest: false },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const two = [
|
||||||
|
ev('e1', day, day, { sourceId: 's1', color: '#111111' }),
|
||||||
|
ev('e2', day, day, { sourceId: 's2', color: '#222222' }),
|
||||||
|
];
|
||||||
|
expect(buildDayBadges(two)).toEqual([
|
||||||
|
{ key: 's1', color: '#111111', count: 1, rest: false },
|
||||||
|
{ key: 's2', color: '#222222', count: 1, rest: false },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const three = [
|
||||||
|
ev('e1', day, day, { sourceId: 's1', color: '#111111' }),
|
||||||
|
ev('e2', day, day, { sourceId: 's2', color: '#222222' }),
|
||||||
|
ev('e3', day, day, { sourceId: 's3', color: '#333333' }),
|
||||||
|
];
|
||||||
|
const threeResult = buildDayBadges(three);
|
||||||
|
expect(threeResult).toHaveLength(3);
|
||||||
|
expect(threeResult.every((b) => b.rest === false)).toBe(true);
|
||||||
|
|
||||||
|
const four = [
|
||||||
|
ev('e1', day, day, { sourceId: 's1', color: '#111111' }),
|
||||||
|
ev('e2a', day, day, { sourceId: 's2', color: '#222222' }),
|
||||||
|
ev('e2b', day, day, { sourceId: 's2', color: '#222222' }),
|
||||||
|
ev('e3', day, day, { sourceId: 's3', color: '#333333' }),
|
||||||
|
ev('e4a', day, day, { sourceId: 's4', color: '#444444' }),
|
||||||
|
ev('e4b', day, day, { sourceId: 's4', color: '#444444' }),
|
||||||
|
ev('e4c', day, day, { sourceId: 's4', color: '#444444' }),
|
||||||
|
];
|
||||||
|
expect(buildDayBadges(four)).toEqual([
|
||||||
|
{ key: 's1', color: '#111111', count: 1, rest: false },
|
||||||
|
{ key: 's2', color: '#222222', count: 2, rest: false },
|
||||||
|
{ key: '__rest__', color: undefined, count: 4, rest: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(buildDayBadges(four, 2)).toEqual([
|
||||||
|
{ key: 's1', color: '#111111', count: 1, rest: false },
|
||||||
|
{ key: '__rest__', color: undefined, count: 6, rest: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(buildDayBadges([])).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,15 @@
|
|||||||
* Starttag-Regel: mehrtaegige und ganztaegige Termine werden im Monatsraster
|
* Starttag-Regel: mehrtaegige und ganztaegige Termine werden im Monatsraster
|
||||||
* bewusst NUR am Starttag gezaehlt/angezeigt (`groupEventsByDate` gruppiert
|
* bewusst NUR am Starttag gezaehlt/angezeigt (`groupEventsByDate` gruppiert
|
||||||
* ausschliesslich nach `event.start`) — eine Terminleiste ueber mehrere Tage
|
* ausschliesslich nach `event.start`) — eine Terminleiste ueber mehrere Tage
|
||||||
* ist nicht Teil dieses Auftrags.
|
* ist nicht Teil dieses Auftrags. Jede Tagesgruppe ist zusaetzlich nach Start
|
||||||
|
* aufsteigend sortiert, damit die Plakettenfarbe (Farbe des ERSTEN Termins)
|
||||||
|
* und die Tooltip-Reihenfolge deterministisch uebereinstimmen (quick-260916-jvj).
|
||||||
|
*
|
||||||
|
* Liegen an einem Tag Termine aus mehreren Kalendern, gruppiert
|
||||||
|
* `groupDayBySource` sie nach `sourceId` (nicht nach Farbe) in der Reihenfolge
|
||||||
|
* ihres ersten Auftretens in der bereits start-sortierten Tagesgruppe;
|
||||||
|
* `buildDayBadges` macht daraus bis zu drei Kreise, ab dem vierten Kalender
|
||||||
|
* einen grauen Restkreis mit der Summe der uebrigen (quick-260916-k2z).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CalendarEvent } from '@/lib/calendar-api';
|
import type { CalendarEvent } from '@/lib/calendar-api';
|
||||||
@@ -101,7 +109,9 @@ export interface CalendarDay {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gruppiert Termine nach lokalem Startdatum. Mehrtaegige/ganztaegige Termine
|
* Gruppiert Termine nach lokalem Startdatum. Mehrtaegige/ganztaegige Termine
|
||||||
* zaehlen bewusst nur am Starttag (siehe Kopfkommentar).
|
* zaehlen bewusst nur am Starttag (siehe Kopfkommentar). Jede Tagesgruppe
|
||||||
|
* wird nach Start aufsteigend sortiert (quick-260916-jvj), damit der erste
|
||||||
|
* Eintrag zuverlaessig der fruehesten Termin des Tages ist.
|
||||||
*/
|
*/
|
||||||
export function groupEventsByDate(events: CalendarEvent[]): Map<string, CalendarEvent[]> {
|
export function groupEventsByDate(events: CalendarEvent[]): Map<string, CalendarEvent[]> {
|
||||||
const groupedEvents = new Map<string, CalendarEvent[]>();
|
const groupedEvents = new Map<string, CalendarEvent[]>();
|
||||||
@@ -110,9 +120,77 @@ export function groupEventsByDate(events: CalendarEvent[]): Map<string, Calendar
|
|||||||
const existingEvents = groupedEvents.get(key) ?? [];
|
const existingEvents = groupedEvents.get(key) ?? [];
|
||||||
groupedEvents.set(key, [...existingEvents, event]);
|
groupedEvents.set(key, [...existingEvents, event]);
|
||||||
}
|
}
|
||||||
|
for (const dayEvents of groupedEvents.values()) {
|
||||||
|
dayEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
||||||
|
}
|
||||||
return groupedEvents;
|
return groupedEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DaySourceGroup {
|
||||||
|
sourceId: string;
|
||||||
|
color?: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gruppiert die Termine EINES Tages nach `sourceId` (nicht nach Farbe).
|
||||||
|
* Reihenfolge = erstes Auftreten in `events` — die Funktion sortiert selbst
|
||||||
|
* NICHT, sie uebernimmt die Reihenfolge von `groupEventsByDate` (dort bereits
|
||||||
|
* nach Start sortiert), damit Kreis- und Tooltip-Reihenfolge uebereinstimmen.
|
||||||
|
* Die Farbe einer Gruppe ist die `color` ihres ERSTEN Termins.
|
||||||
|
*/
|
||||||
|
export function groupDayBySource(events: CalendarEvent[]): DaySourceGroup[] {
|
||||||
|
const groups = new Map<string, DaySourceGroup>();
|
||||||
|
for (const event of events) {
|
||||||
|
const existing = groups.get(event.sourceId);
|
||||||
|
if (existing) {
|
||||||
|
existing.count += 1;
|
||||||
|
} else {
|
||||||
|
groups.set(event.sourceId, { sourceId: event.sourceId, color: event.color, count: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(groups.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CALENDAR_DAY_BADGE_MAX = 3;
|
||||||
|
|
||||||
|
export interface DayBadge {
|
||||||
|
key: string;
|
||||||
|
color?: string;
|
||||||
|
count: number;
|
||||||
|
rest: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die Plaketten-Kreise fuer einen Tag: bis `max` Kalender bekommen je
|
||||||
|
* einen Kreis, ab `max` Kalendern fassen die ersten `max - 1` Kreise je einen
|
||||||
|
* Kalender, der letzte Eintrag (`rest: true`) summiert die uebrigen Kalender
|
||||||
|
* grau (kein Farbwert).
|
||||||
|
*/
|
||||||
|
export function buildDayBadges(
|
||||||
|
events: CalendarEvent[],
|
||||||
|
max: number = CALENDAR_DAY_BADGE_MAX,
|
||||||
|
): DayBadge[] {
|
||||||
|
const groups = groupDayBySource(events);
|
||||||
|
if (groups.length <= max) {
|
||||||
|
return groups.map((group) => ({
|
||||||
|
key: group.sourceId,
|
||||||
|
color: group.color,
|
||||||
|
count: group.count,
|
||||||
|
rest: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const visible = groups.slice(0, max - 1).map((group) => ({
|
||||||
|
key: group.sourceId,
|
||||||
|
color: group.color,
|
||||||
|
count: group.count,
|
||||||
|
rest: false,
|
||||||
|
}));
|
||||||
|
const restCount = groups.slice(max - 1).reduce((sum, group) => sum + group.count, 0);
|
||||||
|
return [...visible, { key: '__rest__', color: undefined, count: restCount, rest: true }];
|
||||||
|
}
|
||||||
|
|
||||||
/** 42 Rasterzellen ab `gridStartFor(monthDate)`, Montag-basiert. */
|
/** 42 Rasterzellen ab `gridStartFor(monthDate)`, Montag-basiert. */
|
||||||
export function buildCalendarDays(
|
export function buildCalendarDays(
|
||||||
monthDate: Date,
|
monthDate: Date,
|
||||||
|
|||||||
@@ -146,6 +146,163 @@ describe('CalendarWidget', () => {
|
|||||||
expect(within(day22).queryByTestId('calendar-day-count')).toBeNull();
|
expect(within(day22).queryByTestId('calendar-day-count')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Test 3b: Plakette traegt die Kalenderfarbe des fruehesten Termins, Tooltip-Zeilen mit Farbpunkt', async () => {
|
||||||
|
mockFetchEvents.mockResolvedValue([
|
||||||
|
ev('Lunch', new Date(2026, 6, 20, 14, 0), new Date(2026, 6, 20, 15, 0), {
|
||||||
|
color: '#4060c4',
|
||||||
|
}),
|
||||||
|
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0), {
|
||||||
|
color: '#c44040',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { CalendarWidget } = await import('./calendar-widget');
|
||||||
|
render(<CalendarWidget instanceId="cal-3b" config={{}} isEditMode={false} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const day20 = document.querySelector('[data-date="2026-07-20"]') as HTMLElement;
|
||||||
|
const badge = within(day20).getByTestId('calendar-day-count');
|
||||||
|
expect(badge).toHaveTextContent('2');
|
||||||
|
expect(badge).toHaveStyle({ backgroundColor: '#c44040' });
|
||||||
|
expect(badge).toHaveClass('text-white');
|
||||||
|
expect(badge).not.toHaveClass('bg-primary');
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(day20);
|
||||||
|
const tooltip = screen.getByTestId('calendar-day-tooltip');
|
||||||
|
const dots = within(tooltip).getAllByTestId('tooltip-color-dot');
|
||||||
|
expect(dots).toHaveLength(2);
|
||||||
|
expect(dots[0]).toHaveStyle({ backgroundColor: '#c44040' });
|
||||||
|
expect(dots[1]).toHaveStyle({ backgroundColor: '#4060c4' });
|
||||||
|
expect(tooltip.textContent?.indexOf('Team Meeting')).toBeLessThan(
|
||||||
|
tooltip.textContent?.indexOf('Lunch') ?? -1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 3c: Plakette ohne Kalenderfarbe behaelt bg-primary', async () => {
|
||||||
|
mockFetchEvents.mockResolvedValue([
|
||||||
|
ev('Ohne Farbe', new Date(2026, 6, 21, 9, 0), new Date(2026, 6, 21, 10, 0), {
|
||||||
|
color: undefined,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { CalendarWidget } = await import('./calendar-widget');
|
||||||
|
render(<CalendarWidget instanceId="cal-3c" config={{}} isEditMode={false} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const day21 = document.querySelector('[data-date="2026-07-21"]') as HTMLElement;
|
||||||
|
const badge = within(day21).getByTestId('calendar-day-count');
|
||||||
|
expect(badge).toHaveClass('bg-primary');
|
||||||
|
expect(badge).toHaveClass('text-primary-foreground');
|
||||||
|
expect(badge).not.toHaveClass('text-white');
|
||||||
|
expect((badge as HTMLElement).style.backgroundColor).toBe('');
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(day21);
|
||||||
|
const tooltip = screen.getByTestId('calendar-day-tooltip');
|
||||||
|
expect(within(tooltip).getAllByTestId('tooltip-color-dot')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 3d: zwei Kalender am selben Tag zeigen zwei kleine Kreise in Startreihenfolge', async () => {
|
||||||
|
mockFetchEvents.mockResolvedValue([
|
||||||
|
ev('Lunch', new Date(2026, 6, 20, 10, 0), new Date(2026, 6, 20, 11, 0), {
|
||||||
|
sourceId: 's2',
|
||||||
|
color: '#4060c4',
|
||||||
|
}),
|
||||||
|
ev('Kaffee', new Date(2026, 6, 20, 14, 0), new Date(2026, 6, 20, 15, 0), {
|
||||||
|
sourceId: 's2',
|
||||||
|
color: '#4060c4',
|
||||||
|
}),
|
||||||
|
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0), {
|
||||||
|
sourceId: 's1',
|
||||||
|
color: '#c44040',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { CalendarWidget } = await import('./calendar-widget');
|
||||||
|
render(<CalendarWidget instanceId="cal-3d" config={{}} isEditMode={false} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const day20 = document.querySelector('[data-date="2026-07-20"]') as HTMLElement;
|
||||||
|
const badges = within(day20).getAllByTestId('calendar-day-count');
|
||||||
|
expect(badges).toHaveLength(2);
|
||||||
|
expect(badges[0]).toHaveTextContent('1');
|
||||||
|
expect(badges[0]).toHaveStyle({ backgroundColor: '#c44040' });
|
||||||
|
expect(badges[1]).toHaveTextContent('2');
|
||||||
|
expect(badges[1]).toHaveStyle({ backgroundColor: '#4060c4' });
|
||||||
|
for (const badge of badges) {
|
||||||
|
expect(badge).toHaveClass('text-white');
|
||||||
|
expect(badge).not.toHaveClass('bg-primary');
|
||||||
|
expect(badge).toHaveClass('h-[clamp(8px,2.4cqw,12px)]');
|
||||||
|
}
|
||||||
|
expect(within(day20).queryByTestId('calendar-day-count-rest')).toBeNull();
|
||||||
|
expect(within(day20).getByTestId('calendar-day-badges').childElementCount).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 3e: vier Kalender am selben Tag zeigen zwei Kreise plus grauen Restkreis mit Summe', async () => {
|
||||||
|
mockFetchEvents.mockResolvedValue([
|
||||||
|
ev('e1', new Date(2026, 6, 21, 8, 0), new Date(2026, 6, 21, 9, 0), {
|
||||||
|
sourceId: 's1',
|
||||||
|
color: '#111111',
|
||||||
|
}),
|
||||||
|
ev('e2a', new Date(2026, 6, 21, 9, 0), new Date(2026, 6, 21, 9, 30), {
|
||||||
|
sourceId: 's2',
|
||||||
|
color: '#222222',
|
||||||
|
}),
|
||||||
|
ev('e2b', new Date(2026, 6, 21, 9, 30), new Date(2026, 6, 21, 10, 0), {
|
||||||
|
sourceId: 's2',
|
||||||
|
color: '#222222',
|
||||||
|
}),
|
||||||
|
ev('e3', new Date(2026, 6, 21, 10, 0), new Date(2026, 6, 21, 11, 0), {
|
||||||
|
sourceId: 's3',
|
||||||
|
color: undefined,
|
||||||
|
}),
|
||||||
|
ev('e4a', new Date(2026, 6, 21, 11, 0), new Date(2026, 6, 21, 12, 0), {
|
||||||
|
sourceId: 's4',
|
||||||
|
color: '#444444',
|
||||||
|
}),
|
||||||
|
ev('e4b', new Date(2026, 6, 21, 12, 0), new Date(2026, 6, 21, 13, 0), {
|
||||||
|
sourceId: 's4',
|
||||||
|
color: '#444444',
|
||||||
|
}),
|
||||||
|
ev('e4c', new Date(2026, 6, 21, 13, 0), new Date(2026, 6, 21, 14, 0), {
|
||||||
|
sourceId: 's4',
|
||||||
|
color: '#444444',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { CalendarWidget } = await import('./calendar-widget');
|
||||||
|
render(<CalendarWidget instanceId="cal-3e" config={{}} isEditMode={false} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const day21 = document.querySelector('[data-date="2026-07-21"]') as HTMLElement;
|
||||||
|
expect(within(day21).getByTestId('calendar-day-badges').childElementCount).toBe(3);
|
||||||
|
|
||||||
|
const badges = within(day21).getAllByTestId('calendar-day-count');
|
||||||
|
expect(badges).toHaveLength(2);
|
||||||
|
expect(badges[0]).toHaveTextContent('1');
|
||||||
|
expect(badges[0]).toHaveStyle({ backgroundColor: '#111111' });
|
||||||
|
expect(badges[1]).toHaveTextContent('2');
|
||||||
|
expect(badges[1]).toHaveStyle({ backgroundColor: '#222222' });
|
||||||
|
|
||||||
|
const restBadge = within(day21).getByTestId('calendar-day-count-rest');
|
||||||
|
expect(restBadge).toHaveTextContent('4');
|
||||||
|
expect(restBadge).toHaveClass('bg-muted-foreground');
|
||||||
|
expect(restBadge).toHaveClass('text-background');
|
||||||
|
expect((restBadge as HTMLElement).style.backgroundColor).toBe('');
|
||||||
|
expect(restBadge).not.toHaveClass('text-white');
|
||||||
|
});
|
||||||
|
|
||||||
it('Test 4: Tooltip erscheint per Portal im body und zeigt bis zu 5 Termine plus Hinweis', async () => {
|
it('Test 4: Tooltip erscheint per Portal im body und zeigt bis zu 5 Termine plus Hinweis', async () => {
|
||||||
mockFetchEvents.mockResolvedValue([
|
mockFetchEvents.mockResolvedValue([
|
||||||
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0)),
|
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0)),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { fetchEvents, fetchSources } from '@/lib/calendar-api';
|
|||||||
import type { CalendarEvent } from '@/lib/calendar-api';
|
import type { CalendarEvent } from '@/lib/calendar-api';
|
||||||
import {
|
import {
|
||||||
buildCalendarDays,
|
buildCalendarDays,
|
||||||
|
buildDayBadges,
|
||||||
computeFetchWindow,
|
computeFetchWindow,
|
||||||
formatEventDate,
|
formatEventDate,
|
||||||
formatEventTime,
|
formatEventTime,
|
||||||
@@ -30,7 +31,11 @@ const TOOLTIP_EDGE_PX = 4;
|
|||||||
*
|
*
|
||||||
* Oben ein Monatsraster (Nav-Zeile Zurueck/Monat/Weiter, Wochentagskopf,
|
* Oben ein Monatsraster (Nav-Zeile Zurueck/Monat/Weiter, Wochentagskopf,
|
||||||
* 42 Zellen ab Montag, Faehrmonatstage gedaempft, heutiger Tag hervorgehoben,
|
* 42 Zellen ab Montag, Faehrmonatstage gedaempft, heutiger Tag hervorgehoben,
|
||||||
* Zaehl-Plakette an Tagen mit Terminen, Tooltip beim Ueberfahren), darunter
|
* Zaehl-Plakette an Tagen mit Terminen in der Farbe des Kalenders (ein
|
||||||
|
* Kalender am Tag); bei mehreren Kalendern am selben Tag je ein kleiner
|
||||||
|
* Kreis pro Kalender (max. drei, danach zwei plus grauer Restkreis mit
|
||||||
|
* Summe, Logik in `buildDayBadges`, quick-260916-k2z), Tooltip beim
|
||||||
|
* Ueberfahren mit Farbpunkt je Zeile (quick-260916-jvj)), darunter
|
||||||
* der Block "Naechste Termine". Beide Bloecke lassen sich unabhaengig
|
* der Block "Naechste Termine". Beide Bloecke lassen sich unabhaengig
|
||||||
* ein-/ausblenden (Config `showMonth`/`maxEvents`, siehe calendar-month.ts).
|
* ein-/ausblenden (Config `showMonth`/`maxEvents`, siehe calendar-month.ts).
|
||||||
*
|
*
|
||||||
@@ -217,6 +222,8 @@ export function CalendarWidget({ config }: WidgetProps) {
|
|||||||
<div className="grid grid-cols-7 gap-px">
|
<div className="grid grid-cols-7 gap-px">
|
||||||
{days.map((day) => {
|
{days.map((day) => {
|
||||||
const hasEvents = day.events.length > 0;
|
const hasEvents = day.events.length > 0;
|
||||||
|
const badges = buildDayBadges(day.events);
|
||||||
|
const single = badges.length === 1;
|
||||||
const cellClass = [
|
const cellClass = [
|
||||||
'widgetNoDrag relative flex min-h-[clamp(16px,5.5cqh,40px)] items-start rounded bg-muted/50 px-1 py-0.5 text-[clamp(9px,2.4cqw,13px)] leading-none',
|
'widgetNoDrag relative flex min-h-[clamp(16px,5.5cqh,40px)] items-start rounded bg-muted/50 px-1 py-0.5 text-[clamp(9px,2.4cqw,13px)] leading-none',
|
||||||
day.inCurrentMonth ? 'text-foreground' : 'text-muted-foreground/60',
|
day.inCurrentMonth ? 'text-foreground' : 'text-muted-foreground/60',
|
||||||
@@ -242,11 +249,37 @@ export function CalendarWidget({ config }: WidgetProps) {
|
|||||||
>
|
>
|
||||||
<span>{day.date.getDate()}</span>
|
<span>{day.date.getDate()}</span>
|
||||||
{hasEvents && (
|
{hasEvents && (
|
||||||
|
// Wrapper traegt die Positionierung, die einzelnen Kreise nicht
|
||||||
|
// mehr (mehrere Kreise stehen sonst uebereinander).
|
||||||
<span
|
<span
|
||||||
data-testid="calendar-day-count"
|
data-testid="calendar-day-badges"
|
||||||
className="absolute bottom-px right-px flex h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] items-center justify-center rounded-full bg-primary px-0.5 text-[clamp(7px,1.8cqw,10px)] font-semibold leading-none text-primary-foreground"
|
className="absolute bottom-px right-px flex items-end gap-px"
|
||||||
>
|
>
|
||||||
{day.events.length}
|
{badges.map((badge) => {
|
||||||
|
const badgeClass = [
|
||||||
|
'flex items-center justify-center rounded-full px-0.5 font-semibold leading-none',
|
||||||
|
single
|
||||||
|
? 'h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] text-[clamp(7px,1.8cqw,10px)]'
|
||||||
|
: 'h-[clamp(8px,2.4cqw,12px)] min-w-[clamp(8px,2.4cqw,12px)] text-[clamp(6px,1.5cqw,8px)]',
|
||||||
|
badge.rest
|
||||||
|
? 'bg-muted-foreground text-background'
|
||||||
|
: badge.color
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-primary text-primary-foreground',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={badge.key}
|
||||||
|
data-testid={badge.rest ? 'calendar-day-count-rest' : 'calendar-day-count'}
|
||||||
|
className={badgeClass}
|
||||||
|
style={badge.color && !badge.rest ? { backgroundColor: badge.color } : undefined}
|
||||||
|
>
|
||||||
|
{badge.count}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -313,6 +346,12 @@ export function CalendarWidget({ config }: WidgetProps) {
|
|||||||
>
|
>
|
||||||
{hoverEvents.slice(0, 5).map((event) => (
|
{hoverEvents.slice(0, 5).map((event) => (
|
||||||
<div key={event.id} className="flex gap-2">
|
<div key={event.id} className="flex gap-2">
|
||||||
|
<span
|
||||||
|
data-testid="tooltip-color-dot"
|
||||||
|
className="mt-1 h-2 w-2 shrink-0 rounded-full"
|
||||||
|
style={{ backgroundColor: event.color || 'var(--muted-foreground)' }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<span className="shrink-0 tabular-nums text-muted-foreground">
|
<span className="shrink-0 tabular-nums text-muted-foreground">
|
||||||
{event.allDay ? t('calendar.allDay') : formatEventTime(event.start)}
|
{event.allDay ? t('calendar.allDay') : formatEventTime(event.start)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user