Reduce typography to 4 sizes (12/14/18/28) and 2 weights (400/600).
Clock scaling declared as responsive exception, not standalone size.
Destructive CTAs now include noun (Quelle loeschen / Delete source).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline SVG (established pattern across all phases)
Font
Inter, system-ui, -apple-system, sans-serif
Note: Project uses shadcn-compatible CSS variable tokens in OKLCH color space (established Phase 01) but does not use components.json or the shadcn CLI. All components are hand-written. Phase 05 continues this pattern for consistency.
Spacing Scale
Declared values (must be multiples of 4):
Token
Value
Usage
xs
4px
Icon gaps, inline padding, grid gap between widget cells
sm
8px
Compact element spacing, widget internal padding minimum
md
16px
Default element spacing, widget content padding
lg
24px
Section padding, settings form group spacing
xl
32px
Layout gaps, dashboard grid outer padding
2xl
48px
Empty state vertical spacing
3xl
64px
Page-level spacing (not used in this phase)
Exceptions:
Grid cell size: 40px per grid unit (react-grid-layout rowHeight). Derived from 8-point scale (5 x 8px).
Grid margin/padding: 16px (consistent with md token)
Touch target minimum: 44px for drag handles and widget action buttons (accessibility)
Widget content, settings form labels, descriptions, Notes widget title (semibold variant)
Heading
18px
600 (semibold)
1.3
Settings section headings, widget catalog title
Display
28px
600 (semibold)
1.2
Clock widget time display
Exactly 2 weights used: 400 (regular) for body/label text, 600 (semibold) for headings, display, and inline emphasis (bold, Notes title). Labels are differentiated from body by smaller size (12px) and muted-foreground color — no third weight needed.
Clock widget responsive exception: Digital clock uses Display size (28px) at weight 600. When widget is resized larger (4x4+), clock scales proportionally via CSS clamp() up to approximately 40px. This is NOT a fifth type size — it is the Display size with responsive scaling applied via font-size: clamp(28px, 4vw, 40px). Analog clock face uses SVG, not text.
Markdown editor (Notes widget): Inherits Body typography (14px/400/1.5). Bold uses weight 600. Code blocks use Label size (12px) in monospace. Notes widget title uses Body size (14px) at weight 600 (semibold) — differentiated from content by weight, not a separate size.
Color
All values reference existing CSS custom properties from globals.css.
Pencil icon button, top-right of dashboard area. Toggles isEditing state. Active state: primary background.
New component (D-01)
WidgetCard
Wrapper for all widgets. Rounded card with border, shadow-sm. In edit mode: shows drag handle (top), resize handle (bottom-right), delete button (top-right).
New component
AddWidgetButton
Visible only in edit mode. Opens widget catalog modal. Uses primary color.
New component (D-01)
WidgetCatalog
Modal dialog showing available widget types as selectable cards (4 types: Clock, Search, Calendar, Notes). Each card shows icon + name + brief description. Selecting adds widget to grid at next available position.
New component
Widgets
Component
Min Size (cols x rows)
Description
ClockWidget
2x2
Digital clock (default). Shows time in configured timezone. Optional date below. Configurable style (analog/digital) in settings.
Shows upcoming events list from selected calendar sources. Read-only. Each event: time, title, source color dot.
NotesWidget
2x3
Markdown editor with compact toolbar (Bold, Italic, Underline, List, Checkbox, Link, Code). Autosave with 1s debounce. Editable title above content.
Settings Page
Component
Description
SettingsLayout
Layout with sub-sidebar (left, 220px) and content area (right). Accessible via user avatar menu in header.
SettingsSubSidebar
Left navigation within settings. Phase 05 categories: "Dashboard" (top-level), with sub-items: "Widgets", "Kalender" / "Calendar".
WidgetSettingsPanel
Lists all placed widget instances. Each expandable to show widget-specific config (clock: timezone + date toggle + style; search: custom providers; notes: title).
CalendarSettingsPanel
Manages calendar sources. List of configured sources + "Quelle hinzufuegen" / "Add source" button. Source form: name, type (CalDAV/Exchange/ICS), URL, credentials (optional). Toggle per source for visibility in widget.
Interaction Contracts
Edit Mode Flow (D-01, D-02, D-03)
Default state: Dashboard shows widgets in their saved layout. No drag handles visible. Widgets are interactive (clock ticks, notes editable, search functional, calendar scrollable).
Enter edit mode: User clicks pencil icon (top-right, positioned at right: 16px, top: 16px relative to dashboard content area). Icon transitions to checkmark icon. Background gets subtle overlay oklch(0 0 0 / 0.02) in light, oklch(1 1 0 / 0.02) in dark.
In edit mode: Grid lines become visible (dashed border, --border color at 30% opacity). Each widget shows: drag handle bar (top, 6px height, --muted color), delete X button (top-right corner, --destructive on hover), resize handle (bottom-right corner, diagonal grip lines). "Widget hinzufuegen" floating button appears bottom-right.
Drag/resize: Widgets snap to grid. Other widgets reflow automatically (react-grid-layout default behavior). Dragged widget gets ring-2 ring-primary highlight and slight scale scale(1.02) with shadow-lg.
Exit edit mode: User clicks checkmark icon. Layout saves via API call. Checkmark transitions back to pencil icon. Edit mode UI elements (handles, grid lines, add button) disappear with 150ms fade.
Empty dashboard (D-02): Shows centered empty state: grid icon (48x48, muted), heading "Keine Widgets aktiv" / "No active widgets", body text "Klicken Sie auf Bearbeiten, um Widgets hinzuzufuegen" / "Click edit to add widgets", and the edit-mode pencil button is visible.
Widget Catalog Modal
Trigger: "Widget hinzufuegen" button (only in edit mode).
Content: 2x2 grid of selectable widget type cards. Each card: 56px icon area (muted background), widget name (14px semibold), one-line description (12px muted-foreground).
Selection: Click on card immediately adds widget instance to dashboard at first available grid position. Modal closes. Dashboard remains in edit mode.
Close: X button top-right or click outside modal.
Widget types shown:
Uhr / Clock: clock icon, "Zeigt die aktuelle Uhrzeit an" / "Shows the current time"
Suchleiste / Search: search icon, "Schnellsuche im Web" / "Quick web search"
Notizen / Notes: file-text icon, "Freitext-Notizen mit Markdown" / "Free-text notes with Markdown"
Widget Delete (D-07)
Trigger: Delete X button on widget card (visible only in edit mode).
Behavior: Immediate removal from grid (no confirmation dialog -- user can re-add). Layout reflows.
Persistence: Deletion is persisted when user exits edit mode (saves layout).
Settings Navigation (D-19, D-20)
Entry point: User clicks avatar in header -> dropdown menu -> "Einstellungen" / "Settings" link. Navigates to /settings.
Layout: Sub-sidebar (220px, bg-sidebar background, left border border-sidebar-border) with settings categories. Content area fills remaining width.
Active indicator: Active settings category has bg-sidebar-accent text-sidebar-accent-foreground background, matching main sidebar active state pattern.
Back navigation: Settings page header includes "Zurueck zum Dashboard" / "Back to Dashboard" link with left arrow icon.
Calendar Source Management (D-08, D-09, D-11)
Source list: Each source shows: color dot, name, type badge (CalDAV/Exchange/ICS), toggle switch for widget visibility, edit/delete actions.
Add source form: Appears below list or in expandable section. Fields: Name (text), Type (select: CalDAV, Exchange, ICS), URL (text), Username (text, optional for CalDAV/Exchange), Password (password, optional for CalDAV/Exchange). Save button.
Validation: URL field validates URL format. Name is required. Type is required.
Connection test: After save, system attempts to fetch events. Success: green checkmark next to source. Failure: orange warning icon with tooltip showing error.
Search Widget Interaction (D-14, D-15)
Layout: Horizontal. Left: dropdown (120px width, shows current provider name). Center: text input (flex-1). Right: search button (icon + "Suchen" / "Search" on wider sizes, icon-only on narrow).
Behavior: Enter key or button click opens {provider_url}?q={query} in new browser tab (window.open).
Layout: Top: editable title (14px semibold, contenteditable or input — Body size at weight 600). Below: compact toolbar (icon buttons: B, I, U, List, Checkbox, Link, Code). Below: editor area (fills remaining widget height, scrollable).
Autosave: Content saves to API 1000ms after last keystroke. No save indicator needed (saves silently). On API error: show small red dot in widget top-right corner.
Desktop (>= 768px): 12-column grid. Grid scales proportionally with container width. --current-sidebar-width CSS variable adjusts available space.
Mobile (< 768px): Widgets stack vertically in a single column. Each widget takes full width. Order matches grid layout top-to-bottom, left-to-right. Edit mode is available but drag/resize is disabled -- only add/delete widgets.
Copywriting Contract
German (Primary)
Element
Copy
Primary CTA (edit mode)
"Widget hinzufuegen"
Edit mode toggle (enter)
aria-label: "Dashboard bearbeiten"
Edit mode toggle (exit/save)
aria-label: "Aenderungen speichern"
Empty state heading
"Keine Widgets aktiv"
Empty state body
"Klicken Sie auf Bearbeiten, um Widgets hinzuzufuegen."
Widget catalog title
"Widget hinzufuegen"
Widget delete tooltip
"Widget entfernen"
Settings link (header menu)
"Einstellungen"
Settings back link
"Zurueck zum Dashboard"
Settings category: widgets
"Widgets"
Settings category: calendar
"Kalender"
Calendar add source CTA
"Quelle hinzufuegen"
Calendar source empty state
"Keine Kalenderquellen eingerichtet. Fuegen Sie eine Quelle hinzu, um Termine anzuzeigen."
Calendar widget empty (no sources)
"Keine Kalenderquellen konfiguriert"
Calendar widget empty (no events)
"Keine anstehenden Termine"
Calendar connection success
"Verbindung erfolgreich"
Calendar connection error
"Verbindung fehlgeschlagen. Bitte ueberpruefen Sie die URL und Zugangsdaten."
Search widget placeholder
"Suchen..."
Notes widget default title
"Notiz"
Notes autosave error indicator
tooltip: "Speichern fehlgeschlagen"
Error state (layout load)
"Dashboard konnte nicht geladen werden. Bitte laden Sie die Seite neu."
Error state (widget save)
"Aenderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut."
Clock widget date format
"dd. MMMM yyyy" (e.g. "23. Juni 2026")
Settings source delete confirm
"Moechten Sie diese Kalenderquelle wirklich loeschen?"
Settings source delete CTA
"Quelle loeschen"
Settings provider delete confirm
"Moechten Sie diesen Suchanbieter wirklich loeschen?"
Settings provider delete CTA
"Anbieter loeschen"
Destructive cancel button
"Abbrechen"
English
Element
Copy
Primary CTA (edit mode)
"Add widget"
Edit mode toggle (enter)
aria-label: "Edit dashboard"
Edit mode toggle (exit/save)
aria-label: "Save changes"
Empty state heading
"No active widgets"
Empty state body
"Click edit to add widgets to your dashboard."
Widget catalog title
"Add widget"
Widget delete tooltip
"Remove widget"
Settings link (header menu)
"Settings"
Settings back link
"Back to Dashboard"
Settings category: widgets
"Widgets"
Settings category: calendar
"Calendar"
Calendar add source CTA
"Add source"
Calendar source empty state
"No calendar sources configured. Add a source to display events."
Calendar widget empty (no sources)
"No calendar sources configured"
Calendar widget empty (no events)
"No upcoming events"
Calendar connection success
"Connection successful"
Calendar connection error
"Connection failed. Please check the URL and credentials."
Search widget placeholder
"Search..."
Notes widget default title
"Note"
Notes autosave error indicator
tooltip: "Save failed"
Error state (layout load)
"Could not load dashboard. Please reload the page."
Error state (widget save)
"Could not save changes. Please try again."
Clock widget date format
"MMMM dd, yyyy" (e.g. "June 23, 2026")
Settings source delete confirm
"Are you sure you want to delete this calendar source?"
Settings source delete CTA
"Delete source"
Settings provider delete confirm
"Are you sure you want to delete this search provider?"
Settings provider delete CTA
"Delete provider"
Destructive cancel button
"Cancel"
Destructive Actions
Action
Confirmation Approach
Delete widget (edit mode)
No confirmation (instant, re-addable, not persisted until save)
Delete calendar source
Confirmation dialog: heading + body text + "Quelle loeschen"/"Delete source" button (destructive color) + "Abbrechen"/"Cancel" button
Delete custom search provider
Confirmation dialog: heading + body text + "Anbieter loeschen"/"Delete provider" button (destructive color) + "Abbrechen"/"Cancel" button
Registry Safety
Registry
Blocks Used
Safety Gate
shadcn official
not initialized — no CLI blocks used
not applicable
Third-party
none
not applicable
Note: No component registry is used. All components are hand-written following shadcn CSS variable conventions. react-grid-layout is installed as an npm package (not a registry block). Markdown editor library selection is at executor discretion (recommended: lightweight option like @tiptap/starter-kit or similar, evaluated during planning).
Widget Size Constraints (D-06)
Widget Type
Min (cols x rows)
Default (cols x rows)
Max
Clock
2 x 2
2 x 2
4 x 4
Search
3 x 2
6 x 2
12 x 2
Calendar
3 x 3
4 x 6
12 x 12
Notes
2 x 3
3 x 4
12 x 12
State Management
Store
Purpose
Persistence
dashboard-store (Zustand)
Client-side edit mode state, current layout, widget instances, dirty flag
Memory only (layout fetched from API on mount)
API: GET /dashboard/layout
Fetch user's saved layout + widget configs
PostgreSQL via Prisma
API: PUT /dashboard/layout
Save layout + widget configs
PostgreSQL via Prisma
API: GET /calendar/sources
Fetch user's calendar sources
PostgreSQL via Prisma
API: POST /calendar/sources
Add calendar source
PostgreSQL via Prisma
API: DELETE /calendar/sources/:id
Delete calendar source
PostgreSQL via Prisma
API: GET /calendar/events
Fetch events from all active sources
Proxied from external calendar servers
Accessibility
Concern
Implementation
Edit mode toggle
aria-label updates based on state. aria-pressed attribute.
Drag and drop
react-grid-layout provides keyboard support via arrow keys when focused. Each widget has role="article" and aria-label with widget type + instance name.
Widget catalog modal
role="dialog", aria-modal="true", focus trap, Escape to close. Widget type cards are buttons with aria-label.
Color contrast
All text meets WCAG 2.1 AA (4.5:1 for body, 3:1 for large text). Calendar color dots are decorative only -- source name is always shown as text.
Settings navigation
Sub-sidebar uses role="navigation" with aria-label="Einstellungen" / "Settings". Active item uses aria-current="page".