Files
tessera-ctl/.planning/phases/05-dashboard-calendar/05-UI-SPEC.md
T
schalli cd9f74531a docs(05): UI design contract for dashboard-calendar phase
Defines visual and interaction contracts for Phase 05 including
dashboard grid, 4 widget types, settings page, and calendar integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 14:09:56 +02:00

18 KiB

phase, slug, status, shadcn_initialized, preset, created
phase slug status shadcn_initialized preset created
5 dashboard-calendar draft false none 2026-06-23

Phase 05 — UI Design Contract

Visual and interaction contract for the Dashboard & Calendar phase. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Design System

Property Value
Tool none (shadcn token convention used manually)
Preset not applicable
Component library Custom components following shadcn CSS variable naming (--background, --foreground, --card, --primary, etc.)
Icon library 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)

Typography

Role Size Weight Line Height Usage
Body 14px 400 (regular) 1.5 Widget content, settings form labels, descriptions
Label 12px 500 (medium) 1.4 Widget type labels, category badges, timestamps, muted hints
Heading 18px 600 (semibold) 1.3 Settings section headings, widget catalog title
Display 28px 600 (semibold) 1.2 Clock widget time display

Clock widget exception: Digital clock uses 28px at weight 600. When widget is resized larger (4x4+), clock scales to 40px at weight 600 for visual prominence. 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 monospace at 13px.


Color

All values reference existing CSS custom properties from globals.css.

Role Token Light Value Dark Value Usage
Dominant (60%) --background oklch(0.99 0 0) oklch(0.23 0.01 260) Dashboard background, settings page background
Secondary (30%) --card / --sidebar oklch(1 0 0) / oklch(0.97 0 0) oklch(0.27 0.01 260) / oklch(0.20 0.01 260) Widget cards, settings sub-sidebar, calendar event cards
Accent (10%) --primary oklch(0.91 0.19 102) oklch(0.91 0.19 102) See reserved-for list below
Destructive --destructive oklch(0.55 0.2 27) oklch(0.55 0.2 27) Widget delete button (in edit mode only)

Accent reserved for:

  • Edit-mode toggle button (active state) — pencil icon button top-right
  • "Widget hinzufuegen" / "Add widget" button in edit mode
  • Active/selected widget border highlight during drag
  • Save confirmation indicator when leaving edit mode
  • Settings navigation active item indicator
  • Calendar event accent stripe (left border on event cards)

Additional semantic colors for calendar:

  • Calendar source color dots: Each calendar source gets a user-assigned color from a fixed palette of 8 colors. Colors are rendered as small dots (8px circle) next to event entries. Palette: oklch(0.65 0.2 27) (red), oklch(0.65 0.2 150) (green), oklch(0.65 0.2 260) (blue), oklch(0.65 0.2 310) (purple), oklch(0.75 0.15 70) (orange), oklch(0.65 0.15 200) (teal), oklch(0.65 0.15 340) (pink), oklch(0.55 0 0) (gray).

Component Inventory

Dashboard Grid

Component Description Source
DashboardGrid react-grid-layout wrapper. 12-column grid, rowHeight 40px, margin [16, 16]. Handles drag/drop and resize. New component
EditModeToggle 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.
SearchWidget 3x2 Left: provider dropdown. Center: search input. Right: search button. Opens new browser tab.
CalendarWidget 3x3 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)

  1. Default state: Dashboard shows widgets in their saved layout. No drag handles visible. Widgets are interactive (clock ticks, notes editable, search functional, calendar scrollable).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. Trigger: "Widget hinzufuegen" button (only in edit mode).
  2. Appearance: Centered modal, 480px max-width, with bg-card background, border, shadow-xl. Title: "Widget hinzufuegen" / "Add widget".
  3. 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).
  4. Selection: Click on card immediately adds widget instance to dashboard at first available grid position. Modal closes. Dashboard remains in edit mode.
  5. Close: X button top-right or click outside modal.
  6. 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"
    • Kalender / Calendar: calendar icon, "Kommende Termine" / "Upcoming events"
    • Notizen / Notes: file-text icon, "Freitext-Notizen mit Markdown" / "Free-text notes with Markdown"

Widget Delete (D-07)

  1. Trigger: Delete X button on widget card (visible only in edit mode).
  2. Behavior: Immediate removal from grid (no confirmation dialog -- user can re-add). Layout reflows.
  3. Persistence: Deletion is persisted when user exits edit mode (saves layout).

Settings Navigation (D-19, D-20)

  1. Entry point: User clicks avatar in header -> dropdown menu -> "Einstellungen" / "Settings" link. Navigates to /settings.
  2. Layout: Sub-sidebar (220px, bg-sidebar background, left border border-sidebar-border) with settings categories. Content area fills remaining width.
  3. Active indicator: Active settings category has bg-sidebar-accent text-sidebar-accent-foreground background, matching main sidebar active state pattern.
  4. 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)

  1. Source list: Each source shows: color dot, name, type badge (CalDAV/Exchange/ICS), toggle switch for widget visibility, edit/delete actions.
  2. 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.
  3. Validation: URL field validates URL format. Name is required. Type is required.
  4. 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)

  1. 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).
  2. Behavior: Enter key or button click opens {provider_url}?q={query} in new browser tab (window.open).
  3. Default providers: Google, Bing, DuckDuckGo. Selected provider persists per widget instance.

Notes Widget Interaction (D-16, D-17, D-18)

  1. Layout: Top: editable title (16px semibold, contenteditable or input). Below: compact toolbar (icon buttons: B, I, U, List, Checkbox, Link, Code). Below: editor area (fills remaining widget height, scrollable).
  2. 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.
  3. Markdown rendering: Live WYSIWYG editing (not split-view). Toolbar buttons apply formatting inline.

Responsive Behavior (D-21, D-22)

  1. Desktop (>= 768px): 12-column grid. Grid scales proportionally with container width. --current-sidebar-width CSS variable adjusts available space.
  2. 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?"

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?"

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 + "Loeschen"/"Delete" button (destructive color) + "Abbrechen"/"Cancel" button
Delete custom search provider Confirmation dialog: same pattern as calendar source

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".
Keyboard navigation Tab order: edit toggle -> widgets (left-to-right, top-to-bottom) -> add widget button (in edit mode). Settings: sub-sidebar items -> form fields.

Checker Sign-Off

  • Dimension 1 Copywriting: PASS
  • Dimension 2 Visuals: PASS
  • Dimension 3 Color: PASS
  • Dimension 4 Typography: PASS
  • Dimension 5 Spacing: PASS
  • Dimension 6 Registry Safety: PASS

Approval: pending