Commit Graph

93 Commits

Author SHA1 Message Date
schalli 709b41a007 feat(260929-if2): faellige Erinnerungen erledigen, spaeter erinnern, bearbeiten und loeschen
- API: Aendern (409 wenn faellig), Spaeter erinnern (409 wenn nicht faellig, setzt E-Mail-Spur zurueck), Loeschen; fremde Kennungen 404
- Kachel: faellige Zeilen hervorgehoben mit Erledigt und drei Spaeter-Optionen, kuenftige mit Bearbeiten und Loeschen
- Zeit-Hilfen (morgen zur gleichen Uhrzeit), Zugriffsklassifikation nachgemessen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 14:11:22 +02:00
schalli 325c5ddbf2 feat(260929-if2): Erinnerungen anlegen und zur Faelligkeit benachrichtigen (Tracer)
- Reminder-Tabelle mit Zeilenschutz (Mandant+Benutzer, Systemlesen fuer den E-Mail-Planer), API reminders (Liste, Anlegen)
- Kachel "Erinnerungen", globaler Melder im Portalrahmen (Browser und Desktop, je Faelligkeit einmal)
- Desktop: Laufzeit-Berechtigung fuer Benachrichtigungen nur fuer die gespeicherte Server-Adresse
- Zugriffsklassifikation nachgemessen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 14:11:22 +02:00
schalli ee97b4ed9f feat(260929-dzu): Einstellungen > Eigene Module fuer jeden Benutzer, Verwaltung nur gemeinsam
- gemeinsame Oberflaeche (CustomModuleManager, Formular, Loeschdialog) fuer Verwaltung und Einstellungen
- Einstellungen: persoenliche Eintraege, Verwaltung sendet shared: true und zeigt nur gemeinsame
- Navigation, Texte de/en, Komponententests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:17:22 +02:00
schalli 9c9e1420fe feat(260929-dmx): Widget-Breiten im 48er-Raster, Kalender schmaler ziehbar
- minW/defaultW aller Widgets verdoppelt (gleiche Bildschirmbreite)
- Kalender minW 8 (rund 250 px), defaultW 16
- Test: bestehender Kalender bekommt das neue Minimum in jedem Breakpoint

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:53:08 +02:00
schalli 97744b59cd feat(260929-dmx): Widget-Raster horizontal doppelt so fein (48 Spalten, Version 3)
- COLS lg 48 / md 40 / sm 24 / xs 16 / xxs 4, Zeilenhoehe und Abstand unveraendert
- Migration stufenweise: v1->v2 wie bisher, v2->v3 nur x/w/minW/maxW x2
- Rueckfallwerte fuer Widgets ohne Eintrag auf 8 Einheiten

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:51:26 +02:00
schalli b9d87be360 feat(api,web): eigene Module — Tabelle, API, Seitenleiste, Rahmen-Seite
- Tabelle CustomModule mit Zeilenschutz (tenant_isolation_policy), Migration 20260929120000
- API /custom-modules: Lesen für jeden Angemeldeten, Schreiben nur Administrator, nur https ohne Zugangsdaten
- Seitenleiste zeigt eigene Module unter ihrer Kategorie, Rahmen-Seite mit Sandbox und „In neuem Tab öffnen“
- MODULE_CATEGORIES als gemeinsame Liste, Zugriffsklassifikation nachgemessen fortgeschrieben

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 07:25:33 +02:00
schalli 12eea333ba feat(web): Widget-Titel je Widget ausblenden, Seitenleiste gegliedert
Tessera CI/CD / Build & Publish Images (push) Successful in 2m56s
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m17s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m24s
Neuer Schalter im Bearbeitungsmodus (neben dem Griff) setzt hideTitle in
der Widget-Konfiguration (setWidgetConfig im Dashboard-Store, optimistisch
mit Ruecksetzen); in der Ansicht entfaellt die Titelzeile per
data-hide-title, Aktionen der Titelzeile bleiben oben rechts. Kategorien der
Seitenleiste 14 px, Moduleintraege 13 px / 32 px hoch. Version 1.5.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:21:26 +02:00
schalli 69d17302e1 fix(260928-ujj): Biome-Formatierung der mit Design Mosaik eingebrachten Dateien
- Nur Formatierung und Importreihenfolge, keine Verhaltensaenderung
- Betrifft ausschliesslich Befunde, die der Merge neu eingebracht hat

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:14:57 +02:00
schalli 0aaa15240d feat(260928-ujj): Dashboard-Hintergrund pro Benutzer in der Datenbank
- Spalte User.dashboardBackground (JSONB) samt Migration
- PATCH /users/me/dashboard-background, geprueft mit parseDashboardBackground aus @tessera/shared (Allowlist, UUID-Bildkennung)
- getMe liefert dashboardBackground normalisiert neben accentColor
- Web liest die Wahl aus dem Auth-Store, speichert ueber die Server-Aktion, alte localStorage-Wahl wird einmalig uebernommen
- Hinweistext: gilt auf jedem Geraet

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:13:49 +02:00
schalli 76f6d87973 test(260928-ujj): rote Tests fuer Dashboard-Hintergrund in der Datenbank
- PATCH me/dashboard-background: Allowlist, UUID-Bildkennung, Mandantenbindung
- getMe liefert dashboardBackground normalisiert
- Web: Uebernahme der alten localStorage-Wahl, Hook liest aus dem Auth-Store

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:08:00 +02:00
schalli 8532b63097 feat(web): Design Mosaik Runde 3 - Reiter in der Seitenleiste, lebendigere Kacheln
- Dashboard-Reiter stehen in der aufgeklappten Seitenleiste als Liste
  unter "Dashboard" (Ziehen sortiert senkrecht, Umbenennen/Loeschen/Neu
  im Bearbeitungsmodus); eingeklappt und auf dem Handy wie bisher in der
  Kopfzeile. Die Kopfzeile zeigt sonst den Namen des Dashboards.
- Begruessung einzeilig auf Hoehe der Befehlsleiste.
- Kachel-Groesse: kollidiert die gezogene Groesse mit einem Nachbarn, wird
  nur die freie Achse uebernommen - vorher verwarf react-grid-layout mit
  preventCollision die ganze Aenderung, sobald die Maus eine Zeile nach
  unten wackelte (Favoriten liessen sich deshalb oft nicht schmaler ziehen).
- Ansicht: belegte Spalten mittig, Begruessung/Befehlsleiste ruecken mit.
- Kalender: einzeilige Terminliste mit "Heute"/"Morgen" in Akzentfarbe,
  Ort im Tooltip, Standardhoehe 16 statt 12.
- Hintergrund "Bluete" im Dunkelmodus weggelassen (Farbstufen), Ersatz Nebel.
- Kacheln: Symbol-Chip in der Akzentfarbe, leichtes Anheben beim Zeigen,
  gestaffeltes Einblenden beim Laden (ohne bei reduzierter Bewegung).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 15:58:01 +02:00
schalli 228112014c style(web): Modul-Kacheln einfarbig, Akzent nur fuer das Modul im Fokus
Statt bunter Modulfarben neutrale graue Kacheln; gelb (persoenliche
Akzentfarbe) nur fuer den gewaehlten Eintrag der Seitenleiste und den
Seitenkopf des Moduls. Schrift auf Akzentflaechen wird jetzt je nach
Akzentfarbe hell oder dunkel gewaehlt (readableOnAccent). Anmelde-
Mosaik und Dashboard-Hintergruende nur noch in Gelb, Grau, Graphit;
Salbei ersetzt durch Kiesel.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:09:05 +02:00
schalli 76520b2010 fix(web): mobile Schublade ueber der App-Leiste, Filterpillen ohne Leiste
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:54:10 +02:00
schalli 219038b3ee feat(web): Dashboard-Kacheln im Fluent-Stil und waehlbarer Hintergrund
Einheitliche Kachel-Kopfzeile, ruhigere Notiz-/Kalender-/Favoriten-
Darstellung, 12 px Raster-Abstand, gestrichelte Kontur und Griff im
Bearbeitungsmodus. Neuer Knopf Hintergrund: Keiner, fuenf eingebaute
(Nebel, Salbei, Bluete, Duenen, Mosaik) oder eigenes Bild aus den
Bilderrahmen-Bildern; Prototyp speichert im localStorage je Benutzer.
Mit Hintergrund werden Kacheln Mica-artig durchscheinend (Kontrast
>= 4,5:1 auch im schlechtesten Fall).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:49:01 +02:00
schalli b80db49c5e style(web): dunkle App-Leiste und Fluent-Seitenleiste
Kopfzeile 48 px dunkel mit Seitentitel statt immer Startseite,
Dashboard-Reiter mit Akzent-Unterstrich; Seitenleiste mit
Auswahlpille, Modul-Kacheln, aufgeklappten Kategorien und gefuelltem
Suchfeld; eingeklappt nur Kacheln. Einheitlicher PageHeader.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:32:37 +02:00
schalli 7192be223a feat(web): Modul-Identitaet mit App-Kacheln und deutschen Kategorienamen
Jedes Modul bekommt Farbe und Symbol (tender-radar Ocker, dkv-fleet
Petrol, cert-manager Violett, domaincheck Blau, proxmox Rotorange,
unbekannte stabil per Hash). Kategorien in Alltagssprache; dazu die
Texte fuer die neue Anmeldeseite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:32:37 +02:00
schalli 3c62a1a29c style(web): Design-Tokens Mosaik (Fluent 2 / GitHub)
Kuehlgraue Arbeitsflaeche, weisse Karten mit Fluent-Schatten statt
Rahmen, dunkle App-Leiste, Link-Blau, neutraler Fokusring, 6/8/12 px
Radien, Segoe-UI-Schriftstapel, Knopf-/Link-Klassen, Fluent-Eingabefeld
und reduzierte Bewegung. Die persoenliche Akzentfarbe setzt nur noch
--primary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:32:37 +02:00
schalli b3b7b5d5e5 docs(260925-bow): Was-ist-neu-Fenster in CHANGELOG, Anwender- und Entwicklerdoku
- CHANGELOG Unveroeffentlicht -> Neu
- Anwenderhandbuch: Fenster nach einem Versionswechsel
- Entwicklerdoku: erweiterte Importregel fuer @/lib/changelog, Versionsquelle,
  Endpunkte, Spalte, Folge fuer die Freigabe
- Kopfkommentare changelog.ts und next.config.ts angepasst

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:54:38 +02:00
schalli 187fb76c91 feat(260925-bow): Was-ist-neu-Fenster im Portal-Rahmen
- release-notes.ts: reine Auswahl der Versionsabschnitte aus CHANGELOG.md
- release-notice-actions.ts ('use server'): Abruf und Merken ueber die API
- ReleaseNoticeDialog: barrierefreies Fenster, Eintraege ueber ChangelogView (variant plain)
- ReleaseNoticeHost in AppShell: einmal je Seitenladung, merkt erst beim Schliessen
- Texte releaseNotice in de.json und en.json

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:48:25 +02:00
schalli cf7784e409 test(260925-bow): Auswahl, Server-Aktionen, Fenster und Host des Was-ist-neu-Fensters
- selectReleaseNotice: Bereich, Deckel 3, null, unparsebar, nur Neu/Geaendert/Behoben
- Server-Aktionen: Cookie, Antwortform, Fehler still
- Fenster: role=dialog, Fokusfalle, alle Schliesswege genau einmal
- Host: merkt erst beim Schliessen, nicht auf /change-password, StrictMode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:48:25 +02:00
schalli 61f95c8c52 feat(260923-lrr): Web — versionierte Symbol-Adresse, Datei-Auswahl, Entfernen-Knopf
- favorites-api.ts: FavoriteRequestError (Grund iconUrlUnreachable/iconTooLarge/
  iconInvalidType/iconUploadFailed), uploadFavoriteIcon/removeFavoriteIcon,
  FAVORITE_ICON_MAX_BYTES, Felder uploadedIconMime/iconVersion
- favorites-widget.tsx: Proxy-Bild traegt ?v=<iconVersion> (Cache-Bust nach
  Aenderung), Remount-Key um iconVersion/uploadedIconMime erweitert, eigenes
  Symbol im Hinzufuegen- und Bearbeitungsformular waehlen, Entfernen-Knopf,
  Fehlermeldung (role=alert) im Formular, Platzhalter jetzt uebersetzbar
- de.json/en.json: neun neue Texte unter widgets.favorites
- CHANGELOG.md und Anwenderhandbuch ergaenzt

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:13:05 +02:00
schalli bf4384ad73 fix(bug-report): Bildschirmfoto scheitert nicht mehr an einem fremden Bild
html-to-image laedt jedes <img> per fetch nach; ein einziges Bild ohne CORS
(z. B. ein direkt von der Website geholtes Favoriten-Symbol) liess die ganze
Aufnahme scheitern, im Dialog blieb das Haekchen "Bildschirmfoto beifuegen"
gesperrt. Nicht ladbare Bilder werden jetzt zum transparenten Pixel; scheitert
die Aufnahme trotzdem, folgt ein zweiter Versuch ohne Bilder und Rahmen.
Im Linux-Client 1.3.1 nachgestellt und nach dem Fix gegengeprueft.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:52:02 +02:00
schalli 710034c80a fix(260923-ku6): Verbindungstest prueft Formularwerte statt gespeicherten Stand
Nachbesserung aus dem Browser-Rundgang zu 260923-dhh (Befund 1): "Verbindung
testen" pruefte bislang immer den in der Datenbank gespeicherten Server, nicht
das ungespeicherte Formular. Eine im Formular abgeschaltete Zertifikatspruefung
oder ein neu eingetipptes Token-Geheimnis wurden dadurch beim Test ignoriert
und erst nach "Speichern" wirksam — eine Falle fuer genau den Ablauf, den
Nutzer instinktiv waehlen (eintippen, testen, dann erst speichern).

Neues `TestProxmoxServerDto` plus Merge-Baustein `resolveEffectiveTestServer`
in `ProxmoxService`: normale Felder folgen dem Formular (auch wenn absichtlich
geleert), Geheimnisfelder folgen der bestehenden "leer -> gespeicherten Wert
behalten"-Regel, weil `ServerForm` sie beim Laden nie aus der Datenbank
vorbefuellt. Neue Route `POST servers/test` (ohne `:id`) deckt die Neuanlage
ab, wo es noch keinen gespeicherten Server gibt. Der Testen-Knopf steht jetzt
immer zur Verfuegung, nicht mehr nur nach dem ersten Speichern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:11:56 +02:00
schalli 06fcdc0157 feat(260923-dhh): Proxmox-Modul Aufgabe 6 - Modulseite mit Auslastung
- ServerCard.tsx: einzige Stelle, die einen Messwert in Text verwandelt
  (formatMetric) — null wird ueberall "unbekannt", nie 0/NaN/leer;
  verzweigt ueber productType auf PVE/PBS/PMG; PBS ohne Sicherung zeigt
  "noch keine Sicherung" statt eines Fehlers; nicht erreichbarer Server
  zeigt Klartext-Ursache plus Zeitpunkt der letzten erfolgreichen Messung
- page.tsx: "Jetzt aktualisieren" fragt alle Server neu ab und laedt die
  Liste danach neu, waehrend des Laufs gesperrt; ruhiger Hinweis bei
  leerer Liste mit Weg zu den Einstellungen; Link zu den Einstellungen
  nur fuer Administratoren sichtbar (Anzeige, kein Zugriffsriegel)
- proxmox-api.ts: ProxmoxMetrics-Union (Pve/Pbs/Pmg) fuer typsichere
  Verzweigung im Frontend
- umlaut-dictionary.ts: zwei weitere korrekte Woerter auf die
  Positivliste (Messung, Prozessorlast)

Tore: web 708/708 (>=693), type-check 4/4, Biome apps/web 53 Warnungen
(unveraendert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 14:45:27 +02:00
schalli 723cf6814b feat(260923-dhh): Proxmox-Modul Aufgabe 5 - Einstellungsseite (anlegen, bearbeiten, loeschen, testen)
- proxmox.service.ts: updateServer (Muster LdapConfigService.updateConfig
  - nicht gesendet laesst unveraendert, leer loescht, gefuellt
  verschluesselt neu; PMG+Token auch beim Bearbeiten abgelehnt, geprueft
  gegen den EFFEKTIVEN Stand nach Zusammenfuehren), deleteServer
- proxmox.controller.ts: PUT/DELETE servers/:id, beide zusaetzlich mit
  scheduler.refreshTenant() nach dem Schreiben
- Frontend: proxmox-api.ts (updateServer/deleteServer/testServer),
  settings/page.tsx (Rollenpruefung nur Anzeige, Serverliste,
  Loeschen mit Rueckfrage), ServerForm.tsx (PMG bietet Token gar nicht
  an, Geheimnisfelder nie vorbefuellt, Zertifikatspruefung-Schalter
  Standard "pruefen", Verbindungstest mit Klartext-Fehlertext)
- umlaut-dictionary.ts: zwei neue, bereits korrekte Woerter
  (bewusst/gemessene) auf die Positivliste des Regressions-Waechters

Tore: api 1311/1311 (>=1240), web 701/701 (>=693), type-check 4/4,
lint 5/5, Biome apps/web 53 Warnungen (unveraendert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 14:41:21 +02:00
schalli 3a1bfd943e feat(260923-dhh): Proxmox-Modul Aufgabe 1 - PVE per Token, Ende-zu-Ende
- ProxmoxServer/ProxmoxServerStatus mit RLS (tenant_isolation_policy +
  system_read_policy auf ProxmoxServer fuer den kommenden Planer)
- proxmox-auth.ts (Token-Kopfzeilen PVE/PBS), proxmox-client.service.ts
  (proxmoxGet, ausschliesslich lesend, Dispatcher je Aufruf aus
  tlsRejectUnauthorized, nie global)
- proxmox.service.ts: Server anlegen (Geheimnis verschluesselt,
  select ohne Geheimnisfelder), Serverliste, PVE-Abfrage mit
  nachsichtiger Grundauswertung (Knoten/Gaeste)
- Controller/Modul/Seed nach Domaincheck-Vorbild, Kategorie
  "infrastructure", @UseModule('proxmox') + @Roles auf Schreibwegen
- Modulseite (duenne Liste) + proxmox-api.ts + Registrierung in
  MODULE_REGISTRY
- Zugriffsklassifikation nachgezogen (rls-access-inventory.spec.ts gruen)

Tore: api 1247/1247 (>=1240), web 693/693, type-check 4/4, lint 5/5,
Biome apps/web 53 Warnungen (unveraendert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 10:17:33 +02:00
schalli 05feaa3dd6 feat(quick-260923-ad9): Reiter per Ziehen umsortieren - Task 4
dashboard-tabs.tsx: Ziehen ueber Pointer-Ereignisse (Muster
xframe-config-form.tsx, D-05 - keine neue Abhaengigkeit). Schwelle 4 px
waagerechte Auslenkung trennt Klick von Ziehen; darueber wird der Zeiger
eingefangen (jsdom-Schutzhuelle), die Leiste zeigt die Vorschau-Reihenfolge
(computeReorderedIds, Einfuegen vor dem ersten Nachbarn mit Mittelpunkt
rechts vom Zeiger), beim Loslassen geht die VOLLSTAENDIGE Kennungsliste an
onReorder. Abbruch des Zeigers verwirft die Vorschau ohne zu senden. Ein
hasDraggedRef-Merker unterdrueckt den Klick, der im echten Browser nach
einem Ziehen folgt. Ziehen ist IMMER moeglich, nicht nur im
Bearbeitungsmodus (D-09) - ein Hinweistext erklaert, dass der erste Reiter
beim Oeffnen geladen wird.

dashboard-store.ts: reorderDashboards() setzt die neue Reihenfolge SOFORT
optimistisch, sendet sie und stellt bei einem Fehler die vorherige
Reihenfolge wieder her; der aktive Reiter bleibt aktiv.

dashboard-tabs.test.tsx: 21 Tests (12 alte aus Task 3 + 9 neue fuer jeden
Punkt des Verhaltensblocks). getBoundingClientRect wird je Reiter-Wrapper
ueber data-tab-index gestubbt (Reiter i belegt 100i..100i+100 - jsdom
liefert keine echten Masse). dashboard-store.test.ts: 17 Tests (15 alte +
2 neue fuer Optimismus/Ruecknahme).

Messages: widgets.tabs.dragHint war bereits in Task 3 eingetragen
(vorausschauend) - in diesem Task keine weitere Aenderung an de.json/en.json
noetig.

Gemessene Abweichung von der Plan-Erwartung (kein Rule-1/2/3-Fall, reine
Zahlendifferenz): `grep -c "react-grid-layout|dnd|sortable"
apps/web/package.json` liefert 2 statt der im Plan erwarteten 1 - der
zweite Treffer ist `@types/react-grid-layout`, bereits vor diesem Task
vorhanden (siehe `git diff --stat apps/web/package.json`: keine Aenderung
in keinem der vier Tasks). D-05 (keine neue Zieh-Abhaengigkeit) ist damit
weiterhin erfuellt, nur an der leeren package.json-Diff nachgewiesen statt
an der im Plan vorausgesagten Zahl. Ebenso liefert `pnpm --filter
@tessera/web test` 82 statt der erwarteten 83 Dateien - Task 4 fuegt (siehe
files_modified oben) keine neue Testdatei hinzu, Task 3 hatte die
Dateizahl bereits auf 82 gebracht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 08:18:25 +02:00
schalli d34f682c28 feat(quick-260923-ad9): Reiterleiste in der Oberflaeche - Task 3
dashboard-api.ts: fuenf neue Abrufe fuer die Reiter-Wege, die vier
bestehenden Aufrufe (Layout/Widgets lesen/schreiben) tragen jetzt die
Reiter-Kennung (Abfrageparameter bzw. Rumpf).

dashboard-store.ts: Zustand um dashboards/activeDashboardId/
isSwitchingDashboard erweitert. loadDashboard() holt zuerst die Reiter,
macht den ersten aktiv, laedt erst danach dessen Inhalt; ein modul-globales
Versprechen schuetzt gegen doppeltes Laden der Reiterliste bei doppeltem
Einhaengen. selectDashboard() schreibt eine ungespeicherte Anordnung ZUERST
fuer den alten Reiter (Kennung vor dem Wechsel gelesen) und ersetzt danach
Kacheln/Anordnung vollstaendig. createDashboard/renameDashboard/
deleteDashboard pflegen Reiterliste und aktiven Reiter; Loeschen des
aktiven Reiters macht den dann ersten Reiter aktiv. Die Marker-Umrechnung
(quick-260916-bwo) laeuft unveraendert je Reiter mit, auch beim Wechsel.

dashboard-tabs.tsx (neu): Reiterleiste, Klick wechselt immer; im
Bearbeitungsmodus zusaetzlich Anlegen, Umbenennen (an Ort und Stelle,
Enter/Escape) und Loeschen (mit Rueckfrage) - Loeschen-Knopf fehlt beim
letzten verbleibenden Reiter (D-10). Fokus beim Umbenennen ueber einen Ref
statt autoFocus (lint/a11y/noAutofocus).

(portal)/page.tsx: Leiste ueber dem Raster, kurze Ladezeile waehrend eines
Reiterwechsels statt des Rasters - die Leiste bleibt stehen. DashboardGrid
selbst unveraendert (D-06).

Uebersetzungen: neue Schluessel unter widgets.tabs.* in de.json/en.json,
Dialog-Knoepfe nutzen die vorhandenen common.cancel/common.delete.

Deviations (Rule 3 - blockierende Nachwirkung dieses Tasks, ausserhalb der
files_modified-Liste, aber direkt durch die dashboardId-Pflicht verursacht):
- settings/dashboard/page.tsx: fetchWidgets() verlangt jetzt eine
  Reiter-Kennung; die Seite ist nicht reiterbewusst (ausserhalb des
  Umfangs) und zeigt jetzt die Kacheln des ERSTEN Reiters - deckungsgleich
  mit dem bisherigen Verhalten fuer den haeufigen Fall genau eines Reiters.
- (portal)/page.test.tsx: mockStore brauchte die neuen Reiter-Felder/
  -Methoden, sonst waere DashboardTabs auf `dashboards.map` von undefined
  gescheitert.

Tests: dashboard-store.test.ts 15 (6 alte angepasste Signaturen + 9 neue),
dashboard-tabs.test.tsx 12 (neu). web gesamt 682 Tests in 82 Dateien,
dashboard-grid.test.tsx unveraendert bei 12. type-check 4/4, lint 5/5 mit
weiterhin genau 53 Warnungen in web.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 08:10:45 +02:00
schalli 56c07c3581 refactor(quick-260922-m1h): Widget-Typen an einer Stelle, Katalog aus der Registry, Kachel kennt ihr Modul
Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine,
fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab.

- WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in
  packages/shared; Registry, Katalog und die @IsIn-Whitelist der API
  leiten davon ab
- neun wireXWidget()-Funktionen durch ein generisches registerWidget()
  ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung)
- der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus
  der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die
  Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der
  Dashboard-Seite, nicht im Dialog
- widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die
  oeffentliche Funktion bleibt unveraendert

Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der
serverseitige Filter in DashboardService.getWidgets.

Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht
von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die
Dockerfiles kopieren packages/shared bereits, der Produktionsbau von
Next.js und der nest build laufen unveraendert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:03:58 +02:00
schalli c080580459 feat(quick-260921-pi9): Bilderrahmen-Widget - Diashow mit Grossansicht, Bildverwaltung in den Einstellungen
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER
  geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg
  (T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe)
- dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen
  Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src>
- PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser
  laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen,
  Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf,
  Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und
  Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff)
- PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall,
  Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird
  auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen
- Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en
  (widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:56:02 +02:00
schalli f471b785db fix(quick-260921-jt4): verschwendete Kalender-Abrufe abgestellt, t-Identitaet gemessen statt behauptet
Restposten 3a (doppeltes Ladefenster): lastFetchWindowRef merkt sich das
zuletzt TATSAECHLICH geholte from/to-Paar; ein Monatswechsel, dessen
berechnetes Fenster damit uebereinstimmt, ueberspringt fetchEvents. Der
5-Minuten-Auffrischer (force=true) umgeht den Sperrgriff immer, sonst
friert die Anzeige ein. computeFetchWindow und die Tagesgrenzen-Rundung
bleiben unangetastet.

Restposten 3b (Quellenliste je Monatswechsel): hasSourcesRef merkt sich
das Ergebnis; fetchSources laeuft nur beim Aufbau (Merkung leer) oder
erzwungen (Auffrischer) — eine neu eingerichtete Quelle wird weiterhin
binnen fuenf Minuten bemerkt.

Restposten 4 (t-Identitaet): neue translations-identity.test.tsx rendert
eine Testkomponente unter dem ECHTEN NextIntlClientProvider und beweist
per Referenzgleichheit, dass t bei einem lokalen Zustandswechsel
dasselbe Funktionsobjekt bleibt — bestaetigt durch den use-intl-4.13.0-
Quelltext (translate entsteht in einem useMemo, dessen Abhaengigkeiten
ausschliesslich aus dem root-staendigen Intl-Kontext stammen). Die
Faustregel aus 260921-gof ("t gehoert in keine Abhaengigkeitsliste")
bleibt als Konvention in Ordnung; die zugrunde liegende Annahme ("t ist
bei jedem Render frisch") ist damit ausdruecklich WIDERLEGT statt ein
drittes Mal weitergetragen. Die vier verbliebenen Stellen
(marketplace/page.tsx, admin/users/page.tsx,
calendar-settings-panel.tsx, calendar-source-form.tsx) bleiben deshalb
unveraendert.

Vier neue zaehlende Testfaelle in calendar-widget.test.tsx (Aufbau je 1,
abweichendes Fenster +1/+0, identisches Fenster +0/+0, erzwungener Lauf
+1/+1) plus ein Test, dass ein uebersprungener Lauf den Ladezustand
sauber beendet und geladene Termine nicht leert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 15:10:10 +02:00
schalli f85c91be01 fix(web): Steuerzeichen in safe-next.ts als Unicode-Escapes statt Rohbytes
FORBIDDEN_CHARS_RE in sanitizeNextPath bestand aus drei rohen Steuerbytes
(NUL, US, DEL) statt Escapes - jeder Editor, Formatierer oder Minifier in
der Kette kann solche Bytes stillschweigend verschlucken. Umgeschrieben auf
Unicode-Escapes fuer den Bereich U+0000 bis U+001F und U+007F. Zeichenmenge
ueber alle 65536 Codepunkte aus U+0000 bis U+FFFF als unveraendert
nachgewiesen (54 abgewiesene Codepunkte, Bitmap-SHA-256
3d58108b87e4641e506602cc701a66d11ec19cf20551937fada1826f50ecbe64 vor und
nach dem Umbau identisch). Kommentar korrigiert: die vorherige Behauptung,
Escapes seien "am Edge" noetig, war falsch - zwischen den beiden
Escape-Schreibweisen gibt es zur Laufzeit keinen Unterschied.

Quick-Vorgang 260921-i8x, Task 1/3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:26:25 +02:00
schalli 636fe0df8f refactor(quick-260921-bi2): maschinelle Lint-Fixe und toten Code abbauen
- Aufgabe 2: vier sichere Biome-Regeln (useImportType pfadgebunden auf
  apps/web+packages, noUselessEscapeInRegex, useConst,
  useExponentiationOperator) sowie fuenf ungesicherte Regeln
  (useNodejsImportProtocol, useLiteralKeys, useOptionalChain, useTemplate,
  useParseIntRadix) angewendet und den gesamten Diff von Hand gelesen
  (ldap.service.ts zeichenweise gegen Gross-/Kleinschreibung der
  AD-Merkmale, auth.service.ts/jwt.strategy.ts gegen Durchwinken bei
  fehlender Sitzung geprueft)
- noUselessSwitchCase bleibt bewusst stehen (tender-normalizer.service.ts:60,
  die Fallmarke dokumentiert Absicht)
- Toter Code (D-03): fuenf folgenlose Auffangvariablen entfernt, eine
  nicht benutzte Funktion (forSystemQuery, Pruefskript) entfernt, ein
  positionsgebundener Dekoratorparameter umbenannt (current-user.decorator.ts),
  fuenf Symptomfunde entfernt und als Folgeaufgaben zu melden (siehe unten)
- Sechs weitere, im Plan nicht namentlich gelistete aber
  gleich-kategorische Dead-Code-Fundstellen in Testdateien zusaetzlich
  bereinigt (groups.service.spec.ts, cert-manager.test.tsx,
  ldap.service.spec.ts, prisma-tenant.extension.spec.ts x3) — noetig, um
  die vom Plan selbst verlangten Nullstaende bei noUnusedVariables/
  noUnusedImports/noUnusedFunctionParameters zu erreichen

Dekoratordaten aus apps/api unveraendert (593 Zeilen, sha256 6e1583f1...).
Endstand 620 Befunde (541 echt, 79 Test) statt der im Plan geschaetzten
621/542 — eine Differenz von 1, weil das Streichen des Namens aus
`catch (e: any)` in calendar.service.ts (Symptom-Fix) den dort ebenfalls
gemeldeten noExplicitAny-Befund miteliminiert; das ist eine erwuenschte
Nebenwirkung, keine Regression. Fehlerstufe 0, beide Testlaeufe
punktgleich gruen (69/1124, 66/459), pnpm type-check 4/4, pnpm lint
--force 5/5.

Folgeaufgaben aus D-03 (nicht in diesem Vorgang behoben):
- force-password-change.interceptor.ts: Freigabeliste prueft nur den Pfad,
  nicht die HTTP-Methode
- change-password/page.tsx: nach erzwungenem Wechsel bleibt die Person auf
  der Seite stehen (keine Weiterleitung, keine Aktualisierung der
  Benutzerablage)
- VehicleTable.tsx: Loeschschaltflaeche hat keinen Besetztzustand, laesst
  sich doppelt ausloesen
- SplitTab.tsx: downloadAllAsZip erhielt eine ungenutzte
  Uebersetzungsfunktion, Hinweis auf fest verdrahtete Texte im Zip-Pfad

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:58:32 +02:00
schalli f2457113a2 feat(web): Herkunft der Fehlermeldung — Cookie tessera_desktop_client und Client-Felder in der Nutzlast
Die Middleware liest jetzt zusaetzlich dv/dc/dos aus der Anfrage und legt
daraus das Cookie tessera_desktop_client an (bereinigt per Muster, nur
wenn alle drei Werte gueltig sind); desktop-client.ts liest es zurueck.
Der Fehler-melden-Dialog fuellt daraus vier neue Nutzlastfelder
(clientKind/clientOs/clientVersion/clientCommit), damit die API die
Herkunft der Meldung ausweisen kann. Ohne das zweite Cookie (alter
Client) bleibt es bei "Desktop-App (unbekannt)".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:36:19 +02:00
schalli b18ac25ccc feat(web): Favoriten-Widget — Symbol-Ersatzweg aus dem Browser, Sortierpfeile im Bearbeitungsmodus
- FavoriteIcon (favorites-widget.tsx): dreistufiger Ersatzweg proxy ->
  direct -> none; Buchstaben-Platzhalter liegt immer darunter. Direktbild
  nur bei http/https-URL (getDirectFaviconSrc), referrerPolicy
  no-referrer, kein Drittanbieter-Favicon-Dienst. key={iconUrl|url}
  setzt die Stufe bei Aenderung zurueck; kein style.display-Hack mehr
- handleMove + Sortierpfeile im Bearbeitungsmodus (nur bei nicht-inline-
  Bearbeitung): optimistische Neuberechnung, PUT /favorites/order ueber
  reorderFavorites; erster/letzter Eintrag deaktiviert; Fehler ->
  Neuladen mit Fehlermeldung
- favorites-api.ts: reorderFavorites(widgetId, ids)
- de.json/en.json: widgets.favorites.moveUpButton/moveDownButton
- 5 neue Widget-Tests (Ersatzbild-Kette, Nicht-http-URL, Pfeilzustand,
  Klick, Fehlerpfad); 11 bestehende unveraendert gruen; volle Web-Suite
  64 Dateien/429 Tests und type-check gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:40:09 +02:00
schalli d9b94bd259 feat(web): Desktop-Client per Cookie erkennen — Download-Links und Browser-Kontextmenü in der App aus
- withDesktopCookie in middleware.ts setzt tessera_desktop=1 auf JEDER
  Antwort (Fruehausstieg, Redirects, next()), wenn ?desktop=1 anliegt
- desktop-client.ts: isDesktopClient() liest das Cookie, useIsDesktopClient()
  kapselt es hydration-sicher per useEffect
- DesktopDownloadLinks fragt /desktop/latest im Desktop-Client gar nicht
  erst an und rendert nichts
- DesktopContextMenuGuard unterdrueckt das WebView2-Kontextmenue ausserhalb
  von Eingabefeldern/contenteditable, in layout.tsx eingebunden
- middleware.test.ts (neu), desktop-client.test.ts (neu),
  desktop-context-menu-guard.test.tsx (neu), Test 4 in
  desktop-download-links.test.tsx — alle 417 Web-Tests und type-check gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:38:13 +02:00
schalli 474d17082b feat(quick-260917-gyd): Sitzungswaechter erkennt tote Sitzung, Header leitet ab
- auth-actions.ts: fetchSessionState() unterscheidet tote Sitzung (401/403/leere 200-Antwort, Cookie wird geloescht) von API-Ausfall (5xx/Netzwerkfehler/Nicht-JSON, unavailable ohne Redirect); fetchCurrentUser bleibt unveraendert
- header.tsx: Waechter im useEffect leitet bei toter Sitzung per Vollnavigation auf /login?next=… um, bleibt bei API-Ausfall still

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:28:38 +02:00
schalli 4b279eac70 feat(quick-260917-gyd): Ruecksprung nach Anmeldung auf urspruenglich angeforderte Seite
- safe-next.ts: buildNextParam()/sanitizeNextPath() als reine, getestete Funktionen (Open-Redirect-Schutz)
- middleware.ts: haengt next-Parameter an beide Login-Umleitungen (fehlendes Cookie, ungueltige Signatur)
- login/page.tsx: springt nach erfolgreicher Anmeldung auf den bereinigten next-Wert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:26:14 +02:00
schalli 795c6a492c feat(settings): Akzentfarbe zusätzlich als Hex-Code eingebbar
- normalizeHexColor() (apps/web/src/lib/color.ts) normalisiert Hex-Eingaben (fuehrendes # optional, 3-stellige Kurzform, Gross-/Kleinschreibung, Leerraum)
- Kontoformular: Hex-Textfeld neben dem Farbwaehler, beide bidirektional synchron, ersetzt den reinen Anzeige-Span
- Ungueltiger Text: aria-invalid + roter Rand + Fehlertext, "Farbe speichern" gesperrt; Zuruecksetzen setzt beides auf #ffed00
- i18n: settings.account.accentColorHex/accentColorHexInvalid in de.json und en.json
- Unit-Tests: color.test.ts (9 Faelle), account-settings-form.test.tsx (7 Faelle)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:14:43 +02:00
schalli 026d9c39af feat(18-03): Task 1 — Fetch-Helfer und Download-Link auf der Anmeldeseite
- apps/web/src/lib/desktop.ts: loadDesktopLatest (memoisiert, still bei
  Fehler), desktopDownloadUrl, formatFileSize (lokalisierte MB-Werte)
- DesktopDownloadLinks: unauffaelliger Link-Block, rendert nichts ohne
  Daten; Windows fuehrt, Linux als Kurzlink wenn beide Pakete vorliegen
- Anmeldeseite bindet den Block nach dem Formular ein (D-12)
- de/en: auth.desktopDownload.* ergaenzt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:27:44 +02:00
schalli ba06db98c5 feat(quick-260916-dcz): CHANGELOG.md, Seite "Was ist neu" mit Kanalfilter, Versionszeile als Link, Bauzeit-Einbettung
- CHANGELOG.md (Wurzel): Unveroeffentlicht (bwo + dyv + diese Seite, 8 Punkte) und 1.0.0 – 2026-09-15 (11 Punkte aus den Handbuechern), Alltagssprache, echte Umlaute
- next.config.ts liest CHANGELOG.md zur Bauzeit nach env.TESSERA_CHANGELOG_MD; .dockerignore-Ausnahme !CHANGELOG.md und COPY-Zeile im Web-Dockerfile
- lib/changelog.ts: filterChangelogForChannel (live ohne Unveroeffentlicht, beta/dev mit "Noch nicht freigegeben (Beta)", leerer Abschnitt ausgeblendet), 10 Tests
- Server-Seite /changelog (MDEditor.Markdown + rehype-sanitize, Hinweis auf Beta, Leer-Text), 3 Tests; Versionszeile ist Link mit aria-label, 2 Tests
- de/en: sidebar.whatsNew und Namensraum changelog; Web-Suite 49/309, tsc 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 11:20:22 +02:00
schalli 3f5afb0f54 feat(quick-260916-bwo): Dashboard-Raster verdoppelt (24 Spalten, 20 px, 8 px Abstand), Konstanten x2, einmalige Umrechnung gespeicherter Anordnungen mit Marker __gridVersion
- dashboard-grid.tsx: COLS 24/20/12/8/2, rowHeight 20, margin 8 (containerPadding folgt), Rueckfallwerte 4
- widget-registry.tsx: alle 32 Werte in WIDGET_CONSTRAINTS verdoppelt
- grid-layout-migration.ts (neu): migrateGridLayouts/withGridVersion, Marker nur im JSON, Idempotenz (T-BWO-02)
- dashboard-store.ts: Umrechnung beim Laden, Sofort-Speichern mit Marker, withGridVersion bei jedem saveLayout
- Tests: Migration 7 (neu), Store 6 (neu), Registry +1 (Tabelle), Grid +2 (Props ueber Mock), API-Spec +2 (Durchreichung __gridVersion, timeFontSizePt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:09:00 +02:00
schalli b41be21190 feat(quick-260914-m97): Feld Fehlermeldungen an im SMTP-Formular — settings-api, Formular, i18n settings.smtp
- SmtpConfig.bugReportRecipient (string | null) und SaveSmtpPayload.bugReportRecipient (null loescht, fehlend bewahrt)
- Eingabefeld type=email hinter der Absenderadresse mit Hinweistext; Payload traegt immer trim() || null
- Zwei Komponententests: Vorbelegung aus GET, PUT-Payload mit Wert bzw. null
- i18n settings.smtp.bugReportRecipient / bugReportRecipientHelp in de und en

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:51:36 +02:00
schalli 60b0ee8409 feat(quick-260914-m97): Fehler-melden-Knopf in der Kopfzeile — Bildschirmfoto vor dem Dialog (html-to-image 1.11.13), Fehlerpuffer, Dialog mit Vorschau, i18n bugReport
- Knopf (Kaefer-Symbol) unmittelbar vor dem Erscheinungsbild-Schalter; captureScreenshot() laeuft VOR dem Oeffnen, Test pinnt es im toPng-Mock
- computeCaptureSize (laengste Kante 1600 px, reine Funktion, getestet), dataUrlToBlob, sendBugReport als Multipart mit credentials und ohne Content-Type-Header
- error-buffer: Ringpuffer 20, window error/unhandledrejection, console.error (Original bleibt), fetch-Wrapper nur bei !ok ohne Anfrage-Rumpf/Suchteil/Kopfzeilen (T-M97-02), idempotent, SSR-sicher; installiert in app-shell
- Dialog mit Vorschau, Haekchen, Beschreibung, Meldungen je Status 409/413/429/502/allgemein, Admin-Link auf /admin/smtp
- i18n bugReport de/en, Allowlist um "passiert"; html-to-image exakt 1.11.13, Lockfile aktualisiert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:50:01 +02:00
schalli cdb571c509 feat(quick-260914-ku1): Versionsstempel — GET /health/version aus APP_*, VersionResponse, app-version.ts und Abzeichen v<Version> · <Kanal> in der Seitenleiste
- packages/shared: VersionResponse { name, version, channel, commit, buildTime }
- apps/api: health/app-version.ts (getAppVersion mit ||-Vorgaben dev/dev, formatAppVersionLine),
  HealthController.getVersion delegiert (bleibt @Public, T-KU1-03), main.ts protokolliert
  "Tessera API <version> (<channel>) <commit>" beim Start; neuer Spec mit 6 Tests
- apps/web: lib/app-version.ts (NEXT_PUBLIC_APP_* mit vollem Literalnamen, loadApiVersion
  memoisiert und still bei Fehler), AppVersionBadge unten in der Seitenleiste (auch mobil,
  nicht eingeklappt), sidebar.channel.{beta,live,dev} in de/en; 5 + 4 + 1 neue Tests
- Suiten: API 65 Dateien / 1060 Tests, Web 40 / 243, tsc in shared/api/web Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 15:32:50 +02:00
schalli c4db3b2e65 feat(quick-260907-let): Knopf "Verbindung testen" im Postfach-Formular
- testEmailConnection im API-Klienten, POST email-config/test
- EmailAlertConfigForm: Testknopf vor Speichern, Wartezustand, gruene/rote
  Rueckmeldung, Formularaenderung raeumt vorherige Rueckmeldung weg
- Beschreibungsblock der Komponente korrigiert (Knopf existiert jetzt)
- Vier neue Schluessel unter tenderRadar.emailAlerts in de/en
- vi.mock-Fabriken in EmailAlertConfigForm.test.tsx und my-sources.test.tsx
  um testEmailConnection erweitert, zwei neue Testfaelle

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 15:43:51 +02:00
schalli 4100bb575e feat(17-03): "Meine Quellen" wird vollstaendig — Postfach, eigene Feeds, Benachrichtigung
- RssFeedSource bekommt isPlatformWide (server-derived), createRssFeed nimmt
  einen scope-Parameter (personal/platform, Vorgabe personal)
- RssFeedListForm bekommt scope-Prop: personal zeigt eigene Feeds editierbar +
  plattformweite als schlichte Aufzaehlung ohne Knoepfe darunter; platform
  zeigt nur plattformweite Feeds editierbar
- DigestIntervalForm aus settings/page.tsx unveraendert herausgeloest (keine
  neuen Beschriftungen, gleiche settings.*-Schluessel)
- my-sources/page.tsx um "Meine Feeds" und "Benachrichtigung" erweitert,
  Verweis auf die Administrationsseite nur fuer ADMIN/SUPER_ADMIN
- settings/page.tsx vorgezogen auf RssFeedListForm scope="platform" (Rule 3,
  eigener Type-Check-Verify sonst rot) — volle Rollenpruesung folgt Task 2

Rule 1: createRssFeed's Antwort traegt kein isPlatformWide (nur GET mappt es
serverseitig) — RssFeedListForm setzt es nach dem Anlegen lokal aus dem
verwendeten scope, sonst wuerde ein frisch angelegter plattformweiter Feed
bis zum naechsten Neuladen aus seiner eigenen Liste verschwinden.
2026-08-12 11:56:42 +02:00
schalli 43c7fa200b feat(15-08): serverseitige Modulsperre mit 403-Seite
- checkModuleAccess (module-access-actions.ts) fragt GET /modules/active
  serverseitig ab, Cookie-Weiterleitung nach fetchCurrentUser-Muster,
  schliesst im Zweifel (fehlendes Cookie, nicht-ok, Fehler -> false)
- page.tsx zur async Server Component umgebaut, rendert bei fehlender
  Freigabe das 403-Markup direkt (kein notFound(), kein Redirect, D-07)
- bisheriger Client-Inhalt (Whitelist-Pruefung, Nicht-gefunden-Zustand,
  Ruecknavigation) unveraendert nach module-shell.tsx ausgelagert
- 5 Tests in module-access.test.tsx: 403-Zustand, Shell-Rendering,
  fehlendes Cookie, nicht-ok-Antwort, unregistrierter Slug trotz Zugriff
2026-08-04 19:41:25 +02:00
schalli bdaf154f66 feat(260723-lvg): add "Jetzt abrufen" manual poll button to tender radar
PollNowButton sits next to the settings gear in the tender-radar header,
mirrors the DKV spinner/disabled UX, and bumps a refreshKey on success to
refetch ResultsList without touching any filter. Failure surfaces an
i18n error (de/en parity). pollNow() client hits POST
/modules/tender-radar/poll-now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:54:43 +02:00
schalli 947325f6ad feat(14-04): CoverageBanner "manuell beobachten" denylist block
- Add DenylistedPortal type + fetchDenylistedPortals() to
  tender-radar-api.ts, following the existing credentials:'include' fetch
  convention
- CoverageBanner fetches the denylisted-portals endpoint on mount and
  renders vergabe24/aumass with direct links (rel="noopener noreferrer",
  target="_blank"); block renders independently of the onlyDoe coverage
  note and fails silently on fetch error
- Add CoverageBanner.test.tsx asserting both portal hrefs, independence
  from the coverage note, and fail-silent behavior

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:59:44 +02:00