Opt-out-Klasse field-plain fuer die Fluent-Unterkante; im Dunkelmodus
wirkte sie im Such-Widget wie eine weisse Linie (Nutzerwunsch 29.09.).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Die eingeklappte Seitenleiste zeigte die Kacheln in Ladefolge; ein Eintrag
aus "Eigene Module" konnte so vor einem eigenen Eintrag anderer
Kategorien stehen. Jetzt gleiche Gruppenfolge wie ausgeklappt.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CUSTOM_MODULE_CATEGORIES = MODULE_CATEGORIES + custom-modules; API prueft
dagegen, das Formular bietet sie an. Die Seitenleiste zeigt die Gruppe wie
jede Kategorie nur mit Eintrag und stellt sie immer ans Ende. Nutzerwunsch
29.09.; ohne Migration (category ist Freitext mit IsIn-Pruefung).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Die Zentrierung der belegten Spalten (Design Mosaik, Runde 3) liess
Widgets nach dem Bearbeiten springen, z. B. ein einzelnes Widget oben
links in die Seitenmitte (Nutzer, live 29.09.). Auf Wunsch ersatzlos
entfernt: Ansicht = Bearbeitungsraster. centeringOffset, breakpointFor
und die ungenutzte Prop onInsetChange entfallen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Liste, Anlegen, Bearbeiten und Löschen unter Verwaltung, Adresse wird im Formular geprüft
- Seitenleiste zieht nach jedem Speichern oder Löschen ohne Neuladen nach
- Texte deutsch und englisch, Eintrag in der Admin-Leiste hinter „Module“
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Tabelle CustomModule mit Zeilenschutz (tenant_isolation_policy), Migration 20260929120000
- API /custom-modules: Lesen für jeden Angemeldeten, Schreiben nur Administrator, nur https ohne Zugangsdaten
- Seitenleiste zeigt eigene Module unter ihrer Kategorie, Rahmen-Seite mit Sandbox und „In neuem Tab öffnen“
- MODULE_CATEGORIES als gemeinsame Liste, Zugriffsklassifikation nachgemessen fortgeschrieben
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Neuer Schalter im Bearbeitungsmodus (neben dem Griff) setzt hideTitle in
der Widget-Konfiguration (setWidgetConfig im Dashboard-Store, optimistisch
mit Ruecksetzen); in der Ansicht entfaellt die Titelzeile per
data-hide-title, Aktionen der Titelzeile bleiben oben rechts. Kategorien der
Seitenleiste 14 px, Moduleintraege 13 px / 32 px hoch. Version 1.5.2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Der Knopf Bearbeiten belegte eine eigene Zeile ueber den Widgets. Die
Aktionen rendern jetzt per Portal in einen Einhaengepunkt rechts in der
Kopfzeile (HEADER_ACTIONS_SLOT_ID); in der Ansicht nur der Stift, im
Bearbeitungsmodus Hintergrund, Widget hinzufuegen und Fertig. Version 1.5.1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Nur Formatierung und Importreihenfolge, keine Verhaltensaenderung
- Betrifft ausschliesslich Befunde, die der Merge neu eingebracht hat
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Spalte User.dashboardBackground (JSONB) samt Migration
- PATCH /users/me/dashboard-background, geprueft mit parseDashboardBackground aus @tessera/shared (Allowlist, UUID-Bildkennung)
- getMe liefert dashboardBackground normalisiert neben accentColor
- Web liest die Wahl aus dem Auth-Store, speichert ueber die Server-Aktion, alte localStorage-Wahl wird einmalig uebernommen
- Hinweistext: gilt auf jedem Geraet
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- PATCH me/dashboard-background: Allowlist, UUID-Bildkennung, Mandantenbindung
- getMe liefert dashboardBackground normalisiert
- Web: Uebernahme der alten localStorage-Wahl, Hook liest aus dem Auth-Store
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rueckmeldung Runde 3: die Reiter in der Seitenleiste gefallen nicht -
zurueck auf den Stand vor 8532b63 (Umschalter in der Kopfzeile). Statt
dessen sitzt die Begruessung jetzt zweizeilig im sonst leeren unteren
Bereich der Seitenleiste ueber "Einklappen"; ueber den Kacheln bleibt nur
die Befehlsleiste rechts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Dashboard-Reiter stehen in der aufgeklappten Seitenleiste als Liste
unter "Dashboard" (Ziehen sortiert senkrecht, Umbenennen/Loeschen/Neu
im Bearbeitungsmodus); eingeklappt und auf dem Handy wie bisher in der
Kopfzeile. Die Kopfzeile zeigt sonst den Namen des Dashboards.
- Begruessung einzeilig auf Hoehe der Befehlsleiste.
- Kachel-Groesse: kollidiert die gezogene Groesse mit einem Nachbarn, wird
nur die freie Achse uebernommen - vorher verwarf react-grid-layout mit
preventCollision die ganze Aenderung, sobald die Maus eine Zeile nach
unten wackelte (Favoriten liessen sich deshalb oft nicht schmaler ziehen).
- Ansicht: belegte Spalten mittig, Begruessung/Befehlsleiste ruecken mit.
- Kalender: einzeilige Terminliste mit "Heute"/"Morgen" in Akzentfarbe,
Ort im Tooltip, Standardhoehe 16 statt 12.
- Hintergrund "Bluete" im Dunkelmodus weggelassen (Farbstufen), Ersatz Nebel.
- Kacheln: Symbol-Chip in der Akzentfarbe, leichtes Anheben beim Zeigen,
gestaffeltes Einblenden beim Laden (ohne bei reduzierter Bewegung).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Begruessung nach Tageszeit mit Datum, Befehlsleiste oben rechts statt
schwebendem Knopf, einheitliche Kachel-Kopfzeilen mit Symbol, Kalender
mit Pfeilknoepfen, runden Tagen und Terminpunkten, Favoriten als
36-px-Zeilen bzw. App-Starter-Kacheln, Notiz-Kaestchen im Akzent,
leerer Zustand mit drei Vorschlaegen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Statt bunter Modulfarben neutrale graue Kacheln; gelb (persoenliche
Akzentfarbe) nur fuer den gewaehlten Eintrag der Seitenleiste und den
Seitenkopf des Moduls. Schrift auf Akzentflaechen wird jetzt je nach
Akzentfarbe hell oder dunkel gewaehlt (readableOnAccent). Anmelde-
Mosaik und Dashboard-Hintergruende nur noch in Gelb, Grau, Graphit;
Salbei ersetzt durch Kiesel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Einheitliche Seitenkoepfe mit Modul-Kachel, Knopf- und Linkklassen
statt gelber Schrift, Karten ohne harte Rahmen, Tabellenkoepfe ohne
Grossbuchstaben, Marktplatz mit Fluent-Reitern, Filterpillen und
Kachel-Karten, Einstellungs-/Verwaltungsnavigation mit Auswahlpille,
leere Zustaende mit Symbol im Kreis, Proxmox ohne leuchtende Schatten.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Einheitliche Kachel-Kopfzeile, ruhigere Notiz-/Kalender-/Favoriten-
Darstellung, 12 px Raster-Abstand, gestrichelte Kontur und Griff im
Bearbeitungsmodus. Neuer Knopf Hintergrund: Keiner, fuenf eingebaute
(Nebel, Salbei, Bluete, Duenen, Mosaik) oder eigenes Bild aus den
Bilderrahmen-Bildern; Prototyp speichert im localStorage je Benutzer.
Mit Hintergrund werden Kacheln Mica-artig durchscheinend (Kontrast
>= 4,5:1 auch im schlechtesten Fall).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Kopfzeile 48 px dunkel mit Seitentitel statt immer Startseite,
Dashboard-Reiter mit Akzent-Unterstrich; Seitenleiste mit
Auswahlpille, Modul-Kacheln, aufgeklappten Kategorien und gefuelltem
Suchfeld; eingeklappt nur Kacheln. Einheitlicher PageHeader.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Jedes Modul bekommt Farbe und Symbol (tender-radar Ocker, dkv-fleet
Petrol, cert-manager Violett, domaincheck Blau, proxmox Rotorange,
unbekannte stabil per Hash). Kategorien in Alltagssprache; dazu die
Texte fuer die neue Anmeldeseite.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
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>
- 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>
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>
- 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>
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>
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER
geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg
(T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe)
- dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen
Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src>
- PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser
laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen,
Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf,
Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und
Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff)
- PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall,
Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird
auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen
- Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en
(widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der alte Test 1 fand den Fehler nur durch Zufall: er las den DOM einmal,
nachdem findByRole den Dialog gemeldet hatte, und traf damit mal den
falschen ersten, meist den richtigen zweiten Commit. Ein Fehlschlag auf
rund siebzehn volle Laeufe.
Die beiden neuen Tests beobachten stattdessen per MutationObserver JEDEN
Commit waehrend des Oeffnens und dulden keinen einzigen, in dem das
Vorschaubild sichtbar ist und das Haekchen aus. Test 14 deckt das erste
Oeffnen ab, Test 15 das erneute Oeffnen nach einem Versand mit
abgewaehltem Haekchen und getipptem Text - dort darf weder der alte
Dankestext noch der alte Text noch ein ausgeschaltetes Haekchen in
irgendeinem Commit auftauchen.
Beide sind gegen den Stand vor dem Fix in fuenf von fuenf Laeufen rot,
danach gruen. Kein retry, kein hoeheres Zeitlimit: die Ursache war nie
blosse Zeit, sondern ein falscher Zustand, den es jetzt nicht mehr gibt.
Test 1 bleibt unveraendert in der Sache und prueft weiterhin dieselbe
Zusicherung.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J