Commit Graph

149 Commits

Author SHA1 Message Date
schalli 0c89c13bb2 fix(quick-260921-jt4): calendar-settings-panel Loeschdialog-Hintergrund wird echte Schaltflaeche
- Zentrierbehaelter verliert den e.target===e.currentTarget-Handler,
  die Hintergrundfarbe wandert auf eine eigene benannte <button> (D-01)
- Muss abbrechen, darf niemals loeschen (T-JT4-04): Beschriftung nennt
  ausdruecklich das Abbrechen (widgets.calendar.deleteDialogCancel)
- Dialogkarte bekommt relative, aria-label des alertdialog aus dem
  Katalog statt fest verdrahtetem Englisch (deleteDialogLabel)
- Neue Testdatei belegt per Klick UND echter Tastaturbetaetigung, dass
  der Hintergrundweg abbricht und deleteSource nie aufgerufen wird;
  eigener Test fuer den tatsaechlichen Loeschweg ueber die CTA

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:43:36 +02:00
schalli 3d0bc0bfaa fix(quick-260921-jt4): widget-catalog-modal Hintergrund wird echte Schaltflaeche
- Aeussere Flaeche verliert onClick, die bislang nur optische
  Hintergrund-Flaeche wird eine benannte <button> und traegt onClose
  (D-01); verliert dabei aria-hidden, weil ein fokussierbares Element
  nicht verborgen sein darf
- stopPropagation auf der Dialogflaeche entfaellt als toter Code, weil
  der Hintergrund jetzt Geschwister statt Vorfahr ist
- Fest verdrahtetes englisches aria-label="Close" durch common.close
  ersetzt
- Neuer Katalogschluessel widgets.catalogClose in de.json/en.json
- Neue Testdatei: Hintergrund schliesst (Klick + Tastatur), Dialogklick
  schliesst nicht, Escape weiterhin, Kartenauswahl fuegt Widget hinzu

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:41:54 +02:00
schalli 8716fa5234 feat(quick-260921-iwr): Rundenliste der Stoppuhr per Test belegt, MergeTab-Entfernen abgesichert
- Wirkungslose eslint-disable-Zeile in stopwatch-widget.tsx ersetzt durch
  Sachhinweis: Zeilen halten keinen Zustand, Rundennummer wird aus Laenge
  und Position berechnet. Keine neue Unterdrueckung, ARRAYKEY bleibt bei 19.
- Neuer Testfall in stopwatch-widget.test.tsx: zwei Runden nacheinander,
  neuere Runde steht oben, Rundennummern 2/1 stimmen zu ihrer eigenen Zeit.
- Neue MergeTab.test.tsx: Entfernen der mittleren Datei laesst genau erste
  und dritte Datei mit eigenem Namen und eigenem Entfernen-Knopf uebrig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:55:42 +02:00
schalli b92dd5dda1 fix(api,web): exec-Schleifen ohne Unterdrueckungskommentar umgeschrieben, LDAP-NUL-Maskierung und Sprachcookie-SameSite dokumentiert/gehaertet
Drei while ((m = re.exec(t)) !== null)-Schleifen (dkv-parser.service.ts,
dkv-parser.validate.ts, icon-discovery.service.ts) sind die korrekte
Standardform fuer globale Regexe - kein verrutschtes "=". Umgeschrieben auf
eine verhaltensgleiche for-Schleife, die ohne noAssignInExpressions-
Unterdrueckung auskommt: Zuweisung wandert in Initialisierung und
Fortschaltung der for-Schleife, Bedingung prueft weiterhin auf null.
Abfolge der exec-Aufrufe, lastIndex-Fortschritt und Rumpfinhalte
unveraendert. Neue Spezifikation dkv-parser.service.spec.ts deckt
parseDkvText erstmals eigenstaendig ab (zwei Fahrzeugbloecke, Rechnungsnummer
und -datum aus einer gemockten pdf-parse-Attrappe) - das Rueckfall-Tor fuer
diesen Umbau. dkv-parser.validate.ts bleibt bei 27 Fahrzeugbloecken/66
Transaktionen gegen die reale invoice.pdf identisch.

LdapService.escapeLdapFilterValue bleibt zeichengleich: der NUL-Treffer in
der Regel ist die von RFC 4515 vorgeschriebene \00-Maskierung, kein Fehler.
Ein biome-ignore-Kommentar dokumentiert das, statt die Funktion zu aendern.

locale-switcher.tsx setzt jetzt SameSite=Lax auf dem NEXT_LOCALE-Cookie -
path=/ und max-age waren bereits korrekt, es lag also kein Persistenzdefekt
vor. Ohne SameSite haengt die Uebertragung am Browservorgabewert statt an
einer Festlegung. Neue Spezifikation locale-switcher.test.tsx haelt die
vollstaendige geschriebene Cookie-Zeichenkette fest.

Biome-Warnungen 446 -> 434 (noControlCharactersInRegex/useIterableCallbackReturn/
noGlobalIsNan auf 0, noAssignInExpressions auf 4 und noDocumentCookie auf 17 -
beide Reste ausschliesslich in Testdateien, suppressions/unused auf 0).

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:32:31 +02:00
schalli e780b2cc69 fix(web): Absicht/Ballast der letzten sechs useExhaustiveDependencies-Befunde entschieden
Befunde 16-21 (quick-260921-gof), Regel-Gesamtstand jetzt 0/446 (vorher
21/467), pnpm lint 5/5, pnpm type-check 4/4:

- InvoiceHistoryTable.tsx (16) und sidebar.tsx (17): refreshKey /
  sidebarRefreshKey bleiben als begruendete Auffrisch-Ausloeser stehen
  (biome-ignore mit deutschem Grund) - ohne sie zeigt die DKV-Historie
  nach "Jetzt pruefen" bzw. die Seitenleiste nach einer Modul-
  Aktivierung den alten Stand.
- ActivateModuleDialog.tsx (18): moduleId aus der Abhaengigkeitsliste
  entfernt - reiner Ballast, der Effekt holt ohnehin nur die vom Modul
  unabhaengige Gruppenliste und die Elternseite haengt den Dialog je
  Modul frisch ein.
- GroupMembersModal.tsx (19/20): fetchMembers/fetchAllUsers in die
  Liste aufgenommen (echter Defekt) - ohne sie zeigt der Dialog bei
  einem Gruppenwechsel ohne Neuaufbau die Mitglieder der vorigen
  Gruppe. Neue Testdatei nach dem grants-matrix.test.tsx-Muster belegt
  genau diesen Fall.
- grants/page.tsx (21): die Suchabgleich-Hilfsfunktion `matches` in den
  Merkungs-Rumpf verschoben statt im Bauteil-Rumpf zu bleiben - reiner
  Ballast, die bestehenden Suchfaelle bleiben unveraendert gruen.
- sidebar.test.tsx um eine Zaehlprobe erweitert: Bump loest genau einen
  weiteren Abruf aus, erneutes Zeichnen ohne Bump keinen.
- Alle drei begruendeten biome-ignore-Zeilen sind jetzt gesetzt (erste
  Verwendung dieses Mechanismus im Projekt), alle wirkungslosen
  eslint-disable-Zeilen fuer diese Regel sind aus apps/web/src
  verschwunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:34:55 +02:00
schalli e2c508cff5 fix(web): t-Falle in acht Ladeeffekten entschaerft, RssFeedListForm/SavedSearchBar-Ladefunktionen stabilisiert
Befunde 7-15 der Biome-Regel useExhaustiveDependencies (quick-260921-gof):

- VehicleTable, TenderDetail, DigestIntervalForm, SourceConfigForm,
  favorites-widget: Ersatz-Fehlertext aus useTranslations wird jetzt vor
  dem Effekt/Rueckruf in eine Konstante gezogen und diese Konstante in
  die Abhaengigkeitsliste aufgenommen - `t` selbst kommt nirgends mehr
  in eine Liste. In diesem Projekt ist belegt, dass `t` bei jedem
  Durchlauf eine frische Funktion ist (Testattrappen), eine `t`-
  Abhaengigkeit haette den jeweiligen Mount-Abruf zur Schleife gemacht.
- RssFeedListForm.tsx und SavedSearchBar.tsx: die Ladefunktionen waren
  gewoehnliche Funktionen im Rumpf (bei jedem Durchlauf neu) - jetzt in
  einen stabilen Rueckruf mit der Text-Konstante als einziger
  Abhaengigkeit eingepackt.
- ResultsList.tsx: Befund 8 (t) wie oben, Befund 15 (refreshKey) in den
  Effekt verschoben, der `load` aufruft, statt in `load` selbst zu
  stehen - eine begruendete `biome-ignore`-Zeile (erste im Projekt)
  haelt fest, dass der Auffrisch-Ausloeser der Elternseite ohne diese
  Abhaengigkeit wirkungslos waere.
- Sieben Testdateien um eine Zaehlprobe erweitert: erneutes Zeichnen mit
  unveraenderten Props darf keinen weiteren Abruf ausloesen; ResultsList
  zusaetzlich um eine Probe, dass ein refreshKey-Bump genau einen
  weiteren Abruf ausloest.
- DigestIntervalForm hat keine Testdatei - nur am laufenden System auf
  Meine Quellen geprueft (siehe SUMMARY).
- Wirkungslose eslint-disable-Zeilen fuer diese Regel entfallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:29:05 +02:00
schalli b3f0e3cdcd fix(web): Kalender-Ladeeffekt an monthDate statt getTime(), Stoppuhr-Takt liest nur noch Einzelwerte
Befunde 1-6 der Biome-Regel useExhaustiveDependencies (quick-260921-gof):

- calendar-widget.tsx: useMemo um resolveCalendarConfig() entfernt
  (reine Funktion, spart nichts). showToday setzt monthDate jetzt
  identitaetserhaltend, wenn der aktuelle Monat schon angezeigt wird -
  erst danach durfte der Ladeeffekt von monthDate.getTime() auf
  monthDate umgestellt werden, sonst haette jeder Druck auf den
  Monatsknopf im laufenden Monat einen Termin-Abruf bis zum
  Exchange-Server ausgeloest (D-04).
- stopwatch-widget.tsx: neue reine Hilfsfunktion computeElapsedFrom()
  fuer den Takt-Effekt, der jetzt nur noch drei Einzelwerte statt des
  ganzen sw-Objekts liest - eine sw-Abhaengigkeit haette den 100-ms-Takt
  bei jeder aufgezeichneten Runde ab- und wiederaufgebaut.
- Testerweiterungen als Rueckfallsicherungen: 2x weiterblaettern -> 3
  Abrufe, 3x Monatsknopf im laufenden Monat -> kein Zusatzabruf; Runde
  waehrend die Stoppuhr laeuft unterbricht den Takt nicht, genau 1 PATCH
  je Klick.
- Wirkungslose eslint-disable-Zeilen fuer diese Regel entfallen (kein
  ESLint mehr im Projekt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:23:35 +02:00
schalli c79bafa179 fix(quick-260921-bi2): a11y - Beschriftungen an Felder binden (22 Fundstellen)
noLabelWithoutControl (22) auf 0: jede Beschriftung ueber htmlFor/id an ihr
Feld gebunden, in Formularen mit wiederholten Zeilen ueber praefixierte,
seitenweit eindeutige Kennungen (z.B. ldap-*, user-*, tenant-*).

Sonderfall calendar-source-form.tsx: die Farbauswahl beschriftet eine ganze
Gruppe von Farb-Schaltflaechen, kein einzelnes Feld. Dafuer fieldset/legend
statt htmlFor/id (Rand/Abstand zurueckgesetzt, damit sich am Erscheinungsbild
nichts aendert) -- eine Umwandlung in <span> haette die Assoziation entfernt
statt sie herzustellen, darum nicht gewaehlt.

Damit steht der gesamte Lint-Rueckstand bei 465 (386 echt, 79 Test),
Fehlerstufe 0 -- Zielwert dieses Vorgangs erreicht. Die fuenf zurueck-
gestellten Regeln (noNoninteractiveElementInteractions, useKeyWithClick-
Events, noStaticElementInteractions, useAriaPropsSupportedByRole,
noAutofocus) stehen unveraendert bei 11/5/5/5/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:38:14 +02:00
schalli 21c85a8b88 fix(quick-260921-bi2): a11y - Rollen, Semantik, Tab-Reihenfolge (32er-Rest, Teil 1/2)
Vier der fuenf noch offenen Regeln aus Teillieferung B bereinigt:

- noRedundantRoles (4): ueberfluessige role-Angaben auf button/time/select
  entfernt (maschineller --unsafe-Fix, gelesen).
- useAriaPropsForRole (1): entfaellt automatisch mit obigem Fix -- das
  <select role="combobox"> in search-widget.tsx verlangte die fehlenden
  ARIA-Attribute nur wegen der ueberfluessigen Rolle.
- useSemanticElements (4): admin-sidebar/settings-sidebar tragen role=
  "navigation" jetzt am <nav> statt am <aside> (kein doppeltes Landmark
  mehr); widget-wrapper.tsx ist jetzt ein echtes <article> statt
  div role="article"; DropZone.tsx trennt die Datei-Entfernen-Schaltflaeche
  als Geschwister ab, damit die Drop-Flaeche selbst ein echtes <button>
  werden kann (ein <button> darf kein zweites <button> verschachteln).
  Die Drop-Flaeche traegt darum jetzt Klick- UND Drag-Handler direkt am
  <button>, sonst waere sie ein "statisches" Element mit Ereignis-Handlern
  geworden (die zurueckgestellten Regeln noStaticElementInteractions /
  noNoninteractiveElementInteractions waeren neu angeschlagen -- geprueft,
  bleiben bei 5/11).
- noNoninteractiveTabindex (1): calculator-widget.tsx traegt jetzt
  tabIndex={-1} statt {0}. Die Zifferntasten sind bereits echte <button>
  und damit selbst Teil der Tab-Reihenfolge; Tastendruecke erreichen
  handleKeyboard weiterhin per Bubbling, sobald eine Taste fokussiert ist.
  Verhalten unveraendert, nur ein wirkungsloser Tab-Stopp auf dem Container
  selbst entfaellt.

Verbleibend: noLabelWithoutControl (22), naechster Schritt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:33:56 +02:00
schalli 27a6e2952c fix(quick-260921-bi2): a11y - Layout-Navigation und Einstellungsformulare
useButtonType: Hamburger-, Profil-, Kategorie-, Ein-/Ausklapp- und Logout-
Buttons (kein Formular in diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Icons neben sichtbarem Text werden dekorativ
(aria-hidden="true"). Zwei bislang unbenannte interaktive Elemente erhalten
zusaetzlich ein aria-label, weil ihr Text im eingeklappten Sidebar-Zustand
verschwindet bzw. ganz fehlte: die Dashboard-/Marketplace-Links und der
"Einstellungen"-Button in der Seitenleiste (t('dashboard')/t('marketplace')/
t('settings'), alle bereits vorhandene Schluessel) sowie der mobile
Schliessen-Button (tCommon('close')).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:43 +02:00
schalli 969fd01f5c fix(quick-260921-bi2): a11y - Dashboard-Widgets und Portal-Startseite
useButtonType: "Widget hinzufuegen"-, Bearbeiten-Modus- und Katalog-Buttons
(kein Formular in diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Widget-Icons in der Katalog-Kachel (immer neben dem
Widget-Namen) und Buttons mit bestehendem aria-label/title werden dekorativ
(aria-hidden="true"); der bislang unbeschriftete Such-Button im Such-Widget
erhaelt aria-label={t('search.searchButton')} (neuer Schluessel, siehe
vorherige i18n-Festschreibung).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:34 +02:00
schalli 278aedb201 fix(quick-260921-bi2): a11y - Modul-Bereich (Kategorie-Seiten, Cert-Manager)
useButtonType: alle Aktions-Buttons in Cert-Manager-Tabs (kein Formular in
diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Modul-Icons neben dem Modulnamen, Leer- und Nicht-
gefunden-Zustaende neben ihrer Ueberschrift werden dekorativ
(aria-hidden="true"); die Augen-Icons im Passwortfeld sind bereits ueber das
aria-label des umschliessenden Buttons benannt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:24 +02:00
schalli 4cff3167ed fix(quick-260921-bi2): a11y - Administrationsbereich (Gruppen, LDAP, Module, Mandanten, Benutzer)
useButtonType: alle Schaltflaechen ausserhalb der drei echten Formulare
(Mandanten/Benutzer/LDAP) erhalten type="button"; die drei tatsaechlichen
Absende-Buttons behalten type="submit" (Zahl bleibt 1/1/2, siehe <verify>).

noSvgWithoutTitle: Symbole neben sichtbarem Text (Zurueck-Pfeil, Navigations-
Icons in der Admin-Seitenleiste) werden dekorativ (aria-hidden="true"); das
Schloss-Symbol der LDAP-Standardzuordnung traegt jetzt einen eigenen Titel
(admin.ldap.fieldMapping.defaultIcon), weil es ohne begleitenden Text pro
Tabellenzeile die ganze Bedeutung allein traegt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:09 +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 4c79874278 feat(web): Einstellungen → Desktop-App zeigt in der App den verbundenen Server
- DesktopAppSettings rendert im Desktop-Client (useIsDesktopClient) einen Block "Verbunden mit: {origin}" plus Hinweis auf "Server-Adresse ändern…" im Infobereich-Menü; im Browser bleibt der Block weg
- Neue i18n-Schlüssel settings.desktop.connectedTo/changeHint in de.json und en.json
- Test 4 (Cookie tessera_desktop=1) und Test 5 (ohne Cookie) ergänzt; RED zuerst (Test 4 schlug auf der Zielbehauptung fehl), dann GREEN
- Alle 431 Web-Tests und type-check grün

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:52:47 +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 ecff144449 feat(brand): ganzes T der Bildmarke übernimmt die Akzentfarbe – Kacheln als abgeleiteter dunklerer Ton
- BRAND_OLIVE_MIX (54 %, #363636) und BRAND_OLIVE_FILL in brand.ts, Kalibrierung kommentiert
- vier achsenparallele Kacheln in tessera-logo.tsx: Inline-Style BRAND_OLIVE_FILL, festes Oliv-Attribut als Rückfall
- brand.test.ts (neu): rechnet Kalibrierung, Neutralität und Grenzfälle aus denselben Konstanten nach
- tessera-logo.test.tsx: Kacheltest auf Token-Fuellung (1x) und abgeleitete Fuellung (4x) umgestellt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:18:58 +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 1601d97f62 feat(brand): gedrehte Kachel der Bildmarke übernimmt die Akzentfarbe
- LogoMark: gedrehte Signalkachel per Inline-Style fill: var(--primary, BRAND_YELLOW) statt festem fill-Attribut
- folgt damit der persoenlichen Akzentfarbe (applyAccentColor in auth-store.ts) in Kopfzeile, Seitenleiste und leerem Dashboard; ohne Nutzer (Anmeldeseite) gilt der CSS-Standard Markengelb
- brand.ts: Kommentar zu BRAND_YELLOW als Rueckfallwert ergaenzt
- tessera-logo.test.tsx: Kacheltest auf Inline-Style-Fuellung umgeschrieben (jsdom haelt var() in style.fill)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:15:33 +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 e96d460b8e feat(18-03): Task 2 — Einstellungsseite Desktop-App mit Knoepfen, Groesse und Erklaerung
- Neue Route /settings/general/desktop (DesktopSettingsPage)
- DesktopAppSettings: Version, zwei Primaerknoepfe mit Plattform-Symbol,
  Dateiname/Groesse je Knopf, Beta-Kanal-Hinweis, vier erklaerende Saetze,
  Hinweistext wenn keine Pakete hinterlegt sind
- Seitenleiste: Eintrag "Desktop-App" unter "Allgemein"
- de/en: settings.categoryDesktopApp, settings.desktop.*
- umlaut-dictionary.ts: "neuere" zur Allowlist ergaenzt (Rule 1 —
  Waechter-Test brach an einer bereits korrekten Umlautschreibung, die
  zufaellig die Buchstabenfolge "ue" enthaelt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:30:34 +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 7429c5bd8d feat(web): Kalender-Widget zeigt je Kalender einen kleinen Kreis am Tag; Changelog
- Plaketten-Wrapper calendar-day-badges rendert buildDayBadges(day.events) statt der alten Einzel-Plakette
- Ein Kalender: unveraendert eine Plakette in Kalenderfarbe/Akzentfarbe (Tests 3/3b/3c bleiben gruen)
- Zwei/drei Kalender: je ein kleiner Kreis pro Kalender in Startreihenfolge (Test 3d)
- Vier oder mehr Kalender: zwei Kreise plus grauer Restkreis bg-muted-foreground/text-background mit Summe (Test 3e)
- CHANGELOG-Stichpunkt erweitert (mehrere Kalender am selben Tag)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:37:01 +02:00
schalli 4ddadc63ab feat(web): Kalender-Tag nach Kalender gruppieren, Kreise je Kalender berechnen (calendar-month)
- groupDayBySource gruppiert Tagestermine nach sourceId (nicht Farbe), Reihenfolge = erstes Auftreten, Farbe = Farbe des ersten Termins der Gruppe
- buildDayBadges baut daraus bis zu drei Plaketten-Kreise, ab dem vierten Kalender einen grauen Restkreis mit Summe
- Tests 8/9 in calendar-month.test.ts (TDD: RED bestaetigt vor Implementierung)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:34:49 +02:00
schalli 1e4ec30190 feat(web): Kalender-Plakette in der Farbe des Kalenders, Farbpunkt je Tooltip-Zeile
- groupEventsByDate sortiert jede Tagesgruppe jetzt nach Start aufsteigend
- Zaehl-Plakette im Monatsraster traegt die Farbe des Kalenders des fruehesten Termins (weisse Schrift), ohne Farbe unveraendert bg-primary
- Tooltip-Zeilen bekommen denselben Farbpunkt wie die Liste "Naechste Termine" (Rueckfall var(--muted-foreground))
- 3 neue Tests: calendar-month Test 2b, calendar-widget Test 3b/3c

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:25:21 +02:00
schalli 4c2495bb64 fix(web): Notiz-Widget folgt dem Hell/Dunkel-Schalter von Tessera statt der Systemeinstellung
- useTheme (next-themes) + mounted-Guard wie changelog-view.tsx
- data-color-mode am Wurzel-Div folgt resolvedTheme statt fest 'auto'
- zwei neue Tests (dark/light) via vi.hoisted-Themenzustand

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:55:18 +02:00
schalli b16e4b8e67 fix(web): Kalender-Tooltip bricht lange Termintitel um (Breite/Klemmung aus einer Konstante)
- Titel-Span im Tooltip: min-w-0 break-words statt truncate
- Breite (288px) und Rand-Klemmung aus TOOLTIP_WIDTH_PX/TOOLTIP_EDGE_PX
- Test 4c: belegt Klassen und Tooltip-Breite

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:54:34 +02:00
schalli 39ea1474a5 feat: Link-Widget restlos entfernt (Web, API, Migration, Handbuch, Changelog)
- Web: Union-Mitglied, Constraints, Icon, Registry-Eintrag, wire-Funktion,
  Katalog, Seiten-Verdrahtung, Tests und i18n (de/en) fuer den Typ link
  entfernt; link-widget.tsx/.test.tsx geloescht
- API: create-widget.dto.ts (@IsIn-Liste) und widget-module-map.ts
  (Kommentare) auf sieben Typen angepasst; schema.prisma unveraendert
- Neue Migration 20260916120000_remove_link_widget: eine idempotente
  DELETE-Anweisung auf WidgetInstance, FavoriteLink kaskadiert ueber den
  bestehenden FK; wird in diesem Auftrag NICHT ausgefuehrt
- Neuer widget-wrapper-Test belegt den grauen Fallback fuer unbekannte
  Widget-Typen (Bestandsverhalten, jetzt festgeschrieben)
- Handbuch (Widget-Tabelle, Einstellungs-Hinweise) und CHANGELOG
  (Geaendert/Entfernt/Behoben) aktualisiert
- Web: 52 Dateien / 344 Tests gruen, tsc Exit 0; API: tsc Exit 0,
  dashboard-Spec 31 Tests gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:44:09 +02:00
schalli 7f1ee3b1f0 feat(web): Favoriten-Widget — optionaler Titel (Kopfzeile, FavoritesConfig, i18n)
- Favoriten-Widget: config.title (nur typeof string) steuert eine Kopfzeile
  im Notiz-Look; leer -> keine Kopfzeile; im Bearbeitungsmodus immer ein
  Titelfeld (widgetNoDrag, 1500 ms entprellt, { title })
- Einstellungen -> Dashboard -> Widgets: FavoritesConfig (Muster NoteConfig)
  mit uebersetzter Beschriftung, Instanz-Kopfzeile zeigt "— {title}" jetzt
  fuer note UND favorites
- NoteConfig: hart kodiertes "Title" durch uebersetzten Schluessel ersetzt
- 3 neue Uebersetzungsschluessel (note.titleLabel, favorites.titleLabel,
  favorites.titlePlaceholder) in de/en
- favorites-Tests 7+4, Panel-Tests 7+3, Umlaut-Waechter 3/3 gruen, tsc Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:39:48 +02:00
schalli 684f063a8e feat(web): Notiz-Widget — Aufgabenlisten in der Ansicht abhakbar
- Hilfsmodul note-task-list.tsx mit isTaskLine/toggleTaskLine (GFM-konforme
  Regex, Code-Zaeune werden uebersprungen) und NoteCheckbox (kein disabled)
- previewOptions des Notiz-Widgets reicht components.input = NoteCheckbox
  durch react-markdown nach rehypeSanitize durch (bleibt aktiv, T-IEX-01)
- Klick im Vorschau-Container kippt genau die N-te Aufgabenzeile und
  speichert sofort (verwirft einen laufenden Tipp-Entprell-Timer)
- Hinweis: note-task-list als .tsx statt .ts angelegt (Komponente braucht
  JSX) — Abweichung dokumentiert in SUMMARY.md
- 16 neue/erweiterte Tests gruen, tsc Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:36:17 +02:00
schalli 6d8c7c46a8 feat(web): CalendarConfig im Einstellungsfeld, Changelog- und Handbuch-Eintrag
- Kalender-Zweig im Einstellungsfeld ersetzt den englischen Fliesstext durch CalendarConfig (Monatsansicht ein/aus, Anzahl Termine 0..10, Zeitraum 7/14/30/60/90) nach Muster ClockConfig; Speichern per partiellem updateWidgetConfig
- Paneltest um drei Kalender-Tests erweitert (7/7 gruen), next-intl-Mock interpoliert jetzt {platzhalter}
- CHANGELOG.md: zweiter Eintrag unter Unveroeffentlicht/Geaendert; Anwenderhandbuch: Widget-Tabellenzeile und Dashboard-Widgets-Absatz ergaenzt
- Gesamtlauf: 51 Testdateien / 332 Tests gruen, Type-Check Exit 0 (quick-260916-htc)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:12:48 +02:00
schalli 61996dceef feat(web): Kalender-Widget neu gebaut mit Monatsraster + Naechste Termine, Mindestgroesse 6x8
- calendar-widget.tsx komplett neu: Monatsraster (Nav-Zeile, Wochentagskopf, 42 Zellen, heutiger Tag, Zaehl-Plakette, Portal-Tooltip) + Block "Naechste Termine"; fetchEvents bekommt immer zwei Tagesgrenzen-ISO-Strings ueber computeFetchWindow
- Komponententest komplett neu geschrieben (9 Tests: Laden/Quellen, Raster, Plakette, Tooltip inkl. 5+-Kuerzung, Naechste-Termine-Filter, showMonth/maxEvents=0, Ladefenster, Blaettern)
- WIDGET_CONSTRAINTS.calendar auf { minW: 6, minH: 8, defaultW: 8, defaultH: 12 } angehoben (quick-260916-htc), Registry-Test A mitgezogen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:10:33 +02:00
schalli 0858102439 feat(web): Uebersetzungsschluessel + Hilfsmodul calendar-month.ts fuer Kalender-Widget
- 16 neue Uebersetzungsschluessel unter widgets.calendar in de.json/en.json (Monatsnavigation, Naechste-Termine-Block, Einstellungsfelder)
- Neues Hilfsmodul calendar-month.ts: resolveCalendarConfig, buildCalendarDays, groupEventsByDate, computeFetchWindow, selectUpcomingEvents, Formatierungsfunktionen (quick-260916-htc)
- 8 Unit-Tests fuer das Hilfsmodul, Umlaut-Waechter weiterhin 3/3 gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:06:15 +02:00
schalli 2306a6dee1 feat(web): typabhaengiger URL-Platzhalter + EWS-Hinweis im Kalenderquellen-Formular
- Platzhalter je Typ (CalDAV/ICS/Exchange-Graph/Exchange-EWS), ohne Typ weiterhin https://
- Grauer Hinweis unter dem Feld nur bei Exchange + EWS, unterhalb eines eventuellen Fehlers
- Neuer Komponententest mit 6 Faellen (TDD: rot vor der Implementierung)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:47:09 +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 8792819e34 fix(quick-260916-dyv): Rechner-Mindesthoehe 10 statt 9 Zeilen — sechs Tastenreihen, unterste Reihe war bei 9 um 25 px abgeschnitten (Browser-Messung)
Tessera CI/CD / Lint & Type Check (push) Successful in 54s
Tessera CI/CD / Tests (push) Successful in 1m1s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m58s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:57:55 +02:00
schalli dbbd54f5a1 feat(quick-260916-dyv): Bearbeiten-Schalter unten rechts (Rand oben 28 px), ganze Kachel als Griff mit Kopfleiste, cancel-Selektor, kein Ueberlappen beim Ablegen
- page.tsx: feste Aktionsleiste `fixed bottom-6 right-6 z-20` mit "Widget
  hinzufuegen" (nur Bearbeitungsmodus) links neben dem Stift/Haekchen; Block
  oben rechts und mt-8-Wrapper entfernt, Grid direkt im Container p-2
  (12 + 8 + 8 = 28 px statt 60 px); page.test.tsx NEU mit 3 Tests
- widget-wrapper.tsx: Karte ist im Bearbeitungsmodus der Griff (cursor-grab),
  20-px-Overlay-Kopfleiste mit Griff-Symbol und Tooltip dragHint, Loesch-Knopf
  in der Kopfleiste mit data-no-drag; Rumpf h-full unveraendert (Hoehenkette)
- dashboard-grid.tsx: WIDGET_DRAG_HANDLE_SELECTOR, WIDGET_DRAG_CANCEL_SELECTOR
  (input, textarea, select, button, a, [contenteditable], [data-no-drag],
  .widgetNoDrag), threshold 3; FREE_PLACEMENT_COMPACTOR = noCompactor +
  preventCollision: true (freie Platzierung bleibt, Commit c8f3361)
- dashboard-grid.test.tsx: Mock per importOriginal (echter noCompactor),
  Tests 6-8 (dragConfig-Pin, Compactor-Pin, cancel/handle-Semantik im DOM)
- edit-mode-toggle.tsx: schwebend (shadow-lg, inaktiv border + bg-card)
- de.json/en.json: widgets.dragHint

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:41:49 +02:00
schalli dc992c9bc6 fix(quick-260916-dyv): Mindestgroessen inhaltsgetrieben (kleinste bedienbare Kachel je Typ), gespeicherte Minima ueberschrieben, Stoppuhr-Bedienleiste kompakt
- WIDGET_CONSTRAINTS: Minima aus dem Innenaufbau gerechnet (clock 2/2, search 6/2,
  calendar 3/3, note 4/4, calculator 3/9, favorites 3/3, link 3/2, stopwatch 4/3),
  Vorgaben unveraendert; Test A pinnt alle 32 Werte per toEqual
- dashboard-grid.tsx: effectiveLayouts (useMemo) ueberschreibt minW/minH jedes
  gespeicherten Eintrags in jedem Breakpoint aus den Konstanten und hebt zu
  kleine w/h auf das Minimum an — RGL 2.2.3 nimmt gespeicherte Eintraege
  woertlich (synchronizeLayoutWithChildren) und liest data-grid nur ohne Eintrag
- dashboard-grid.test.tsx: Test 5 angepasst, Test 9 (Ueberschreibung je
  Breakpoint, unbekannter Typ unveraendert) und Test 9b (h 8 -> 9, w >= minW bleibt)
- stopwatch-widget.tsx: Bedienleiste px-2 py-1 text-xs, Zeile gap-1 py-1, damit
  Stop + Runde + Reset in 4 Spalten passen und die Kachel auf 4x3 schrumpfen kann

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:37:55 +02:00
schalli a175c00c18 feat(quick-260916-bwo): Abstaende halbiert — Seitenrahmen p-3 (alle Seiten), Dashboard p-2, Grid 8 px, Widget-Innenabstaende
- app-shell.tsx: main p-3 (12 px statt 24) — gilt fuer alle Seiten, Nachtrag des Users
- page.tsx: Container relative p-2, Umschalter right-2 top-2; mt-8 bleibt (Umschalter 36 px hoch, sonst Ueberlappung)
- link/favorites p-1, calendar p-1.5 (Zustandsansichten p-2), search px-1.5, note-Kopfzeile px-1.5
- Grid-Abstand 8 px bereits in Task 1, Uhr/Stoppuhr/Rechner-Innenabstaende bereits in 2a

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:13:23 +02:00
schalli 2d8efe1a97 feat(quick-260916-bwo): Widget-Inhalt skaliert per Container-Queries (Uhr, Stoppuhr, Rechner), Punktgroesse der Uhrzeit einstellbar
- widget-wrapper.tsx: Rumpf ist Groessen-Container (@container-size) fuer cqw/cqh
- clock-font-size.ts (neu): Grenzen 8..200, resolveClockTimeFontSizePt nur fuer endliche Zahlen (T-BWO-01)
- clock-widget.tsx: Container-Query-Klassen mit clamp(), data-font-mode auto/fixed, Inline-pt nur bei Zahl, keine vw-Formel mehr, p-1
- stopwatch-widget.tsx: Anzeige per Container-Query, p-1.5; calculator-widget.tsx: Anzeige und Tasten skalieren, Tasten h-full min-h-7, p-1
- widget-settings-panel.tsx: Zahlenfeld Schriftgroesse der Uhrzeit (Blur/Enter, leer = automatisch, Fehlermeldung ausserhalb 8..200)
- de.json/en.json: widgets.clock.fontSizeLabel/fontSizeAuto/fontSizeHint/fontSizeInvalid (Sie-Form, Waechter gruen)
- Tests: Uhr +4, Stoppuhr +1, Rechner +1, widget-settings-panel.test.tsx neu (4)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:12:02 +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 bf3c082234 fix(web): Bildmarke statt Platzhalter im leeren Dashboard
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m28s
Ueber "Keine Widgets aktiv" stand noch dasselbe handgezeichnete
Vier-Quadrate-Symbol, das auf der Anmeldeseite bereits durch das Logo ersetzt wurde
— beim Logo-Einbau uebersehen, weil der leere Zustand nur ohne Widgets sichtbar ist.

Jetzt die echte Bildmarke aus der Marken-Komponente, ohne den bisherigen grauen
Rahmen und ohne Kontur: die Marke steht frei auf dem Seitenhintergrund und braucht
keine eigene Flaeche. 226 Web-Tests gruen, im Browser gegengeprueft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 14:04:55 +02:00
schalli 31a92c9b9a fix(web): Schriftzug traegt im dunklen Modus die Markenfarbe
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 50s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m36s
Die vorherige Korrektur war zu grob: sie hat das Gelb ueberall durch die
Vordergrundfarbe ersetzt, obwohl es nur im hellen Erscheinungsbild ein Problem war.
Nachgerechnet gegen die tatsaechlichen Farbwerte aus globals.css:

  Gelb auf hellem Grund   1,26 : 1   (Kopfleiste)   unlesbar
  Gelb auf heller Leiste  1,24 : 1   (Seitenleiste) unlesbar
  Gelb auf dunklem Grund 13,06 : 1                  hervorragend
  Gelb auf dunkler Leiste 14,00 : 1                 hervorragend

WCAG verlangt 3:1 fuer grossen fetten Text. Das Gelb faellt also nur auf hellem
Grund durch — auf dunklem ist es die mit Abstand beste Wahl.

Der Schriftzug erbt jetzt im hellen Erscheinungsbild die Vordergrundfarbe (19:1) und
traegt im dunklen die Markenfarbe. Umgesetzt ueber wordmarkClassName an den beiden
Aufrufstellen, nicht in der Komponente selbst: das gelbe Anmelde-Panel ist
themeunabhaengig hell und braucht dort weiterhin dunkle Schrift.

Im Browser in beide Richtungen gemessen: hell oklch(0.15 0 0), dunkel
oklch(0.91 0.19 102) — letzteres ist exakt --primary.

Ausserdem in STATE.md festgehalten, dass Tessera zunaechst nur intern eingesetzt wird
und die Abnahme der Mandantentrennung (Plan 02-05, Erfolgskriterium 4) deshalb
bewusst zurueckgestellt ist — nicht zurueckgebaut, und vor einem Einsatz bei externen
Kunden nachzuholen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 14:00:12 +02:00
schalli b70161717c fix(260907-ipz): Anrede in vier deutschen Texten auf Siezen umstellen
Die Oberflaeche siezt ueberall, ausser an vier Stellen: leere
Gruppenliste, Modul-Aktivierungsdialog, Modul-Sperrseite und
Marktplatz-Hinweis duzten bislang. Alle vier Werte in de.json werden
auf die Sie-Form umgestellt, en.json bleibt unveraendert, da Englisch
die Unterscheidung nicht kennt. Die Mock-Uebersetzer und Zusicherungen
in den drei betroffenen Testdateien werden auf den neuen Wortlaut
nachgezogen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:37:12 +02:00
schalli 11c0ec63cb feat(quick-260907-fgv): Anmeldeseite zeigt Logo links, Text rechts daneben
Auf Wunsch des Users stehen Bildmarke und Text auf dem gelben Anmelde-Panel jetzt
nebeneinander statt untereinander — dieselbe Anordnung wie in der gelieferten Datei
tessera-logo-schriftzug.svg: links die Marke, rechts der Name und darunter der
Untertitel.

Dafuer nimmt die horizontale Variante der Marken-Komponente einen optionalen
Untertitel entgegen. Die gestapelte Variante entfaellt — sie war nach dieser
Umstellung nirgends mehr im Einsatz, alle uebrigen fuenf Stellen standen schon vorher
nebeneinander.

Der Test auf die entfallene Variante wurde nicht geloescht, sondern durch zwei Tests
auf das neue Verhalten ersetzt: einer prueft, dass ohne Angabe kein Untertitel
erscheint, der andere die tatsaechliche Reihenfolge im Dokument (Marke vor Name, Name
vor Untertitel) statt nur deren Vorhandensein. Die Erwartungstexte sind von Hand
geschrieben und nicht aus derselben Quelle gebildet, aus der die Komponente sie zieht.

222/222 Web-Tests gruen, Typpruefung sauber, im Browser gegengeprueft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:41:07 +02:00