Commit Graph

594 Commits

Author SHA1 Message Date
schalli 25c8db746a test(260925-bow): Anlagewege tragen die laufende Version ein (rot)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:49:21 +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 59db32a01f feat(260925-bow): gesehene Version pro Benutzer merken - Spalte, Versionsfunktionen, API
- packages/shared: parseReleaseVersion, compareReleaseVersions, ReleaseNoticeResponse
- getRunningRelease(): einzige Quelle der laufenden Version (APP_VERSION der API)
- User.lastSeenReleaseVersion (nullbar, Migration 20260925120000)
- GET /users/me/release-notice, POST /users/me/release-seen (gebunden an Benutzer und Mandant)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:42:11 +02:00
schalli b35edd5f31 test(260925-bow): Versionsvergleich und Was-ist-neu-Endpunkte (rot)
- parseReleaseVersion/compareReleaseVersions/getRunningRelease
- GET me/release-notice, POST me/release-seen: Format, nicht ueber laufend,
  nie absenken, Mandanten- und Benutzerbindung, ReleaseSeenDto

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:42:11 +02:00
schalli dd54ec5d42 feat(260924-m4n): Bilderrahmen Stufe 2 - alte Bildspalte data entfernt, storagePath Pflicht
- Migration 20260924120000_dashboard_image_drop_data: Schutzpruefung
  (bricht ab, solange eine Zeile ohne storagePath existiert; row_security
  aus, damit ein Eigentuemer ohne BYPASSRLS nicht still 0 Zeilen sieht),
  dann NOT NULL, DROP COLUMN data, DROP POLICY system_read_policy
- Dienst: Bootstrap-Umzug samt forSystem() und Selbstheilung aus data
  entfernt; Upload vergibt die UUID selbst, Zeile gleich mit Pfad
- FORSYSTEM_ALLOWED_CALL_SITES, Tests, Zugriffsklassifikation (per
  Gate-Schleife gemessen: 61/213/6) nachgezogen
- Betriebshandbuch Kap. 4: Hinweis und Wiederherstellungsweg bei Abbruch
- Todo 2026-09-22 nach completed/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 16:09:36 +02:00
schalli b10734f382 test(260924-m4n): flackernden Marktplatz-Test entschaerfen, act-Warnungen der Proxmox-Kachel weg
- tenant-selector: Komponenten statisch statt im Test dynamisch importiert
  (Laden zaehlte in die 5-s-Frist des ersten Tests), SUPER_ADMIN/ADMIN in
  zwei it aufgetrennt
- marketplace/marketplace-filters: gleiche Umstellung; userEvent an die
  falsche Uhr gekoppelt statt auf shouldAdvanceTime zu warten
- proxmox-widget: Rendern wartet das erste Laden in act() ab (81 Warnungen weg)
- Todo 2026-09-23 nach completed/

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 16:02:45 +02:00
schalli 586da44fa1 feat(260924-i8v): Titel und Serverauswahl der Proxmox-Kachel an der Kachel und in den Einstellungen
- ProxmoxServerPicker gemeinsam fuer Kachel und Einstellungsformular
- Bearbeitungsmodus: entprelltes Titelfeld, Server auswaehlen statt Liste, sofort gespeichert
- ProxmoxWidgetConfigForm unter Einstellungen > Dashboard, Titel-Zusatz in der Kopfzeile

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:33:48 +02:00
schalli 377b6e37b2 test(260924-i8v): RED fuer Titel und Serverauswahl der Proxmox-Kachel
- Auswahl-Bauteil: Reihenfolge nach position, Produktwort, Aufraeumen geloeschter Kennungen
- Einstellungsformular: Laden, Fehler, Leer, onChange fuer Titel und Auswahl
- Einstellungsbereich: Zweig proxmox mit Titel-Zusatz in der Kopfzeile
- Kachel im Bearbeitungsmodus: entprellter Titel, Auswahl statt Liste, schliesst beim Verlassen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:30:22 +02:00
schalli a217d606bb feat(260924-i8v): Serverliste mit Kennzahl, Auswahlfilter, Links und Minutentakt der Proxmox-Kachel
- Zeilen nach down, warn, ok, idle, orphan mit genau einer Kennzahl, unbekannt statt 0
- config.serverIds filtert Liste und Balken, nur geloeschte Kennungen: eigener Satz
- Ansichtsmodus Links auf /modules/proxmox, Bearbeitungsmodus ohne Links
- Groessenstufen per Container-Query, Nachladen alle 60 s, pausiert bei verborgenem Tab
- formatPercent/formatCount einmal in components/proxmox, ServerCard nutzt sie

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:28:43 +02:00
schalli 92bf1307f6 test(260924-i8v): RED fuer Serverliste, Kennzahlen, Auswahl und Minutentakt der Proxmox-Kachel
- Modell: resolveProxmoxWidgetConfig, selectServers, widgetKeyFigure
- formatPercent/formatCount gemeinsam in proxmox-status
- Kachel: Sortierung, Kennzahltexte, unbekannt statt 0, Filter, Links, Titel, Takt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:26:40 +02:00
schalli a906c6705d feat(260924-i8v): Proxmox-Kachel als Durchstich mit Balken und Zusammenfassung
- Typ proxmox am Ende von WIDGET_TYPES, WIDGET_MODULE_SLUGS = { proxmox: 'proxmox' }
- gemeinsame Statusteile (proxmox-status, HealthBar, status-styles) nach components/proxmox/
- HealthBar variant compact: 6 px, ohne Legende, aria-hidden
- Registry 3/4/8/8, Katalog-/Registry-/API-Tests auf die erste Modul-Kachel umgestellt
- ProxmoxWidget liest nur listServers, zeigt Lade-, Leer-, Fehlerzustand

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:24:51 +02:00
schalli 60b6973933 fix(260924-h7x): Proxmox-Karten im Spaltenfluss statt Zeilenraster
Eine kurze Karte liess im Raster eine Luecke bis zur hoeheren Nachbarkarte.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:48:56 +02:00
schalli 57a4196c4b fix(260924-h7x): Nachschliff nach Browser-Rundgang
- Kopfzeile unter 640 px nur mit Bildmarke, damit die Dashboard-Reiter
  lesbar bleiben (vorher war vom Reiter nur "Das" zu sehen)
- Proxmox-Karten strecken sich nicht mehr auf gleiche Zeilenhoehe
- PBS-Pruefstatus auf Deutsch ("Pruefung fehlgeschlagen"), Rohwert als title

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:44:37 +02:00
schalli 0b659d67e8 fix(260924-h7x): verwaiste Karte daempft nur Symbol und Name
Gedaempfte muted-Schrift fiele auf 2,75:1 (hell) bzw. 3,0:1 (dunkel),
gemessen; Produktname, Adresse und Fussangabe bleiben deshalb voll lesbar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:38:55 +02:00
schalli 7416a925a6 feat(260924-h7x): Dashboard-Reiter als Umschalter in der Kopfzeile
- Kopfzeile bekommt Einhaengepunkt header-center-slot; auf / entfaellt der Text Startseite
- DashboardTabs rendert per createPortal dorthin, eigene Zeile ueber dem Raster entfaellt
- eingelassene Spur mit erhabenem aktivem Reiter, waagrecht scrollbar mit weicher Randausblendung
- role=tablist/tab, Pfeiltasten/Pos1/Ende, sichtbarer Fokusring, aktiver Reiter wird ins Bild gescrollt
- Plus-Knopf ausserhalb der Spur, Loeschdialog am Dokumentkoerper, Ziehhinweis als sr-only/Tooltip

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:38:08 +02:00
schalli 57c338fe53 feat(260924-h7x): Proxmox-Seite mit Status-Design und Tiefe
- Statusfarben als OKLCH-Tokens (Flaeche + kontrastgepruefte Textvariante) und Well-Token
- Gesundheitsbalken mit Legende und vorgelesener Zusammenfassung
- Karten nach Zustand sortiert, Statusleiste, getoenter Schatten, Statuspille
- Messwerte in eingelassenen Feldern: PVE-Knoten mit Balken, PBS-Fuellstand/Sicherung/Pruefung, PMG-Zahlfelder
- offline & verwaist: gestrichelt, gedaempft, keine alten Messwerte
- Skelett-Karten beim Laden, Leerzustand als Well, relative Zeitangaben

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:34:16 +02:00
schalli 0fa7ce0f44 feat(260924-h7x): Proxmox-Statuslogik als reine Funktionen
- serverHealth mit Vorrang fuer offline & verwaist (isActive=false)
- THRESHOLDS als einzige Schwellenquelle, meterLevel fuer Balken
- Sortierung/Zusammenfassung je Zustand, formatAge ueber Intl.RelativeTimeFormat
- null erzeugt nie eine Warnung (24 Tests)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:27:05 +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 7704372c3c feat(260923-lrr): API — Favoriten-Symbol hochladen, Vorrang, Versionszaehler, Abrufprobe
- FavoriteLink: neue Spalten uploadedIconMime/iconVersion (Migration 20260923160000)
- favorite-icon-files.ts: Erkennung PNG/JPEG/GIF/WebP/ICO/SVG, Pfadbildung ohne
  Byte aus der Anfrage im Pfad (T-LRR-01), best-effort Dateientfernung
- FavoritesService: uploadIcon/removeUploadedIcon, Vorrang der hochgeladenen
  Datei in getIconBytes, Abrufprobe fuer eine neue iconUrl (422 statt stiller
  Speicherung), iconVersion-Erhoehung bei jeder Aenderung der Symbolquelle
- FavoritesController: POST/DELETE /favorites/:id/icon, Cache-Control private
- T-LRR-07 (Restrisiko aus dem Plan-Threat-Model geschlossen, ueber den Plan
  hinaus): DashboardService.removeWidget/deleteDashboard raeumen jetzt die
  Symboldateien der per Datenbank-Kaskade mitgeloeschten Favoriten auf
  (best effort, nie blockierend)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:04:29 +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 b03ffb5d10 fix(dashboard): Favoriten-Kachel bis auf eine Spalte schmal ziehbar
Bei kurzen Linknamen blieb rechts viel Leerraum; minW 3 -> 1. Der Titel
kuerzt mit Auslassungszeichen, das Symbol bleibt stehen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:40:26 +02:00
schalli e1b191bf0b fix(260923-le6): Aktualisieren-Knopf der Proxmox-Seite nur fuer Admins
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:33:32 +02:00
schalli 2f8dd14bfb fix(260923-le6): Proxmox-Karte verweist Nicht-Admins nicht auf den Aktualisieren-Knopf
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:31:58 +02:00
schalli 2eb86e14ea fix(260923-le6): PMG-Summe null bei fehlendem Teilwert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:30:58 +02:00
schalli c13d657e41 test(260923-le6): PMG-Teilsumme ohne Haelfte muss null sein
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:30:31 +02:00
schalli f1bb7f7191 fix(260923-ku6): ruhiger "noch nicht abgefragt"-Zustand und Adresse ohne Grossschreibung
Zwei weitere Nachbesserungen aus dem Rundgang zu 260923-dhh, beide in
ServerCard.tsx und deshalb in einem Commit:

Befund 2: ein frisch angelegter, noch nie abgefragter Server zeigte
faelschlich "Ein unerwarteter Fehler ist aufgetreten" — die leere
Zwischenlagerzeile aus createServer hat `reachable: false` und
`errorKind: null`, was bisher blind in die Fehler-Uebersetzung `unbekannt`
lief. Neuer ruhiger Zustand fuer `status.lastPolledAt === null`, der auf
"Jetzt aktualisieren" verweist; die bestehenden Fehlermeldungen (inkl.
`unbekannt` fuer echte unbekannte Fehler) bleiben fuer `lastPolledAt !== null`
unveraendert.

Befund 3: die Klasse `uppercase` sass auf der ganzen Statuszeile und faerbte
dadurch auch die Adresse gross ("PVE — HTTPS://..."). Jetzt nur noch auf dem
Produktkuerzel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:12:12 +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 fccaf8db0f feat(260923-dhh): Proxmox-Modul Aufgabe 4 - Hintergrundabfrage je Mandant, Verbindungstest
- proxmox-scheduler.service.ts: ein Cron-Auftrag je aktivem Mandant
  (proxmox-poll:<tenantId>), onApplicationBootstrap (nicht onModuleInit,
  Tender-Muster), Abfrageintervall = kleinstes pollIntervalMin der
  aktiven Server, ein fehlgeschlagener Server bricht die Tick-Schleife
  nicht ab, refreshTenant() zieht nach jedem Speichern sofort nach
- proxmox.service.ts: loadActiveServersForScheduler() als einziger
  forSystem()-Aufruf des Moduls (Erlaubnisliste in
  rls-access-inventory.spec.ts), testConnection() schreibt nicht ins
  Zwischenlager, pollServer() bekommt eine Zehn-Sekunden-Sperre (T-DHH-06)
- proxmox.controller.ts: POST servers/:id/test, create() zieht den
  Planer nach dem Anlegen sofort nach
- Zugriffsklassifikation: proxmoxServer wechselt auf system-gebunden
  (Startpfad des Planers), proxmoxServerStatus bleibt gebunden

Tore: api 1306/1306 (>=1240), type-check 4/4, rls-access-inventory
und rls-coverage gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 14:34:11 +02:00
schalli 998aba9ef3 feat(260923-dhh): Proxmox-Modul Aufgabe 3 - PBS und PMG auswerten
- proxmox-normalize.ts: nachsichtige Leser (readNumber/readText/readBool/
  readList) und normalizePve/normalizePbs/normalizePmg als reine
  Funktionen, nie ein Wurf bei unerwarteter Form
- PVE ergaenzt um je Speicherort Belegung (storages)
- PBS: Belegung je Datenspeicher plus letzte Sicherung/Pruefergebnis aus
  bis zu 10 Folgeabfragen je Durchlauf (Deckel in proxmox.service.ts)
- PMG: Tageszahlen eingehend/ausgehend/Spam/Viren
- Feldnamen je Produkt als benannte Konstante (Annahmen A3/A5 der
  Recherche), mehrere plausible Namen je Feld moeglich
- proxmox.service.ts: produktabhaengige Abfragefolge, Ticket-Erneuerung
  jetzt je Durchlauf statt je Aufruf (PBS-Mehrfachabfragen loggen nicht
  mehrfach neu ein)
- proxmox-nur-lesen.spec.ts: Riegel erkennt jetzt auch den Umschlag
  getWithRetry als zulaessige Aufrufform

Tore: api 1293/1293 (>=1240), type-check 4/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 10:28:25 +02:00
schalli 4f8a368c9e test(260923-dhh): Proxmox-Modul Aufgabe 2 - Benutzer/Passwort, Fehlerklassen, Nur-Lesen-Riegel
- proxmox-auth.ts: loginTicket (die einzige nicht-lesende Anfrage im
  Modul, POST /access/ticket) und buildTicketCookieHeader je Produkt
  (Cookie-Namen als benannte Konstante, Annahme A2 kommentiert)
- proxmox-client.service.ts: classifyFailure (401->zugang, 403->rechte,
  404->antwortform, 5xx->server, Netzfehler->netz, Zertifikatsfehler->
  zertifikat) und parseJsonLenient (kein Wurf bei Nicht-JSON); kein
  explizites method-Feld mehr an proxmoxGet (GET ist Grundwert)
- proxmox.service.ts: Passwort-Zweig via Ticket-Anmeldung, genau ein
  zweiter Versuch nach 401 (Ticket-Ablauf alle zwei Stunden kein
  Fehlalarm)
- proxmox-nur-lesen.spec.ts: maschinischer Riegel zu D-01 — genau eine
  Stelle (proxmox-auth.ts) uebergibt ein Anfrageverfahren an
  undiciFetch, jeder Proxmox-Pfad ausserhalb laeuft ueber proxmoxGet

Tore: api 1270/1270 (>=1240), type-check 4/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 10:23:22 +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 df7a5e7e8e feat(quick-260923-ad9): Reiter anlegen, umbenennen, loeschen, umsortieren - Task 2
dashboard.service.ts: createDashboard() (Namensvergabe "Dashboard N" fuellt
Luecken, D-08; Obergrenze 20 Reiter, T-AD9-06), renameDashboard() (Riegel
zuerst), deleteDashboard() (letzter Reiter bleibt, D-10; Loeschen + Neu-
Nummerierung als EINE withTenantTransaction), reorderDashboards() (woertlich
nach FavoritesService.reorder-Muster: Exakt-Abgleich vor jedem Schreiben,
kein Teilschreiben, dieselbe Abweisung fuer unvollstaendige/unbekannte/
fremde Kennungen - T-AD9-04).

dashboard.controller.ts: fuenf neue Wege unter tabs; PUT tabs/order VOR
PATCH/DELETE tabs/:id deklariert (Routenreihenfolge).

dashboard.controller.spec.ts (neu, 8 Tests): Durchreichung, Abweisung ohne
Kontext, quelltextlesender Waechter fuer die Routenreihenfolge.

dashboard.service.spec.ts: 61 Tests (43 alte + 18 neue fuer Anlegen,
Umbenennen inkl. DTO-Beschneidung/-Laengenpruefung, Loeschen und
Umsortieren - je ein Fall fuer "fremder Reiter" und "letzter Reiter bleibt").

Deviation (Rule 1): widget-module-map.spec.ts's CreateWidgetDto-Whitelist
helper needed a dashboardId fixture after Task 1 made the field required -
fixed inline, out of the plan's files_modified list but directly caused by
Task 1's DTO change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 08:00:59 +02:00
schalli 9c518238f5 feat(quick-260923-ad9): Datenmodell, Migration und Reiter-Grundlage - Task 1
Neues Modell Dashboard (D-01/D-02/D-09): position statt Standard-Feld,
kein Unique auf (userId, position) - Umsortieren schreibt spaeter alle
Positionen einer Transaktion neu. WidgetInstance/DashboardLayout haengen
jetzt am Reiter statt am Benutzer (DashboardLayout.dashboardId @unique
ersetzt userId @unique).

Migration 20260923120000_dashboard_tabs: Zeilenschutz mit Mandant- UND
Benutzerdimension (Form 20260911120000/20260921120000), Bestands-
uebernahme fuer jeden Benutzer mit Kacheln oder Anordnung VOR den
Fremdschluesseln (D-03) - gemessen: 0 Kacheln/Anordnungen ohne Reiter,
genau 2 Reiter auf Position 0.

dashboard.service.ts: listDashboards() (Transaktionssperre gegen
doppelte Erstanlage, T-AD9-07), Riegel assertOwnedDashboard() (fail-
closed gegen fremde Reiter, T-AD9-01/02/03) - getLayout/saveLayout/
getWidgets/addWidget laufen jetzt ueber dashboardId statt userId.
GET /dashboard/tabs neu; die vier bestehenden Wege reichen die Reiter-
Kennung durch. Verhalten fuer den Benutzer unveraendert (ein Reiter,
wie bisher) - Task 2 ergaenzt Anlegen/Umbenennen/Loeschen/Umsortieren.

dashboard.service.spec.ts: 43 Tests (31 alte unveraendert + 12 neue fuer
Reiter-Anlage, -Reihenfolge und den Fremdreiter-Riegel bei allen vier
Wegen). Zugriffsklassifikation nachgerechnet: 75 Paare (+1), Bereich
dashboard 21->24 gebunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 07:56:38 +02:00
schalli d9f2af32d6 fix(quick-260922-vdk): Dashboard-Raster misst seine Breite auch aus dem Leerzustand heraus
- Messung an Ref-Rueckruf measureRef gehaengt statt an Effekt mit leerer
  Abhaengigkeitsliste: der Effekt sah den gemessenen Knoten nie, wenn er
  waehrend eines leeren Dashboards einhaengt, weil der fruehe Ruecksprung
  in den Leerzustand das <div> gar nicht rendert
- Synchrone Erstmessung in der Commit-Phase, vor dem ersten Zeichnen
- Fenster-Horcher als Netz, zusaetzlich zum ResizeObserver
- applyWidth verwirft 0 und nicht endliche Werte (T-VDK-01/T-VDK-03)
- Beobachter trennt sich im null-Zweig des Ref-Rueckrufs (T-VDK-02)
- Zwei neue Regressionstests (Test 10/11), zuerst rot nachgewiesen
- FREE_PLACEMENT_COMPACTOR, Konstanten, Leerzustand unveraendert

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 22:45:24 +02:00
schalli 8be0725577 docs(quick-260922-m1h): Hinweis bei gesperrter Kachel, Changelog und Entwicklerdoku
- eine Kachel ohne Bauteil (entfernter Typ oder gesperrtes Modul) zeigt
  statt des rohen Typnamens den Satz `widgets.unavailable`, zentriert und
  grau; Schluessel in de.json und en.json
- Entwicklerdoku: neuer Abschnitt "Eine Kachel zum Modul" im
  Modul-Walkthrough — die drei verbliebenen Stellen und was eine Kachel
  mit moduleSlug automatisch tut
- Changelog unter "Unveroeffentlicht - Geaendert"

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:06:25 +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 82472ee665 fix(quick-260922-hk4): fehlende Bilddatei aus der alten data-Spalte wiederherstellen
Beim Rundgang aufgefallen: nach dem Umzug zeigt eine Zeile auf eine Datei,
die es auf diesem Server nicht gibt — lokal, weil der Umzug am Host lief und
der Container ein eigenes Volume hat. Derselbe Zustand entsteht im Betrieb,
wenn jemand einen `pg_dump` von vor dem Umzug zurueckspielt: die Bytes
stecken noch in der Spalte `data`, das getrennt gesicherte Volume
`user-files` ist aber leer.

`getBytes` schreibt die Datei in diesem Fall aus `data` neu und liefert sie
aus, statt 404 zu melden. Fehlt beides, bleibt es bei 404. Nach dem
Entfernen der Spalte (eigenes Todo) faellt der Zweig ersatzlos weg.

api-Tests 1186 -> 1188, type-check und lint unveraendert gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 15:33:55 +02:00
schalli 9039cea686 refactor(quick-260922-hk4): Bilderrahmen-Bilder in user-files statt in der Datenbank
- Bytes liegen unter user-files/dashboard-images/<userId>/<id>.<ext>, die
  Zeile haelt nur noch storagePath (Muster User.avatarPath)
- Dateiname immer servergeneriert: UUID der Zeile + Endung aus dem
  ERKANNTEN Mime-Typ, originalName kommt in keinem Pfad vor (T-HK4-01)
- Migration 20260922120000: storagePath dazu, data wird NULLbar, kein DROP
  (zweistufig, T-HK4-03); system_read_policy fuer den Umzug
- onApplicationBootstrap zieht Altbestand automatisch um: systemgebunden
  lesen, je Zeile mandantengebunden schreiben (Muster DKV-Planer)
- Upload nimmt die Zeile bei fehlgeschlagenem Schreiben zurueck, Loeschen
  entfernt die Datei mit, fehlende Datei -> 404 (T-HK4-04)
- 11 neue Dienst-Tests gegen ein echtes Temp-Verzeichnis (kein fs-Mock)
- Zugriffsklassifikation: Stand system-gebunden, Zahlen nachgemessen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 15:22:47 +02:00
schalli cf70a197cd fix(quick-260922-ge2): Rahmenhoehe in der Kachel = Vorschauhoehe, Vorschau ohne Querbalken
Befund aus dem Browser-Rundgang: example.com setzt `margin: 15vh` — mit
3000 px Vorschauhoehe lag die Ueberschrift bei y 450, in der Kachel mit
720 px Rahmenhoehe bei y 108. Der in der Vorschau gewaehlte Ausschnitt
zeigte in der Kachel also etwas anderes. Die Kachel nutzt jetzt dieselbe
Layouthoehe wie die Vorschau (3000), damit vh-relative Seiten identisch
umbrechen; der Rest wird ohnehin weggeschnitten.

Vorschau: `overflow-x-hidden`, die Eckgriffe am rechten Rand erzeugten
einen 4-px-Querbalken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 12:22:37 +02:00
schalli 445b1d3100 feat(quick-260922-ge2): XFrame-Widget - Ausschnitt der Seite waehlen und einpassen, Zoom fuer die ganze Seite, Nur anzeigen
- Resolver: crop (x/y/w/h, geklemmt auf 1280er-Seite, x verschoben statt abgewiesen), zoom (50..150, groesste Stufe <= n), readOnly (nur echtes true)
- xframe-crop.ts: reine Geometrie - computeCropLayout (contain + zentriert, scale 0 bis gemessen), applyCropDrag (Verschieben, vier Ecken, Gegenecke bleibt)
- Kachel: Clip + verschobener, skalierter <iframe> bei fester Layoutbreite 1280, ResizeObserver am Koerper; Zoom-Zweig mit Prozentmassen; 100 % wie bisher; readOnly-Flaeche nur im Ansichtsmodus
- Formular: Checkbox Ausschnitt (ein Aufruf mit crop + readOnly), Vorschau 1280 px breit mit derselben Sandbox und pointer-events none, Rahmen mit vier Griffen (Pointer-Events, Capture-Waechter fuer jsdom), Zahlenfelder, Zoom-Auswahl nur ohne Ausschnitt, Nur anzeigen, dauerhafter Hinweis
- Rahmen als <fieldset> statt div role=group (Biome useSemanticElements, kein biome-ignore)
- Test-Helfer stubResizeObserver, 13 neue Schluessel de/en, Ausschnitt auf der Umlaut-Allowlist; Web-Tests 604 -> 640

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 12:12:18 +02:00
schalli 747a4d432b fix(desktop): Download-Knoepfe in der App reichen an den System-Browser weiter
Befund des Nutzers (22.09.2026): "Herunterladen" unter Einstellungen ->
Desktop-App tut in der App nichts, unter Windows wie Linux. Die Webansicht
hatte keinen Download-Handler; webkit2gtk verwirft Downloads dann still,
WebView2 zeigte ebenfalls nichts.

Das Hauptfenster entsteht jetzt im Code (app.windows in tauri.conf.json
leer), weil nur der Builder `on_download` annimmt. Der Handler bricht den
Download in der App ab und oeffnet die Adresse ueber den Opener im
System-Browser -- mit Fortschritt, Speicherort und Passwortfenster fuer
einen vorgeschalteten Proxy. Masse, Zentrierung, Titel wie bisher; die
Capability "main" gilt unveraendert.

cargo fmt/clippy/test/check gruen (44 Tests).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 11:43:27 +02:00
schalli d73aad1ef1 fix(desktop): Update-Eintrag nennt den Grund einer fehlgeschlagenen Pruefung, Klick prueft erneut, Pruefung alle 4 h
Befund 22.09.2026: alpha bietet 1.2.0-beta.gc001a08 an, der Client auf
a6d1a64 zeigt aber nur den grauen Eintrag "Update installieren". Der
Nginx Proxy Manager vor alpha beantwortet die Update-Anfrage mit 401
(Basic-Auth); die Webansicht kann das Passwortfenster beantworten, der
Updater (eigener reqwest-Client) nicht. tauri-plugin-updater verschluckt
einen Nicht-2xx-Status (updater.rs Z. 529-559: nur Log, last_error
leer, Ergebnis Err(ReleaseNotFound)), und spawn_version_check fing das
mit Err(_) => {} stumm ab. Ein fehlgeschlagener Check war damit vom
Zustand "kein Update" nicht unterscheidbar, und ohne Neustart gab es
keinen Weg, erneut zu pruefen.

- Drei Endzustaende, alle anklickbar: "Auf Version/Beta-Stand ...
  aktualisieren", "Kein Update verfuegbar - erneut pruefen",
  "Update-Pruefung fehlgeschlagen (HTTP n | keine Verbindung) - erneut
  pruefen"; waehrend der Pruefung "Suche nach Updates..." (gesperrt),
  http-Server unveraendert gesperrt
- Bei ReleaseNotFound stellt der Client dieselbe Anfrage einmal selbst
  (Platzhalter ersetzt, Timeout 8 s) und liest nur den Statuscode;
  401/403 erklaeren den Proxy-Passwortschutz, Zugangsdaten werden
  bewusst NICHT in den Client eingebaut
- Benachrichtigung je unterschiedlichem Fehlertext einmal
  (LastCheckNotice), Erfolg leert die Entprellung
- Wiederhol-Thread alle 4 h (std::thread, kein neues Crate), liest die
  Adresse frisch, ueberspringt bei bereits abgelegtem Update
- Klick ohne abgelegtes Update prueft erneut; der Browser-Weg bleibt nur
  Rueckfall einer fehlgeschlagenen Installation
- 7 neue Tests (Labels, Diagnose-URL, Konstanten), zuvor rot (E0425)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 11:26:09 +02:00
schalli 8b45a281be fix(dashboard): Bilderrahmen - "1 Stunde" statt "60 Minuten", Bildanzahl in der Einstellungs-Kopfzeile
Zwei Kosmetik-Punkte nach dem Browser-Rundgang vom 21.09.2026:

- Das laengste Wechselintervall (3600 s) hiess "60 Minuten", beim XFrame
  heisst dieselbe Stufe "Jede Stunde". Neuer Schluessel
  `pictureFrame.intervalHours` (ICU-Plural, de + en).
- Unter Einstellungen -> Dashboard zeigte "XFrame #1 - Board" seinen Titel,
  "Bilderrahmen #1" nichts. Die Kopfzeile nennt jetzt "- 1 Bild" bzw.
  "- N Bilder" (Schluessel `imageCountOne`/`imageCountMany`, zwei Schluessel
  statt ICU, weil die Panel-Tests eine einfache Uebersetzungs-Attrappe nutzen).

Web-Tests 603 -> 604, type-check und lint unveraendert gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 08:21:23 +02:00
schalli d63d9f5563 feat(quick-260921-qd3): XFrame-Widget - Webseite als Rahmen im Dashboard, Sandbox ohne Top-Navigation, Neuladen-Intervall
- xframe-config.ts: Resolver (https-Pruefung via isHttpsUrl des Bilderrahmens,
  Titel bis 100 Zeichen, Neuladen 0/60/300/600/1800/3600 s geklemmt),
  XFRAME_SANDBOX ohne allow-top-navigation und allow-modals; 12 Tests zuerst rot
- xframe-widget.tsx: genau ein <iframe> (sandbox, allow="", no-referrer, lazy),
  Kopfleiste mit Titel oder Ecksymbol "In neuem Tab oeffnen", Neuladen ueber
  key-Wechsel mit Timer-Raeumung, transparente Flaeche im Bearbeitungsmodus
  damit die Kachel Ziehgriff bleibt; 12 Tests zuerst rot
- xframe-config-form.tsx: Adresse/Titel mit Uebernahme bei Blur/Enter, http wird
  mit Meldung abgewiesen und nicht gespeichert, Intervall-Auswahl, dauerhafter
  Hinweis auf verweigertes Einbetten; 8 Tests
- Panel-Zweig samt "— Titel" in der Kopfzeile, Registry (12x12, Fenster-Symbol),
  Katalog, Seite, DTO @IsIn, de/en widgets.xframe (15 Schluessel), Umlaut-Allowlist
  "neuem"; der Server ruft die Adresse nie ab

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:18:48 +02:00
schalli 8bf3601a18 fix(quick-260921-pi9): Grossansicht per Portal, "1 Minute" statt "1 Minuten", Kachel-Vorgabe 8x12
Befunde aus dem Browser-Rundgang am 21.09.2026:

- Die Grossansicht lag in einem `react-grid-item` mit CSS-`transform`; ein
  transformierter Vorfahr wird fuer `position: fixed` zum Bezugsrahmen, der
  Dialog war deshalb auf die Kachelflaeche (531x216) beschraenkt statt den
  Viewport zu fuellen. Jetzt per `createPortal` in `document.body`, wie der
  Kalender-Tooltip.
- Wechselintervall "1 Minuten" -> ICU-Plural (`one {# Minute}`), de + en; der
  Formular-Test nutzt dafuer den echten `createTranslator` von next-intl auf
  der echten de.json statt eines `{n}`-Ersatzes.
- Standardgroesse 8x8 (216 px hoch) war zu flach fuer ein Foto -> 8x12 wie die
  Kalender-Vorgabe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:10:52 +02:00