From 9c9e1420fed985e07dbfafc1152a5397b52638fe Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 29 Sep 2026 09:53:08 +0200 Subject: [PATCH] feat(260929-dmx): Widget-Breiten im 48er-Raster, Kalender schmaler ziehbar - minW/defaultW aller Widgets verdoppelt (gleiche Bildschirmbreite) - Kalender minW 8 (rund 250 px), defaultW 16 - Test: bestehender Kalender bekommt das neue Minimum in jedem Breakpoint Co-Authored-By: Claude Opus 5.5 (1M context) --- .../dashboard/dashboard-grid.test.tsx | 61 +++++++++++++++---- .../dashboard/widget-registry.test.tsx | 28 ++++----- .../components/dashboard/widget-registry.tsx | 58 ++++++++++-------- .../src/lib/stores/dashboard-store.test.ts | 4 +- 4 files changed, 100 insertions(+), 51 deletions(-) diff --git a/apps/web/src/components/dashboard/dashboard-grid.test.tsx b/apps/web/src/components/dashboard/dashboard-grid.test.tsx index 172bada..08de3e2 100644 --- a/apps/web/src/components/dashboard/dashboard-grid.test.tsx +++ b/apps/web/src/components/dashboard/dashboard-grid.test.tsx @@ -233,8 +233,9 @@ describe('DashboardGrid', () => { const first = children[0]; expect(isValidElement(first)).toBe(true); const props = (first as React.ReactElement>).props; - // quick-260916-dyv: Uhr-Minimum 2x2 (vorher verdoppelt 4x4), Vorgabe 4x4 unveraendert. - expect(props['data-grid']).toEqual({ x: 0, y: 0, w: 4, h: 4, minW: 2, minH: 2 }); + // quick-260916-dyv: Uhr-Minimum 2 Zeilen hoch; quick-260929-dmx: Breiten im + // 48er-Raster verdoppelt (Minimum 4, Vorgabe 8), Hoehen unveraendert. + expect(props['data-grid']).toEqual({ x: 0, y: 0, w: 8, h: 4, minW: 4, minH: 2 }); }); it('quick-260916-dyv Test 9: gespeicherte minW/minH werden in JEDEM Breakpoint aus WIDGET_CONSTRAINTS ueberschrieben, x/y/w/h bleiben, unbekannte Typen und Schluessel unveraendert', async () => { @@ -265,9 +266,9 @@ describe('DashboardGrid', () => { const passed = captured.props?.layouts as Record>>; expect(Object.keys(passed)).toEqual(Object.keys(layouts)); - expect(passed.lg[0]).toEqual({ i: 'inst-1', x: 2, y: 4, w: 6, h: 6, minW: 2, minH: 2 }); - expect(passed.md[0]).toEqual({ i: 'inst-1', x: 0, y: 0, w: 4, h: 4, minW: 2, minH: 2 }); - expect(passed.md[0].minW).toBe(2); + expect(passed.lg[0]).toEqual({ i: 'inst-1', x: 2, y: 4, w: 6, h: 6, minW: 4, minH: 2 }); + expect(passed.md[0]).toEqual({ i: 'inst-1', x: 0, y: 0, w: 4, h: 4, minW: 4, minH: 2 }); + expect(passed.md[0].minW).toBe(4); // Unbekannter Typ: Eintrag bleibt woertlich erhalten (kein Absturz, T-DYV-01). expect(passed.lg[1]).toEqual({ i: 'inst-x', x: 10, y: 0, w: 5, h: 5, minW: 7, minH: 7 }); expect(passed.sm).toEqual([]); @@ -278,9 +279,9 @@ describe('DashboardGrid', () => { const { DashboardGrid } = await import('./dashboard-grid'); const layouts = { lg: [ - // Rechner: gespeichert mit h 8 (bwo-Minimum), neues Minimum 3x9 -> h wird 9, w 6 bleibt. + // Rechner: gespeichert mit h 8 (bwo-Minimum), neues Minimum 6x10 -> h wird 10, w 6 bleibt. { i: 'calc-1', x: 0, y: 0, w: 6, h: 8, minW: 4, minH: 8 }, - // Suche: gespeichert mit w 4 (unter minW 6) -> w wird 6, h 4 bleibt. + // Suche: gespeichert mit w 4 (unter minW 12) -> w wird 12, h 4 bleibt. { i: 'srch-1', x: 6, y: 0, w: 4, h: 4, minW: 6, minH: 4 }, ], md: [{ i: 'calc-1', x: 0, y: 0, w: 3, h: 7, minW: 4, minH: 8 }], @@ -302,15 +303,53 @@ describe('DashboardGrid', () => { ); const passed = captured.props?.layouts as Record>>; - expect(passed.lg[0]).toEqual({ i: 'calc-1', x: 0, y: 0, w: 6, h: 10, minW: 3, minH: 10 }); - expect(passed.lg[1]).toEqual({ i: 'srch-1', x: 6, y: 0, w: 6, h: 4, minW: 6, minH: 2 }); - // Jeder Breakpoint: md-Rechner 3x7 -> 3x10 (w schon = minW, h angehoben). - expect(passed.md[0]).toEqual({ i: 'calc-1', x: 0, y: 0, w: 3, h: 10, minW: 3, minH: 10 }); + expect(passed.lg[0]).toEqual({ i: 'calc-1', x: 0, y: 0, w: 6, h: 10, minW: 6, minH: 10 }); + expect(passed.lg[1]).toEqual({ i: 'srch-1', x: 6, y: 0, w: 12, h: 4, minW: 12, minH: 2 }); + // Jeder Breakpoint: md-Rechner 3x7 -> 6x10 (w und h auf das Minimum angehoben). + expect(passed.md[0]).toEqual({ i: 'calc-1', x: 0, y: 0, w: 6, h: 10, minW: 6, minH: 10 }); // Eingabe unveraendert (keine Mutation des Store-Objekts). expect(layouts.lg[0].h).toBe(8); expect(layouts.lg[0].minH).toBe(8); }); + it('quick-260929-dmx Test 9c: bestehender Kalender (migrierte Werte minW 12, w 16) bekommt minW 8 in JEDEM Breakpoint und kann schmaler gezogen werden; zu schmale Breite wird auf 8 angehoben', async () => { + captured.props = null; + const { DashboardGrid } = await import('./dashboard-grid'); + // So sieht ein aus v2 (minW 6 x2) migrierter Kalender im Zustand aus. + const layouts = { + lg: [ + { i: 'cal-1', x: 4, y: 0, w: 16, h: 16, minW: 12, minH: 8 }, + { i: 'cal-2', x: 24, y: 0, w: 6, h: 16, minW: 12, minH: 8 }, + ], + md: [{ i: 'cal-1', x: 0, y: 0, w: 16, h: 16, minW: 12, minH: 8 }], + sm: [], + xs: [], + xxs: [], + }; + render( + , + ); + + const passed = captured.props?.layouts as Record>>; + // Breite bleibt (Bildschirmgroesse unveraendert), nur das Minimum sinkt. + expect(passed.lg[0]).toEqual({ i: 'cal-1', x: 4, y: 0, w: 16, h: 16, minW: 8, minH: 8 }); + expect(passed.md[0]).toEqual({ i: 'cal-1', x: 0, y: 0, w: 16, h: 16, minW: 8, minH: 8 }); + expect(passed.lg[1]).toEqual({ i: 'cal-2', x: 24, y: 0, w: 8, h: 16, minW: 8, minH: 8 }); + // Auch der data-grid-Wert der Kinder traegt das neue Minimum. + const children = Children.toArray(captured.props?.children as React.ReactNode); + const grid = (children[0] as React.ReactElement>).props['data-grid'] as Record; + expect(grid.minW).toBe(8); + }); + const CANCEL = 'input, textarea, select, button, a, [contenteditable], [data-no-drag], .widgetNoDrag'; it('quick-260916-dyv Test 6: dragConfig-Pin — handle Karte, cancel fuer Interaktives, threshold 3; resizeConfig folgt dem Bearbeitungsmodus', async () => { diff --git a/apps/web/src/components/dashboard/widget-registry.test.tsx b/apps/web/src/components/dashboard/widget-registry.test.tsx index b2d2295..457ae33 100644 --- a/apps/web/src/components/dashboard/widget-registry.test.tsx +++ b/apps/web/src/components/dashboard/widget-registry.test.tsx @@ -67,21 +67,21 @@ describe('WIDGET_CONSTRAINTS (DASH-11)', () => { }); it('Test A (quick-260916-dyv): Minima = kleinste bedienbare Kachel je Typ, Vorgaben unveraendert (quick-260916-iex: Link-Widget entfernt; quick-260921-pi9: Bilderrahmen dazu; quick-260921-qd3: XFrame dazu; quick-260924-i8v: Proxmox dazu, zehn Typen)', () => { - // Raster 24 Spalten / 20 px (quick-260916-bwo). Die Minima sind seit - // quick-260916-dyv inhaltsgetrieben (Rechnung im Plan), die Vorgaben - // (defaultW/defaultH) bleiben das Doppelte der alten 12-Spalten-Werte. - // 3 und 9 sind ungerade — die bwo-Pruefung "jeder Wert ist gerade" entfaellt. + // Raster 48 Spalten / 20 px (quick-260929-dmx, vorher 24 Spalten). Alle + // Breitenwerte sind gegenueber dem 24er-Raster verdoppelt (gleiche + // Bildschirmbreite), Hoehen unveraendert; einzige Ausnahme Kalender-minW 8 + // (rund 250 px, gemessene kleinste benutzbare Breite; vorher 6 von 24). expect(WIDGET_CONSTRAINTS).toEqual({ - clock: { minW: 2, minH: 2, defaultW: 4, defaultH: 4 }, - search: { minW: 6, minH: 2, defaultW: 12, defaultH: 4 }, - calendar: { minW: 6, minH: 8, defaultW: 8, defaultH: 16 }, - note: { minW: 4, minH: 4, defaultW: 6, defaultH: 8 }, - calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 }, - favorites: { minW: 1, minH: 3, defaultW: 6, defaultH: 10 }, - stopwatch: { minW: 4, minH: 3, defaultW: 6, defaultH: 6 }, - 'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 12 }, - xframe: { minW: 4, minH: 4, defaultW: 12, defaultH: 12 }, - proxmox: { minW: 3, minH: 4, defaultW: 8, defaultH: 8 }, + clock: { minW: 4, minH: 2, defaultW: 8, defaultH: 4 }, + search: { minW: 12, minH: 2, defaultW: 24, defaultH: 4 }, + calendar: { minW: 8, minH: 8, defaultW: 16, defaultH: 16 }, + note: { minW: 8, minH: 4, defaultW: 12, defaultH: 8 }, + calculator: { minW: 6, minH: 10, defaultW: 12, defaultH: 10 }, + favorites: { minW: 2, minH: 3, defaultW: 12, defaultH: 10 }, + stopwatch: { minW: 8, minH: 3, defaultW: 12, defaultH: 6 }, + 'picture-frame': { minW: 8, minH: 4, defaultW: 16, defaultH: 12 }, + xframe: { minW: 8, minH: 4, defaultW: 24, defaultH: 12 }, + proxmox: { minW: 6, minH: 4, defaultW: 16, defaultH: 8 }, }); let counted = 0; diff --git a/apps/web/src/components/dashboard/widget-registry.tsx b/apps/web/src/components/dashboard/widget-registry.tsx index 2005e93..c929778 100644 --- a/apps/web/src/components/dashboard/widget-registry.tsx +++ b/apps/web/src/components/dashboard/widget-registry.tsx @@ -40,45 +40,55 @@ export const WIDGET_CONSTRAINTS: Record< { minW: number; minH: number; defaultW: number; defaultH: number } > = { // quick-260916-dyv: minW/minH = kleinste noch bedienbare Kachel je Typ im - // 24-Spalten/20-px-Raster, aus dem Innenaufbau gerechnet (Suche: Auswahl 120 + + // Raster, aus dem Innenaufbau gerechnet (Suche: Auswahl 120 + // Eingabe + Knopf; Rechner: Anzeige 40 + Speicherzeile 28 + 5 Tastenreihen 28 = // 268 px -> 10 Zeilen; Stoppuhr: kompakte Bedienleiste). defaultW/defaultH = // altes 12-Spalten-Mass x2, unveraendert. Gespeicherte minW/minH werden in // dashboard-grid.tsx aus dieser Tabelle ueberschrieben. + // quick-260929-dmx: Raster in der Breite doppelt so fein (48 Spalten am + // lg-Breakpoint). Alle Breitenwerte (minW, defaultW) sind gegenueber dem + // 24-Spalten-Raster verdoppelt, die Kacheln behalten ihre Bildschirmbreite; + // Hoehen (minH, defaultH) unveraendert. Einzige Ausnahme: Kalender-minW. // Existing widgets - clock: { minW: 2, minH: 2, defaultW: 4, defaultH: 4 }, - search: { minW: 6, minH: 2, defaultW: 12, defaultH: 4 }, + clock: { minW: 4, minH: 2, defaultW: 8, defaultH: 4 }, + search: { minW: 12, minH: 2, defaultW: 24, defaultH: 4 }, // quick-260916-htc: Monatsraster braucht Breite fuer 7 Spalten und Hoehe // fuer Nav-Zeile + Wochentagskopf + 6 Rasterzeilen + Naechste-Termine-Block. // Runde 3 (Design „Mosaik“): defaultH 16 statt 12 — mit 12 passte unter // den Monat nur ein Termin. - calendar: { minW: 6, minH: 8, defaultW: 8, defaultH: 16 }, - note: { minW: 4, minH: 4, defaultW: 6, defaultH: 8 }, + // quick-260929-dmx: auf Nutzerwunsch (29.09.) schmaler ziehbar — minW 8 von + // 48 Spalten = rund 250 px am lg-Breakpoint (gemessen: Monatsraster, + // Ueberschrift und Terminliste sind dort noch benutzbar; bei rund 185 px + // wird die Ueberschrift abgeschnitten). defaultW 16 = unveraenderte Breite. + calendar: { minW: 8, minH: 8, defaultW: 16, defaultH: 16 }, + note: { minW: 8, minH: 4, defaultW: 12, defaultH: 8 }, // Phase 8 new widgets (D-01) // minH 10, nicht 9 (260916-dyv, Browser-Messung des Orchestrators): der Rechner hat // SECHS Tastenreihen (%/CE/C/Loeschen, 1/x/x2/Wurzel/Division, 7-9, 4-6, 1-3, 0/Komma/=), // Inhalt 268 px; bei 9 Zeilen (244 px) fehlte die unterste Reihe um 25 px. - calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 }, - // Favoriten: minW 1 statt 3 (Nutzerwunsch 23.09.) — bei kurzen Linknamen - // blieb rechts viel Leerraum. Der Titel kuerzt mit Auslassungszeichen, - // in der schmalsten Stufe bleibt das Symbol als Leiste stehen. - favorites: { minW: 1, minH: 3, defaultW: 6, defaultH: 10 }, - stopwatch: { minW: 4, minH: 3, defaultW: 6, defaultH: 6 }, - // quick-260921-pi9: ein Bild braucht Flaeche — 8x12 entspricht der - // Kalender-Vorgabe (rund 530x330 px, Querformat wie ein Foto); 8x8 war im - // Browser-Rundgang mit 216 px Hoehe zu flach. 4x4 ist die kleinste Kachel, - // in der Bild und Unterschrift-Streifen noch erkennbar sind. - 'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 12 }, - // quick-260921-qd3: eine Webseite braucht Breite UND Hoehe — 12x12 = halbe - // Rasterbreite bei rund 330 px Hoehe (8 Zeilen waeren nur 216 px, zu flach - // fuer eine Seite; vgl. Bilderrahmen 8x12). 4x4 kleinste Kachel, in der ein - // Rahmen noch Sinn hat. - xframe: { minW: 4, minH: 4, defaultW: 12, defaultH: 12 }, + calculator: { minW: 6, minH: 10, defaultW: 12, defaultH: 10 }, + // Favoriten: minW 1 (im 24er-Raster, jetzt 2 von 48) statt 3 (Nutzerwunsch + // 23.09.) — bei kurzen Linknamen blieb rechts viel Leerraum. Der Titel + // kuerzt mit Auslassungszeichen, in der schmalsten Stufe bleibt das Symbol + // als Leiste stehen. + favorites: { minW: 2, minH: 3, defaultW: 12, defaultH: 10 }, + stopwatch: { minW: 8, minH: 3, defaultW: 12, defaultH: 6 }, + // quick-260921-pi9: ein Bild braucht Flaeche — 16x12 (im 48er-Raster) + // entspricht der Kalender-Vorgabe (rund 530x330 px, Querformat wie ein + // Foto); Hoehe 8 war im Browser-Rundgang mit 216 px zu flach. 8x4 ist die + // kleinste Kachel, in der Bild und Unterschrift-Streifen noch erkennbar sind. + 'picture-frame': { minW: 8, minH: 4, defaultW: 16, defaultH: 12 }, + // quick-260921-qd3: eine Webseite braucht Breite UND Hoehe — 24x12 im + // 48er-Raster = halbe Rasterbreite bei rund 330 px Hoehe (8 Zeilen waeren + // nur 216 px, zu flach fuer eine Seite; vgl. Bilderrahmen). 8x4 kleinste + // Kachel, in der ein Rahmen noch Sinn hat. + xframe: { minW: 8, minH: 4, defaultW: 24, defaultH: 12 }, // quick-260924-i8v: 4 Zeilen = 104 px reichen genau fuer Balken und // Zusammenfassung (die Serverliste blendet sich darunter per Container- - // Query aus); 8x8 = rund 456x216 px bei 1400 px Breite zeigt etwa sechs - // Serverzeilen; 3 Spalten = rund 166 px zeigen nur Punkte und Namen. - proxmox: { minW: 3, minH: 4, defaultW: 8, defaultH: 8 }, + // Query aus); 16x8 (im 48er-Raster) = rund 456x216 px bei 1400 px Breite + // zeigt etwa sechs Serverzeilen; 6 Spalten = rund 166 px zeigen nur Punkte + // und Namen. + proxmox: { minW: 6, minH: 4, defaultW: 16, defaultH: 8 }, }; /** diff --git a/apps/web/src/lib/stores/dashboard-store.test.ts b/apps/web/src/lib/stores/dashboard-store.test.ts index b339c6f..35bb11d 100644 --- a/apps/web/src/lib/stores/dashboard-store.test.ts +++ b/apps/web/src/lib/stores/dashboard-store.test.ts @@ -143,7 +143,7 @@ describe('dashboard-store — einmalige Umrechnung mit Marker (quick-260916-bwo) expect(errorSpy).toHaveBeenCalledTimes(1); }); - it('Test 6: neues Widget wird in den neuen (verdoppelten) Vorgabegroessen fuer den aktiven Reiter angelegt', async () => { + it('Test 6: neues Widget wird in den Vorgabegroessen des 48er-Rasters (quick-260929-dmx) fuer den aktiven Reiter angelegt', async () => { await useDashboardStore.getState().loadDashboard(); vi.mocked(api.addWidget).mockResolvedValue({ id: 'n1', widgetType: 'clock', config: {} }); @@ -151,7 +151,7 @@ describe('dashboard-store — einmalige Umrechnung mit Marker (quick-260916-bwo) const state = useDashboardStore.getState(); expect(api.addWidget).toHaveBeenCalledWith('dash-1', 'clock'); - expect(state.layouts.lg).toContainEqual({ i: 'n1', x: 0, y: 0, w: 4, h: 4 }); + expect(state.layouts.lg).toContainEqual({ i: 'n1', x: 0, y: 0, w: 8, h: 4 }); expect(state.isDirty).toBe(true); }); });