feat(quick-260916-bwo): Dashboard-Raster verdoppelt (24 Spalten, 20 px, 8 px Abstand), Konstanten x2, einmalige Umrechnung gespeicherter Anordnungen mit Marker __gridVersion

- dashboard-grid.tsx: COLS 24/20/12/8/2, rowHeight 20, margin 8 (containerPadding folgt), Rueckfallwerte 4
- widget-registry.tsx: alle 32 Werte in WIDGET_CONSTRAINTS verdoppelt
- grid-layout-migration.ts (neu): migrateGridLayouts/withGridVersion, Marker nur im JSON, Idempotenz (T-BWO-02)
- dashboard-store.ts: Umrechnung beim Laden, Sofort-Speichern mit Marker, withGridVersion bei jedem saveLayout
- Tests: Migration 7 (neu), Store 6 (neu), Registry +1 (Tabelle), Grid +2 (Props ueber Mock), API-Spec +2 (Durchreichung __gridVersion, timeFontSizePt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
2026-09-16 09:09:00 +02:00
parent 50f201ecc1
commit 3f5afb0f54
9 changed files with 511 additions and 25 deletions
@@ -579,6 +579,48 @@ describe('DashboardService — Anordnung und Widgets gebunden an forTenant() (26
expect(result).toEqual([]);
});
// --- Durchreichung fuer das feinere Dashboard-Raster (quick-260916-bwo) ---
// Die API kennt weder die Raster-Einheiten noch den Marker; sie reicht das
// `layouts`-JSON (`@IsObject()`) unveraendert durch. Diese beiden Tests sind
// nach heutigem Code bereits gruen — sie PINNEN die Durchreichung, damit eine
// spaetere Bereinigung des JSON die Frontend-Umrechnung nicht unbemerkt
// bricht (fehlt der Marker beim Laden, verdoppelt das Frontend erneut).
it('quick-260916-bwo Test A: der Marker __gridVersion ueberlebt saveLayout -> getLayout unveraendert', async () => {
const prisma = makeFakePrisma({});
const moduleAccessService = makeFakeModuleAccessService(new Set());
const service = new DashboardService(prisma as any, moduleAccessService as any);
const stored = { lg: [{ i: 'w1', x: 0, y: 0, w: 4, h: 4 }], __gridVersion: 2 };
await service.saveLayout('user-1', 'tenant-1', { layouts: stored } as any);
const result = await service.getLayout('user-1', 'tenant-1');
expect(result).toEqual(stored);
expect((result as Record<string, unknown>).__gridVersion).toBe(2);
});
it('quick-260916-bwo Test B: timeFontSizePt wird in die Widget-Konfiguration gemischt, null ueberschreibt, timezone bleibt', async () => {
// Die API prueft Config-Felder nicht (`@IsObject()`); die Grenzen 8..200
// liegen im Frontend (`clock-font-size.ts`), ein Fremdwert faellt dort auf
// "automatisch" zurueck (T-BWO-01).
const widget = makeWidget({ id: 'w1', userId: 'user-1', config: { timezone: 'Europe/Berlin' } });
const prisma = makeFakePrisma({ widgets: [widget] });
const moduleAccessService = makeFakeModuleAccessService(new Set());
const service = new DashboardService(prisma as any, moduleAccessService as any);
const first = await service.updateWidgetConfig('w1', 'user-1', 'tenant-1', {
config: { timeFontSizePt: 36 },
} as any);
expect(first.config).toEqual({ timezone: 'Europe/Berlin', timeFontSizePt: 36 });
const second = await service.updateWidgetConfig('w1', 'user-1', 'tenant-1', {
config: { timeFontSizePt: null },
} as any);
expect((second.config as Record<string, unknown>).timeFontSizePt).toBeNull();
expect((second.config as Record<string, unknown>).timezone).toBe('Europe/Berlin');
});
});
// --- Bindung an forTenant() (260910-krx, Aufgabe 3: Suchmaschinen) ---------