Commit Graph

1170 Commits

Author SHA1 Message Date
schalli e56cce4bf3 fix(web): Doppelausloesung des Loeschknopfs sperren, Fahrzeugtabelle uebersetzt
VehicleTable.confirmDelete liess das Beschaeftigt-Kennzeichen zwar setzen,
aber nie lesen (const [, setIsDeleting]); Dialogschaltflaechen blieben
waehrend der laufenden Loeschanfrage bedienbar. Kennzeichen jetzt lesbar
gebunden, Dialog reicht den Zustand an Bestaetigen/Abbrechen weiter
(disabled + Sperr-Klassen), und confirmDelete bricht bei bereits laufender
Loeschung selbst ab.

Alle sieben fest verdrahteten Texte und sechs Vorlesehilfen der Tabelle
jetzt ueber next-intl (sieben neue Schluessel im Bereich dkvFleet, gleicher
Schluesselsatz in de.json und en.json).

Nebenbefund beim Testen: `load` haette mit `t` als Abhaengigkeit bei einem
instabilen Uebersetzer-Mock einen Abruf-bei-jedem-Render-Zyklus ausgeloest
— bewusst mit leerem Abhaengigkeitsfeld gelassen. Ausserdem
`vi.restoreAllMocks()` im Testabbau ersetzt: es leerte die Aufrufzaehlung
der reinen vi.fn()-Mocks nicht, wodurch der neue Doppelklick-Test falsche
Aufrufzahlen sah.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:39:03 +02:00
schalli f7c02b7fc9 fix(api): erzwungenen Passwortwechsel an der API wirklich durchsetzen
JwtStrategy.validate liess mustChangePassword auf dem Weg vom Token zu
request.user fallen; der global registrierte ForcePasswordChangeInterceptor
prueft genau dieses Feld und hat seit seiner Einfuehrung nie etwas
blockiert. validate() reicht das Feld jetzt durch (strenger Vergleich mit
true, Alt-Sitzungen ohne den Anspruch bleiben unveraendert unbetroffen).

Zusaetzlich die Erlaubnisliste des Abfangers von Teilstring-Vergleich auf
exakten Abgleich von Methode UND Pfad umgestellt (Absicherung gegen eine
kuenftige kollidierende Route, heute nicht ausnutzbar).

Nahttest gepinnt, der gegen den alten Quelltext nachweislich scheitert
(6 von 12 neuen Faellen rot vor der Aenderung, gruen danach).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:27:36 +02:00
schalli 116041b7fd docs(quick-260921-bi2): gemeldetes Symptom Passwortwechsel widerlegt
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 1m9s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m51s
Der Lint-Durchlauf meldete, die Seite change-password leite nach
erfolgreichem Wechsel nicht weiter und lasse die Person bei erzwungenem
Wechsel stehen. Am laufenden System durchgespielt: trifft nicht zu.
Middleware leitet auf /change-password, der Wechsel landet auf /,
mustChangePassword steht danach auf false, Weiternavigieren geht.

changePasswordAction setzt das neue Sitzungs-Cookie und ruft redirect('/')
serverseitig; die ungenutzten router/setUser im Seitenmodul waren
Ueberbleibsel, kein Symptom. Entfernung des toten Codes war richtig,
die daraus abgeleitete Diagnose nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:05:58 +02:00
schalli f198c5765d docs: Stand nach CI-Lauf 389 (komplett gruen, Abbilder veroeffentlicht)
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m6s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 17s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m57s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 10:15:23 +02:00
schalli c001a081c2 docs(quick-260921-bi2): Lint-Rueckstand 2923 -> 465, Akte und Verifikation
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m24s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m15s
Zusammenfassung, Verifikation und STATE.md zum Quick-Vorgang 260921-bi2.

Kernbefund: Biomes als "safe" eingestufte Korrektur style/useImportType
zerstoert in apps/api die NestJS-Abhaengigkeitsspritze — das erzeugte
__metadata("design:paramtypes", [...]) kollabiert zu [Function, ...] und
die API startet nicht mehr, waehrend tsc gruen bleibt und alle 1124
API-Tests gruen bleiben (kein Test ruft createTestingModule auf).
Deshalb ein zweiter, auf apps/api/** begrenzter overrides-Eintrag.

Nachweis fuer "kein Verhaltenswechsel" ist nicht die Testsuite, sondern
ein sha256 ueber alle 593 erzeugten __metadata-Zeilen (6e1583f1...),
vor und nach dem Umbau identisch. Dazu Klicktest am laufenden System
mit den echten Abbildern: API healthy, Abbrechen legt nichts an,
Speichern legt an, ADMIN sieht auf der SUPER_ADMIN-Zeile nur Details,
abgewiesene Server-Antworten erscheinen sichtbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 10:02:44 +02:00
schalli 97a6836444 docs(quick-260921-bi2): Entwickleranleitung auf Endstand 465/386/79 gebracht
Der Lint-Abschnitt trug noch den Zwischenstand aus Aufgabe 1 (754/633/121).
Jetzt der tatsaechliche Endstand nach allen drei Aufgaben, beide Ausnahmen
weiterhin begruendet, plus die namentlich benannten Folgeaufgaben: fuenf
zurueckgestellte a11y-Regeln, die bewusst nicht angewendete
noUselessSwitchCase-Fundstelle, und die vier gemeldeten D-03-Symptomfunde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:43:40 +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 73ac08af17 fix(quick-260921-bi2): a11y - Bildmarke und Desktop-Einrichtungsseite
apps/web/src/app/icon.svg (Next.js-Favicon, keine next-intl-Anbindung
moeglich): <title>Tessera</title> als einzige Fundstelle, die die ganze
Bedeutung allein traegt.

apps/desktop/src/setup.html: Bildmarke steht direkt vor der Ueberschrift
"Tessera" und wird dekorativ (aria-hidden="true").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:49 +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 ae821254ef fix(quick-260921-bi2): a11y - Marketplace-Bereich
useButtonType: alle Filter-, Karten- und Dialog-Schaltflaechen (keine davon
in einem Formular) erhalten type="button".

noSvgWithoutTitle: Modul-Icons neben dem Modulnamen und die Erfolgs-/Fehler-
Symbole neben der Toast-Nachricht werden dekorativ (aria-hidden="true"); der
bislang unbeschriftete Toast-Schliessen-Button erhaelt aria-label={t('close')}
aus dem bereits vorhandenen common.close-Schluessel, sein Icon wird dekorativ.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:17 +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 e76f3b8d84 fix(quick-260921-bi2): a11y - Login, Passwort-Reset und Passwort-Aenderung
useButtonType (Absenden-Button behaelt type="submit", keine weiteren Buttons
in diesen Dateien) und noSvgWithoutTitle (Lade-Spinner im Absenden-Button ist
rein dekorativ, da er den sichtbaren Beschriftungstext waehrend des Ladens
ersetzt -> aria-hidden="true").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:20:59 +02:00
schalli 38115783b4 i18n(quick-260921-bi2): neue Schluessel fuer a11y-Beschriftungen (LDAP-Standardzuordnung, Suchbutton)
- admin.ldap.fieldMapping.defaultIcon: Beschriftung fuer das Schloss-Symbol der
  Standardzuordnung (Aufgabe 3, noSvgWithoutTitle)
- widgets.search.searchButton: Beschriftung fuer den bislang unbenannten
  Such-Icon-Button (Aufgabe 3, noSvgWithoutTitle)
- de/en Schluesselmengen bleiben identisch

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:20:53 +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 8d1c8f320b chore(quick-260921-bi2): Lint-Konfiguration bereinigen, Messgrundlage herstellen
- overrides fuer Testdateien (noExplicitAny off) und apps/api (useImportType off, siehe
  Regelbeschreibung "Caveat with TypeScript experimental decorators")
- Fixture-Ausnahme in files.includes ohne angehaengten Doppelstern (Biome-Hinweis
  useBiomeIgnoreFolder befolgt)
- Entwickleranleitung auf den neuen Stand gebracht: 754 Befunde (633 echt, 121 Test), beide
  Ausnahmen begruendet, Folgeaufgaben benannt

Rueckstand: 2923 -> 754, Fehlerstufe weiterhin 0. security-Regelgruppe unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:46:06 +02:00
schalli e7c2c4c6a9 docs(quick-260921-bi2): Plan fuer den Lint-Rueckstand (2923 -> 466)
Drei Durchgaenge nach Risikoklasse: Konfiguration, maschinelle Fixes
plus toter Code, Barrierefreiheit von Hand. Alle Zielzahlen sind beim
Planen probeweise ausgefuehrt, gemessen und zurueckgenommen worden.

Wichtigster Befund: style/useImportType darf in apps/api nicht
maschinell laufen. emitDecoratorMetadata plus NestJS bedeutet, dass die
Korrektur 61 von 65 Dateien mit Abhaengigkeitsdaten zerstoert — bei
gruenem tsc und gruenen 1124 Tests, weil kein Test den Container
startet. Der Plan begrenzt die Regel auf apps/web und verankert als
Nachweis einen sha256 ueber die erzeugten __metadata-Zeilen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:39:32 +02:00
schalli 6de5eb4f07 docs(quick-260921-a1d): Benutzerverwaltung meldet abgewiesene Aktionen (WINDOWS #36)
Tessera CI/CD / Lint & Type Check (push) Successful in 55s
Tessera CI/CD / Tests (push) Successful in 1m11s
Tessera CI/CD / Desktop-Pakete bauen (push) Failing after 11m56s
Tessera CI/CD / Build & Publish Images (push) Has been skipped
Zusammenfassung und Verifikation zum Quick-Vorgang 260921-a1d,
Registereintrag #36 geschlossen, STATE.md nachgezogen.

Nachgewiesen: alle drei zuvor stillen Stellen (Liste laden, Formular
speichern, Loeschen) zeigen den Servertext oder eine uebersetzte
Ersatzmeldung; fuer einen ADMIN entfallen Bearbeiten und Loeschen in der
SUPER_ADMIN-Zeile. apps/api blieb unangetastet — der Zielrollen-Riegel
im Controller bleibt die wirksame Grenze. Web-Tests 66 Dateien / 459
Tests gruen (vorher 65/447), type-check Exit 0, pnpm lint 5/5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:41:19 +02:00
schalli 13b70dfbe8 fix(web): SUPER_ADMIN-Zeile bietet einem ADMIN keine Aktionsknoepfe mehr an
WINDOWS #36, Aufgabe 3/3: canManageRow spiegelt den Zielrollen-Riegel aus
apps/api/src/user/user.controller.ts (update/remove, WINDOWS #29) rein
ergonomisch — die Serverpruefung bleibt unveraendert und ist die einzige
wirksame Grenze. Bearbeiten und Loeschen entfallen jetzt in der Zeile
eines SUPER_ADMIN, wenn die angemeldete Person selbst keiner ist;
Details bleibt in jeder Zeile. Die Sperre gegen Selbstloeschung bleibt
unveraendert. Gesamtbestand apps/web: 66 Dateien / 459 Tests gruen,
type-check Exit 0, lint 5/5 erfolgreich, apps/api unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:33:48 +02:00
schalli 51bff7564f fix(web): Formularweg und Listenladen der Benutzerverwaltung melden Fehler sichtbar
WINDOWS #36, Aufgabe 2/3: handleSubmit und fetchUsers verschluckten
abgewiesene Antworten und Verbindungsfehler ebenso wie der Loeschweg aus
Aufgabe 1. formError zeigt jetzt den Servertext oder eine Ersatzmeldung
im offenen Formulardialog; loadError verhindert die irrefuehrende
Meldung "Keine Benutzer gefunden", wenn das Laden selbst gescheitert
ist. Beide Zustaende werden beim Oeffnen eines neuen Dialogs
zurueckgesetzt, damit eine alte Meldung nicht in den naechsten Aufruf
hinueberwandert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:31:47 +02:00
schalli 38d2586466 fix(web): Loeschweg der Benutzerverwaltung meldet abgewiesene Server-Antworten
WINDOWS #36, Aufgabe 1/3: handleDelete verschluckte einen 403 bisher
komplett (nur res.ok geprueft, Fang-Zweig ohne Wirkung). readApiMessage
liest jetzt gezielt das Feld message aus dem Antwortrumpf; der
Loeschdialog zeigt den Servertext, eine uebersetzte Ersatzmeldung ohne
verwertbaren Rumpf oder bei Verbindungsfehler — und bleibt in allen drei
Faellen offen. Neue Texte unter admin.users.errors in de.json/en.json,
Umlaut-Waechter gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:29:51 +02:00
schalli 24f51e932d docs(quick-260921-a1d): Plan fuer WINDOWS #36 (stille 403-Antworten)
Drei Aufgaben: Loeschweg end-to-end sichtbar (Tracer), Formular- und
Ladeweg nachziehen, Aktionsknoepfe der SUPER_ADMIN-Zeile fuer ADMIN
nicht anbieten. Texte ueber next-intl in de/en, Serverpruefung
unangetastet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:24:05 +02:00
schalli 551d25075f docs(quick-260921-9ie): Biome lauffaehig, Lint-Tor scharf (WINDOWS #35)
Plan, Zusammenfassung und Verifikation zum Quick-Vorgang 260921-9ie,
Registereintrag #35 geschlossen, STATE.md nachgezogen.

Nachgewiesen: pnpm lint fuehrt 5 von 5 Workspace-Aufgaben aus (vorher
"No tasks were executed") und endet auf dem Bestand mit Exit 0; eine
Wegwerfdatei mit debugger laesst denselben Aufruf mit Exit 1 und
noDebugger-Befund scheitern. Repo-weit 0 parse-Fehler, 0 Fehler.
Die Regelgruppe security bleibt unangetastet auf error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:13:20 +02:00
schalli 6f0f05aa00 docs(tooling): Entwickler-Anleitung um echten Lint-Umfang und Warnungs-Rueckstand ergaenzt
- pnpm lint prueft seit #35 echt (biome lint . je Workspace), CI-Schritt Lint blockiert entsprechend
- Fehler stoppen den Lauf, Stilhinweise laufen als Warnungen mit (rund 2800 offen: any-Familie, Barrierefreiheit apps/web) — bewusst eigener Durchlauf, nicht Teil dieses Vorgangs
- Klargestellt: pnpm lint formatiert nicht, dafuer separat biome format --write von Hand

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:07:11 +02:00
schalli 00d769b466 feat(tooling): lint-Skript in web/desktop/shared/module-sdk, biome.json als turbo-Cache-Abhaengigkeit
- apps/web, apps/desktop, packages/shared, packages/module-sdk erhalten lint: biome lint .
- turbo.json globalDependencies bezieht biome.json ein, damit Regelaenderungen den Lint-Cache verwerfen
- pnpm lint fuehrt jetzt 5 von 5 echten Aufgaben aus statt "No tasks were executed"
- Gegenprobe bestaetigt: Wegwerfdatei mit debugger/loser Gleichheit laesst turbo lint --filter=@tessera/api mit Exit 1 und noDebugger-Befund scheitern; Datei danach entfernt, Lauf wieder gruen

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:06:06 +02:00
schalli 6a727e936b fix(tooling): biome.json fuer Biome 2.5.0 migrieren, lint-Skript in apps/api
- organizeImports auf oberster Ebene entfernt, assist.actions.source.organizeImports: "on" ergaenzt
- linter.rules.recommended durch linter.rules.preset: "recommended" ersetzt
- javascript.parser.unsafeParameterDecoratorsEnabled aktiviert (238 parse-Fehler in 19 Dateien behoben)
- javascript.formatter.quoteStyle auf "single" gesetzt (1496 einfach-, 0 doppelt-gequotete Importzeilen)
- vcs.useIgnoreFile aktiviert, files.includes schliesst __fixtures__ und globals.css aus
- a11y/correctness.useExhaustiveDependencies/ausgewaehlte suspicious-Regeln auf warn, security bleibt error
- apps/api/package.json: lint-Skript biome lint .

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:05:07 +02:00
schalli 55aa287296 docs(quick-260918-gza): Fehlermeldung — Herkunft ausweisen (Browser/Desktop-App, OS, App-Version)
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m6s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m43s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m7s
Plan, Ausfuehrungsbericht, Verifikation (9/9 must_haves) und Aktenstand;
lokaler Nachweis per Playwright/mailhog fuer Browser- und Desktop-Marker-Fall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:48:01 +02:00
schalli e2a79467df docs: Fehlermeldungen — Herkunft (Browser/Desktop-App, Betriebssystem, Version) und Betreff-Kürzel (Handbücher, CHANGELOG)
Damit Betreiber im Postfach sofort erkennen, ob eine Meldung aus einem
Browser oder der Desktop-App kommt (und mit welchem Stand), beschreiben
Administrationshandbuch und Betriebshandbuch das neue Betreff-Kürzel und
die Zeile "Herkunft"; das Betriebshandbuch erklaert zusaetzlich den Fall
eines alten Desktop-Clients ohne Details.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:37:07 +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 b03cb211b3 feat(desktop): Version, Stand und Betriebssystem im Desktop-Marker mitgeben (dv, dc, dos)
with_desktop_marker haengte bisher nur desktop=1 an die erste Navigation;
der Fehler-melden-Knopf konnte deshalb Windows- und Linux-Client nicht
unterscheiden. with_client_marker ist die neue reine Kernfunktion (dv,
dc, dos zusaetzlich zu desktop=1), with_desktop_marker bleibt als Huelle
mit den echten env!-Werten die unveraenderte Aufrufstelle an allen drei
Navigationen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:36:10 +02:00
schalli 716947228e feat(bug-reports): Herkunft der Fehlermeldung im Betreff-Kuerzel und als Zeile Herkunft ausweisen
WebView2 (Windows) sieht im User-Agent aus wie Edge, WebKitGTK (Linux) wie
Safari — im Postfach war eine Client-Meldung von einer Browser-Meldung
nicht zu unterscheiden. Neuer reiner Helfer origin.ts leitet aus vier
optionalen DTO-Feldern (Desktop-App) bzw. dem User-Agent (Browser) ein
Betreff-Kuerzel und eine Zeile "Herkunft: ..." ab; rein informativ,
laengenbegrenzt, nichts wird gespeichert (T-GZA-01). Browser-Pfad ist
damit Ende-zu-Ende fertig.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:32:29 +02:00
schalli ab99a9ab5d docs: Wiedereinstieg 2026-09-18 — Handoff verbraucht, naechster Auftrag (Herkunft in Fehlermeldung)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:12:28 +02:00
schalli 9009dade81 wip: Betrieb nach 1.2.0 pausiert — Wiedereinstieg (HANDOFF.json + .continue-here.md), nichts angefangen
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m5s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 12:07:43 +02:00
schalli efbd6e8974 docs(quick-260917-kgc): Aktenstand — Update in der Desktop-App, alle Nachweise erbracht, Wiedereinstieg bereinigt
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m22s
Quick 260917-kgc (Plan/Recherche/Bericht/Verifikation) und Schnellfix a6d1a64
in der Quick-Task-Tabelle; Nachweise in allen sechs Berichten nachgetragen
(Playwright lokal, CI-Laeufe 382-384, Windows-Test-VM: In-App-Update
7479cb4 -> a6d1a64). Ueberholte .continue-here-Dateien entfernt,
Desktop-Client-Todo geschlossen.

Dieser Push aendert nichts unter apps/desktop -- er ist zugleich der
Beweisfall 2 des CI-Desktop-Skips (Pakete aus dem Zwischenspeicher).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
2026-09-18 11:29:14 +02:00
schalli a6d1a648d1 feat(desktop): Setup-Seite zeigt Version und Stand der installierten App
Tessera CI/CD / Lint & Type Check (push) Successful in 50s
Tessera CI/CD / Tests (push) Successful in 1m4s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m0s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m57s
Neues Command get_client_info liefert "Tessera-App X.Y.Z · Stand <sha7>"
(ohne Stempel nur die Version); die Setup-Seite blendet die Zeile beim
Erststart und unter "Server-Adresse ändern" dezent ein. So ist ohne
Adressleiste erkennbar, welcher Client-Stand läuft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 16:00:56 +02:00
schalli 7479cb485f docs: Desktop-App — Update in der App (Handbücher, CI-Secrets, CHANGELOG)
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m8s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 7m56s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m12s
- Anwenderhandbuch: Menüeintrag "Update installieren", Ablauf per Klick
  unter Windows/Linux, Fehlerfall, https-Bedingung, einmaliger Wechsel für
  Clients bis 1.2.0
- Betriebshandbuch Kap. 10: Signierschlüssel (Secrets, Ablage, Sicherung,
  Verlust), Kontrollzeile /desktop/update, Manifest-Felder, zwei Fehlerbilder
- Entwicklungshandbuch: lokal `tauri build --no-sign`, Hinweise zu tauri dev
- ci-cd-setup.md: die zwei neuen Secrets, Bau-Schritte signieren,
  Fehlerbild "no private key"
- CHANGELOG: eine Zeile unter Unveröffentlicht → Neu

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:36:58 +02:00
schalli 7004b5b020 ci: Desktop-Pakete mit dem Updater-Schluessel signieren, Signatur und updateVersion ins Manifest
- ci.yml: Secrets TAURI_SIGNING_PRIVATE_KEY/_PASSWORD als env nur an den
  beiden tauri-build-Schritten des Jobs desktop (kein Job-env, kein
  --no-sign); alles andere strukturgleich
- desktop-collect.sh: liest <bundle>.sig (genau eine Base64-Zeile) als
  files.<p>.signature ins Manifest, schreibt updateVersion (X.Y.Z bzw.
  X.Y.Z-beta.g<sha7>); fehlende .sig bricht auf main/Tag oder bei
  gesetztem Schluessel ab, warnt sonst (dev, lokal --no-sign)
- desktop-stamp.sh check: kein Cache-Stand ohne updateVersion und
  Signaturen beider Plattformen
- .gitignore: *.key

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:34:45 +02:00
schalli de81c74e53 feat(api): GET /desktop/update — signierte Desktop-Pakete im Format des Tauri-Updaters ausliefern
- Neuer oeffentlicher Endpunkt (vor download/:platform): base-Origin wird
  per safeOrigin validiert (nur http/https, kein Pfad/Query/Fragment/
  Userinfo, sonst 400) und nur zum Bau der absoluten Download-URL genutzt,
  nie serverseitig abgerufen
- 204 ohne Body bei fremder Plattform/Architektur, fehlendem Manifest,
  fehlender Signatur oder fehlendem/ungueltigem updateVersion; sonst
  { version, pub_date (nur RFC 3339), url, signature, notes }
- Manifest-Felder signature (je Plattform) und updateVersion optional in
  @tessera/shared, Eintragspruefung akzeptiert nur String-Signaturen
- 10 neue Spec-Tests, Test 11 erweitert (23 gesamt); latest/download
  unveraendert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:32:52 +02:00
schalli 678ba51725 feat(desktop): Update in der App — Herunterladen, Installieren und Neustart per Klick im Infobereich (tauri-plugin-updater)
- tauri-plugin-updater 2.11 + semver eingebunden; pubkey, installMode passive
  und createUpdaterArtifacts in tauri.conf.json (kein endpoints, keine
  dangerous*-Schalter)
- Versionspruefung laeuft ueber updater_builder().check() gegen
  /api-proxy/desktop/update mit eigenem version_comparator (is_update_newer:
  hoehere Basis oder anderer Beta-Stempel beta.g<sha7>; gleiche Basis Live
  → kein Update); 15 s Pruefung, 600 s Download-Timeout
- Tray-Eintrag "Update installieren" laedt mit Fortschritt im Menuetext,
  prueft die minisign-Signatur, installiert (Windows NSIS passiv, Linux
  AppImage an Ort und Stelle) und startet neu; Fehler → Benachrichtigung
  und Rueckfall auf die Einstellungsseite im Browser
- http-Server im Release: Menuetext "Update nur über https möglich", gesperrt
- 15 neue Tests, 2 umgestellt (33 gesamt); gen/schemas vom Bau nachgezogen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:30:28 +02:00
schalli 5a444ec8f2 docs(quick-260917-jdf/jdh/jdd/jn2): Aktenstand — Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m21s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m20s
Vier Quick-Tasks mit Plan, Bericht und Verifikation; Browser-Nachweis der
Web-Teile lokal erbracht (Kacheln #284a7b bei #0057b8, Proxy-Symbol trotz
Zertifikatsfehler, Direktbild bei interner Adresse, Sortierung ueber Reload).
Offen: CI-Beweis des Desktop-Skips nach diesem Push, Windows-VM-Probe der
Client-Aenderungen mit dem CI-Paket.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:10:20 +02:00
schalli 4d485432c0 docs: Desktop-App — Server-Adresse sehen und ändern (Handbuch, CHANGELOG)
- Neuer Unterabschnitt „Server-Adresse ändern" vor „Automatischer Start": wo die Adresse steht (Hinweistext, Menüzeile, Einstellungen → Desktop-App) und wie sie geändert wird (Rechtsklick → „Server-Adresse ändern…" → Setup-Seite → „Verbinden"/„Abbrechen")
- Tray-Liste ergänzt um „Verbunden mit …" (erste Zeile) und „Server-Adresse ändern…"; Hinweis auf den Tooltip nach der Liste; Erststart-Absatz verlinkt den neuen Unterabschnitt
- CHANGELOG.md: zwei Desktop-App-Stichpunkte unter Unveröffentlicht / Neu angehängt, fremde Zeilen unverändert (per git diff gegengeprüft)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:53:26 +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 29c132ecf3 feat(desktop): Verbundenen Server im Infobereich zeigen, Server-Adresse nachträglich änderbar
- Tray zeigt erste (gesperrte) Menüzeile "Verbunden mit {host}" und Tooltip "Tessera – {host}" (bzw. "nicht verbunden")
- Neuer Menüpunkt "Server-Adresse ändern…" navigiert zur gebündelten Setup-Seite (lokaler Ursprung, kein Capability-Eintrag nötig)
- setup.html erkennt per neuem Command get_server_url den Änderungsmodus (Feld vorbelegt, "Aktuell verbunden mit: …", Knopf "Abbrechen" → open_server)
- Nach save_server_url aktualisieren apply_server (Tooltip + Menüzeile) und spawn_version_check (aus setup herausgezogen, läuft neu gegen den neuen Server) ohne Neustart; Tray-Klick "update" liest die Adresse jetzt beim Klick aus dem Store
- Reine Helfer server_host, tray_labels, setup_page_url, parse_server_url mit 13 neuen Tests (RED zuerst: 13 Compile-Fehler auf die fehlenden Helfer, dann GREEN); 18 Rust-Tests gesamt, fmt/check/clippy sauber
- capabilities/default.json unverändert (T-JN2-01, kein remote-Block)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:51:01 +02:00
schalli b023d6f726 docs: Favoriten-Sortierung und Symbol-Ersatzweg im CHANGELOG und Anwenderhandbuch; Nachtraege zur Mandantenbindung
- CHANGELOG.md: je ein Stichpunkt unter Neu (Sortierpfeile) und Behoben
  (Symbol trotz Zertifikatsfehler/interner Adresse)
- docs/anleitung-anwender.md: Tabellenzeile „Favoriten" nennt die Pfeile
  und den Browser-Ersatzweg fuer das Symbol
- docs/mandantentrennung-zugriffsklassifikation.md: Nachtrag zu
  favoriteLink — reorder() laeuft ueber withTenantTransaction() ohne
  Benutzerdimension in der Sitzung, Stand bleibt gebunden
- prisma-tenant.extension.ts: Kopfkommentar-Nachtrag, favorites.service.ts
  (reorder) ist der erste Nutzer-CRUD-Aufrufer von withTenantTransaction()
  — nur Kommentartext, Funktionscode unveraendert (prisma-tenant.extension.spec.ts
  und rls-access-inventory.spec.ts weiterhin gruen)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:41:28 +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 2a562d0b14 feat(api): Favoriten — Symbol trotz Zertifikatsfehler holen, Reihenfolge per PUT /favorites/order speichern
- icon-discovery.service.ts: undicis eigenes fetch mit Modul-Singleton
  LENIENT_TLS_AGENT (Agent({ connect: { rejectUnauthorized: false } }))
  als dispatcher in fetchWithRedirectGuard, der einzigen Ausgangsstelle
  fuer HTML-Ermittlung und Icon-Byte-Holen; SSRF-Schutz unveraendert
- undici 7.28.0 (bereits im Lockfile aufgeloest) als direkte Abhaengigkeit
  von @tessera/api via pnpm add --offline
- PUT /favorites/order (ReorderFavoritesDto) vor den :id-Routen;
  FavoritesService.reorder() setzt position=index fuer die Favoriten
  eines Widgets in EINER withTenantTransaction, userId+widgetId in jeder
  Bedingung (zweites Netz), eine BadRequestException fuer alle
  Abweichungen (T-JDD-06)
- getIcon: X-Content-Type-Options nosniff + restriktive CSP (T-JDD-02)
- 10 neue Tests (3 Dispatcher, 7 reorder); volle API-Suite 68 Dateien/
  1101 Tests und type-check gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:36:03 +02:00
schalli e7633e15de docs: CI-Desktop-Bau nur bei geaendertem Desktop-Stand — Betriebshandbuch, CI-Runbook, Entwicklungsanleitung, CHANGELOG
- anleitung-betrieb.md Kap. 10: neuer Unterabschnitt "Wann gebaut wird und
  wann Pakete uebernommen werden", Job-Dauer-Satz und Fehlerbilder-Tabelle
  ergaenzt
- ci-cd-setup.md Abschnitt 4: Absatz zum Ueberspringen bei unveraendertem
  Desktop; Abschnitt 6: neuer Fehlerbehebungs-Eintrag
- anleitung-entwicklung.md: Absatz zum CI-Ueberspringen bei "Desktop-App
  lokal bauen"
- CHANGELOG.md: ein Stichpunkt unter Unveroeffentlicht/Geaendert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:28:54 +02:00
schalli 8c4aaa51fa ci: Job desktop ueberspringt den Bau, wenn der Desktop-Stand unveraendert ist — Pakete aus dem Zwischenspeicher
- Neues Skript .gitea/scripts/desktop-stamp.sh (Unterbefehle stamp/check):
  Stempel aus Version + letztem Commit an den Desktop-Pfaden, Pruefung eines
  restaurierten desktop-dist/ gegen Manifest, Kanal, Groesse und sha256
- ci.yml Job desktop: stamp -> actions/cache/restore (Stempel-Schluessel,
  nur main) -> check -> 13 Bau-Schritte mit if: steps.reuse.outputs.reuse,
  Stempel-Save vor unveraenderter Uebergabe an publish; quality/test/publish
  und on: unveraendert (js-yaml-Tiefenvergleich gegen 38c1400)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:26:49 +02:00