diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d048aa..d0566d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,16 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
## Unveröffentlicht
+## 1.5.2 – 2026-09-28
+
+### Neu
+
+- Dashboard: Den Titel eines Widgets können Sie jetzt ausblenden. Im Bearbeitungsmodus sitzt dafür oben in der Mitte jedes Widgets mit Titel ein kleines „T“; ein Klick blendet den Titel aus, ein zweiter wieder ein. Die Einstellung gilt je Widget und bleibt gespeichert. Im Bearbeitungsmodus sehen Sie den Titel weiterhin, damit Sie ihn ändern können; Knöpfe wie der Stift der Notiz bleiben auch ohne Titel oben rechts erreichbar.
+
+### Geändert
+
+- Seitenleiste: Die Kategorien (etwa „Fuhrpark“ oder „Infrastruktur“) sind etwas größer beschriftet, die Module darunter etwas kleiner – so ist die Gliederung auf einen Blick erkennbar.
+
## 1.5.1 – 2026-09-28
### Geändert
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index 29f1eef..e6746ae 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -474,6 +474,26 @@ input[type="checkbox"], input[type="radio"] {
}
}
+/* Titel ausgeblendet (quick-260928-wtt, `hideTitle` in der Widget-
+ Konfiguration, nur in der Ansicht): die Titelzeile entfaellt. Traegt sie
+ Aktionen (Notiz-Stift, XFrame „In neuem Tab“), bleiben nur diese als
+ kleine Schwebeleiste oben rechts. */
+[data-hide-title] .widget-head {
+ display: none;
+}
+[data-hide-title] .widget-head:has(.widget-action) {
+ display: flex;
+ position: absolute;
+ top: 0.25rem;
+ right: 0.25rem;
+ z-index: 5;
+ min-height: 0;
+ padding: 0;
+}
+[data-hide-title] .widget-head:has(.widget-action) > :not(.widget-action) {
+ display: none;
+}
+
/* Beim Laden blenden die Kacheln gestaffelt ein (je 80 ms, --enter-delay). */
@media (prefers-reduced-motion: no-preference) {
.widget-card {
diff --git a/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx b/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx
index 88a3b86..ac37b62 100644
--- a/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx
+++ b/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from '@testing-library/react';
+import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
// quick-260916-iex: Link-Widget entfernt — unbekannte Widget-Typen (z. B.
@@ -14,6 +14,12 @@ vi.mock('next-intl', () => ({
: key,
}));
+const setWidgetConfig = vi.fn();
+vi.mock('@/lib/stores/dashboard-store', () => ({
+ useDashboardStore: (selector: (state: { setWidgetConfig: typeof setWidgetConfig }) => unknown) =>
+ selector({ setWidgetConfig }),
+}));
+
import { WidgetWrapper } from './widget-wrapper';
describe('WidgetWrapper', () => {
@@ -54,4 +60,53 @@ describe('WidgetWrapper', () => {
),
).toBeNull();
});
+
+ // quick-260928-wtt: Titel je Widget ein-/ausblenden
+ it('Bearbeitungsmodus: Schalter blendet den Titel aus und speichert hideTitle', () => {
+ setWidgetConfig.mockClear();
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole('button', { name: 'hideTitle' }));
+ expect(setWidgetConfig).toHaveBeenCalledWith('w-uhr', { hideTitle: true });
+ // im Bearbeitungsmodus bleibt der Titel sichtbar
+ expect(screen.getByRole('article')).not.toHaveAttribute('data-hide-title');
+ });
+
+ it('ausgeblendeter Titel: Schalter blendet wieder ein, Ansicht markiert die Karte', () => {
+ setWidgetConfig.mockClear();
+ const { rerender } = render(
+ ,
+ );
+ fireEvent.click(screen.getByRole('button', { name: 'showTitle' }));
+ expect(setWidgetConfig).toHaveBeenCalledWith('w-uhr', { hideTitle: false });
+
+ rerender(
+ ,
+ );
+ expect(screen.getByRole('article')).toHaveAttribute('data-hide-title');
+ });
+
+ it('Widgets ohne Titelzeile (Suche) bekommen keinen Schalter', () => {
+ render(
+ ,
+ );
+ expect(screen.queryByRole('button', { name: 'hideTitle' })).toBeNull();
+ });
});
diff --git a/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx b/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
index 073aea1..1265181 100644
--- a/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
+++ b/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
@@ -2,6 +2,7 @@
import { useTranslations } from 'next-intl';
import type { CSSProperties } from 'react';
+import { useDashboardStore } from '@/lib/stores/dashboard-store';
import { WIDGET_REGISTRY, type WidgetType } from '../widget-registry';
import { WidgetIcon } from './widget-icon';
@@ -14,6 +15,14 @@ import { WidgetIcon } from './widget-icon';
*/
const FRAME_HEADER_TYPES = new Set(['calendar', 'clock', 'calculator', 'stopwatch']);
+/**
+ * Kacheltypen mit Titelzeile — nur diese bekommen im Bearbeitungsmodus den
+ * Schalter „Titel ausblenden“ (quick-260928-wtt). Die Wahl steht als
+ * `hideTitle: true` in der Widget-Konfiguration und gilt nur in der Ansicht;
+ * im Bearbeitungsmodus bleibt der Titel sichtbar (und editierbar).
+ */
+const TITLE_TYPES = new Set([...FRAME_HEADER_TYPES, 'note', 'favorites', 'proxmox', 'xframe']);
+
interface WidgetWrapperProps {
widget: {
id: string;
@@ -42,6 +51,9 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }:
const t = useTranslations('widgets');
const definition = WIDGET_REGISTRY[widget.widgetType as WidgetType];
const WidgetComponent = definition?.component;
+ const setWidgetConfig = useDashboardStore((state) => state.setWidgetConfig);
+ const hasTitle = TITLE_TYPES.has(widget.widgetType);
+ const titleHidden = widget.config?.hideTitle === true;
// Design „Mosaik“: in der Ansicht eine ruhige Fluent-Karte (hell Schatten,
// dunkel Haarlinie), die Akzent-Umrandung nur im Bearbeitungsmodus.
@@ -54,6 +66,7 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }:
className={cardClassName}
style={{ '--enter-delay': `${Math.min(enterIndex, 8) * 80}ms` } as CSSProperties}
data-widget-id={widget.id}
+ data-hide-title={!isEditMode && hasTitle && titleHidden ? '' : undefined}
aria-label={definition ? t(definition.nameKey) : widget.widgetType}
>
{/* Edit mode: Overlay-Kopfleiste (Griff-Hinweis) mit Loesch-Knopf rechts.
@@ -81,6 +94,39 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }:
+ {hasTitle && (
+
+ )}
@@ -219,7 +225,7 @@ export function Sidebar() {
const active = isActive(href) || isActive(`/modules/${mod.slug}`);
return (
-
+
{active && }
{mod.name}
diff --git a/apps/web/src/lib/stores/dashboard-store.ts b/apps/web/src/lib/stores/dashboard-store.ts
index 1a49cb0..02ffcd9 100644
--- a/apps/web/src/lib/stores/dashboard-store.ts
+++ b/apps/web/src/lib/stores/dashboard-store.ts
@@ -26,6 +26,8 @@ interface DashboardState {
updateLayouts: (layouts: Record) => void;
addWidget: (widgetType: WidgetType) => Promise;
removeWidget: (id: string) => Promise;
+ /** Teil-Konfiguration eines Widgets setzen — sofort sichtbar, bei Fehler zurueck. */
+ setWidgetConfig: (id: string, partial: Record) => Promise;
loadDashboard: () => Promise;
saveLayout: () => Promise;
selectDashboard: (id: string) => Promise;
@@ -159,6 +161,22 @@ export const useDashboardStore = create()((set, get) => ({
}
},
+ setWidgetConfig: async (id: string, partial: Record) => {
+ const before = get().widgets.find((w) => w.id === id)?.config;
+ if (!before) return;
+ const patch = (config: Record) =>
+ set((state) => ({
+ widgets: state.widgets.map((w) => (w.id === id ? { ...w, config } : w)),
+ }));
+ patch({ ...before, ...partial });
+ try {
+ await api.updateWidgetConfig(id, partial);
+ } catch (err) {
+ console.error('Failed to update widget config:', err);
+ patch(before);
+ }
+ },
+
loadDashboard: async () => {
set({ isLoading: true, error: null });
try {
diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json
index e4a8adb..46c4fbb 100644
--- a/apps/web/src/messages/de.json
+++ b/apps/web/src/messages/de.json
@@ -236,6 +236,8 @@
"catalogTitle": "Widget hinzufügen",
"catalogClose": "Dialog schließen",
"deleteTooltip": "Widget entfernen",
+ "hideTitle": "Titel ausblenden",
+ "showTitle": "Titel einblenden",
"dragHint": "Ziehen Sie die Kachel, um sie zu verschieben",
"editMode": "Dashboard bearbeiten",
"saveChanges": "Änderungen speichern",
diff --git a/apps/web/src/messages/en.json b/apps/web/src/messages/en.json
index f4dad94..e4bda8a 100644
--- a/apps/web/src/messages/en.json
+++ b/apps/web/src/messages/en.json
@@ -236,6 +236,8 @@
"catalogTitle": "Add widget",
"catalogClose": "Close dialog",
"deleteTooltip": "Remove widget",
+ "hideTitle": "Hide title",
+ "showTitle": "Show title",
"dragHint": "Drag the tile to move it",
"editMode": "Edit dashboard",
"saveChanges": "Save changes",