From cd9f74531ab38113ffd82ee49f9408ca574efc43 Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 23 Jun 2026 14:09:56 +0200 Subject: [PATCH] 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 --- .../05-dashboard-calendar/05-UI-SPEC.md | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 .planning/phases/05-dashboard-calendar/05-UI-SPEC.md diff --git a/.planning/phases/05-dashboard-calendar/05-UI-SPEC.md b/.planning/phases/05-dashboard-calendar/05-UI-SPEC.md new file mode 100644 index 0000000..21b8153 --- /dev/null +++ b/.planning/phases/05-dashboard-calendar/05-UI-SPEC.md @@ -0,0 +1,313 @@ +--- +phase: 5 +slug: dashboard-calendar +status: draft +shadcn_initialized: false +preset: none +created: 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