Files
tessera-ctl/.planning/phases/04-marketplace-portal-navigation/04-UI-SPEC.md
T
2026-06-22 13:53:44 +02:00

23 KiB

phase, slug, status, shadcn_initialized, preset, created
phase slug status shadcn_initialized preset created
4 marketplace-portal-navigation draft false none 2026-06-22

Phase 4 — UI Design Contract

Visual and interaction contract for Marketplace & Portal Navigation. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Design System

Property Value
Tool none (hand-rolled components with shadcn-compatible CSS variable naming)
Preset not applicable
Component library none (custom components, Radix-compatible token structure)
Icon library Inline SVG (project convention from Phase 1-3, no external icon library)
Font Inter, system-ui, -apple-system, sans-serif (declared as --font-sans in globals.css)

Note: The project uses shadcn-compatible CSS custom property naming (--primary, --card, --muted, etc.) and Tailwind v4 @theme inline mapping, but does not have components.json. All UI components are hand-rolled. This phase continues that pattern.


Spacing Scale

Declared values (must be multiples of 4):

Token Value Usage
xs 4px Icon gaps, inline badge padding, chip internal spacing
sm 8px Compact element spacing, card internal gaps, filter chip gaps
md 16px Default element spacing, card padding, search bar padding
lg 24px Section padding, space between marketplace header and grid
xl 32px Layout gaps, space between major page sections
2xl 48px Empty state vertical padding, page top/bottom padding
3xl 64px Not used in this phase

Exceptions: Sidebar search input height at 36px (not a spacing token — component-specific dimension for comfortable text input within narrow 240px sidebar). Marketplace card minimum height at 180px for visual consistency in the grid.


Typography

Role Size Weight Line Height Usage in Phase 4
Body 14px 400 (regular) 1.5 Card descriptions, sidebar module names, filter labels
Label 12px 600 (semibold) 1.4 Category badges, status badges, sidebar section headers, version text
Heading 20px 700 (bold) 1.2 Page titles ("Marktplatz"), section headings
Display 28px 700 (bold) 1.2 Not used in this phase (reserved for Dashboard phase)

Font weights used: 400 (regular) and 600 (semibold). 700 (bold) only for page headings per existing codebase convention (text-2xl font-bold in admin pages).


Color

All values in OKLCH (project convention from Phase 1, defined in globals.css).

Role Light Mode Dark Mode Usage
Dominant (60%) oklch(0.99 0 0) — near-white oklch(0.23 0.01 260) — dark gray-blue Page background, marketplace grid background
Secondary (30%) oklch(1 0 0) / oklch(0.96 0 0) — white/light gray oklch(0.27 0.01 260) / oklch(0.30 0.01 260) — dark card/muted Marketplace cards (bg-card), sidebar (bg-sidebar), filter bar background, tenant selector dropdown
Accent (10%) oklch(0.91 0.19 102) — Tessera yellow oklch(0.91 0.19 102) — same yellow See reserved-for list below
Destructive oklch(0.55 0.2 27) — red oklch(0.55 0.2 27) — same red Deactivation confirmation dialog only
Success oklch(0.55 0.16 145) — green oklch(0.55 0.16 145) — green "Aktiviert" status badge, activation success toast

Accent reserved for:

  1. "Aktivieren" / "Modul aktivieren" primary CTA button background
  2. Active module card border highlight on hover (hover:border-primary/30 — existing pattern)
  3. Active sidebar item background accent (bg-sidebar-accent)
  4. Marketplace search input focus ring (ring-ring maps to primary)
  5. Tenant context selector active state indicator
  6. Toggle switch active state (existing pattern from admin/modules)

Accent explicitly NOT used for:

  • Category badges (use bg-muted with text-muted-foreground)
  • Status text (use semantic green/red)
  • Card backgrounds (use bg-card)

Component Inventory

New Components (Phase 4)

Component Location Purpose
MarketplaceCard apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx Module card for marketplace grid — extends ModuleCard pattern with activation toggle and status badge
MarketplaceSearch apps/web/src/app/(portal)/marketplace/components/MarketplaceSearch.tsx Search input with live filter, debounced 300ms
CategoryFilter apps/web/src/app/(portal)/marketplace/components/CategoryFilter.tsx Horizontal chip/pill row for category filtering
StatusFilter apps/web/src/app/(portal)/marketplace/components/StatusFilter.tsx Tab bar: "Alle" / "Aktiviert" / "Verfuegbar" for filtering by activation status
TenantContextSelector apps/web/src/app/(portal)/marketplace/components/TenantContextSelector.tsx Dropdown for Super-Admin to switch tenant context in marketplace
ModuleDetailView apps/web/src/app/(portal)/marketplace/[slug]/page.tsx Compact detail page for a single module
ActivationDialog apps/web/src/app/(portal)/marketplace/components/ActivationDialog.tsx Confirmation dialog for deactivation only (activation is immediate with undo toast)
SidebarSearch apps/web/src/components/layout/sidebar-search.tsx Inline search/filter field within sidebar categories section

Reused Components (from Phase 1-3)

Component Source Reuse
ModuleCard apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx Pattern reference for MarketplaceCard — same card structure, extended with activation controls
Sidebar apps/web/src/components/layout/sidebar.tsx Extended with SidebarSearch and individual module links per category
Header apps/web/src/components/layout/header.tsx Unchanged — breadcrumb updates for marketplace route
AppShell apps/web/src/components/layout/app-shell.tsx Unchanged — marketplace pages render within existing shell

Layout Specifications

Marketplace Page (/marketplace)

+----------------------------------------------------------+
| Header (sticky, 60px)                                     |
+--------+-------------------------------------------------+
| Sidebar | Marketplace Page                                |
| 240px   |                                                 |
|         | [Tenant Selector - Super-Admin only]            |
|         |                                                 |
|         | Marktplatz (h1, 20px bold)                      |
|         | Entdecken Sie verfuegbare Module (14px muted)   |
|         |                                                 |
|         | [Search input, full width]                      |
|         | [StatusTabs: Alle | Aktiviert | Verfuegbar]    |
|         | [CategoryChips: Alle | Domain-Tools | Utils...] |
|         |                                                 |
|         | +--------+ +--------+ +--------+               |
|         | | Card 1 | | Card 2 | | Card 3 |               |
|         | +--------+ +--------+ +--------+               |
|         | +--------+ +--------+ +--------+               |
|         | | Card 4 | | Card 5 | | Card 6 |               |
|         | +--------+ +--------+ +--------+               |
+--------+-------------------------------------------------+

Grid responsive breakpoints:

  • Mobile (<768px): 1 column, sidebar hidden
  • Tablet (768px-1023px): 2 columns, sidebar collapsed or hidden
  • Desktop (1024px-1279px): 3 columns, sidebar expanded
  • Wide (1280px+): 4 columns, sidebar expanded

Grid gap: 16px (gap-4)

MarketplaceCard Anatomy

+------------------------------------------+
| [Icon 24x24]  Module Name                |
|               Category Badge  Status Badge|
|                                          |
| Localized description text, max 2 lines  |
| with line-clamp-2 truncation.            |
|                                          |
| v1.0.0            [Aktivieren] (button)  |
+------------------------------------------+
  • Card padding: 20px (p-5)
  • Card border: border border-border (1px)
  • Card background: bg-card
  • Card border-radius: 8px (rounded-lg)
  • Card hover: hover:shadow-md hover:border-primary/30 (existing pattern)
  • Icon container: rounded-md bg-muted p-2.5 (40px total, existing pattern)
  • Status badge (activated): bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded-full px-2 py-0.5 text-xs
  • Status badge (available): bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs
  • Category badge: bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs
  • Activation button (activate): bg-primary text-primary-foreground rounded-md px-3 py-1.5 text-sm font-medium
  • Activation button (deactivate): border border-border text-foreground hover:bg-muted rounded-md px-3 py-1.5 text-sm font-medium

Module Detail Page (/marketplace/[slug])

Compact detail view (appropriate for v1 module count). Single-column layout within main content area.

+------------------------------------------+
| < Zurueck zum Marktplatz                 |
|                                          |
| [Icon 48x48]                             |
| Module Name (h1, 20px bold)              |
| Category Badge   v1.0.0                  |
|                                          |
| Full localized description text.         |
| No line clamping. Renders full text.     |
|                                          |
| Status: Aktiviert / Nicht aktiviert      |
| [Aktivieren / Deaktivieren] (button)     |
+------------------------------------------+
  • Back link: text-sm text-muted-foreground hover:text-foreground with left chevron icon
  • Icon container: rounded-lg bg-muted p-4 (56px total) — larger than card icon
  • Max content width: max-w-2xl (672px)

Sidebar Enhancement

Sidebar (240px expanded):
+----------------------------------+
| [Dashboard icon]  Dashboard      |
| [Cart icon]       Marktplatz     |
+----------------------------------+
| [Search input _______________]   |  <-- NEW: SidebarSearch
+----------------------------------+
| KATEGORIEN            [chevron]  |
|   [Folder] Domain Tools    (1)   |
|      - Domaincheck               |
|   [Folder] Utilities       (2)   |
|      - Tool A                    |
|      - Tool B                    |
+----------------------------------+
| VERWALTUNG                       |
|   [Users icon]    Benutzer       |
|   [Home icon]     Mandanten      |  (Super-Admin only)
|   [Box icon]      Module         |
|   [Layers icon]   LDAP           |
+----------------------------------+
| [Collapse]                       |
| [User Info]                      |
+----------------------------------+

Sidebar (64px collapsed):
+--------+
| [Dash]  |
| [Cart]  |
+--------+
| [Usr]   |
| [Home]  |  (Super-Admin only)
| [Box]   |
| [Lyrs]  |
+--------+
| [<]     |
| [Avtr]  |
+--------+

Sidebar Search (SidebarSearch):

  • Position: Between main nav (Dashboard/Marketplace) and Categories section
  • Height: 36px
  • Padding: 8px horizontal within sidebar's 12px padding (px-3 within p-3 nav)
  • Placeholder text: "Module suchen..." (14px, muted-foreground)
  • Border: border border-border rounded-md
  • Focus: focus:ring-2 focus:ring-ring focus:border-transparent
  • Behavior: Filters visible module names and category names in real-time. Categories with no matching modules are hidden.
  • Collapsed sidebar: Search field hidden (not enough space at 64px)

Sidebar collapsed mode decision: Show main navigation icons (Dashboard, Marketplace) and admin icons. Do NOT show individual module icons -- category/module list is hidden when collapsed. This keeps the collapsed sidebar clean and avoids icon overload.

Tenant Context Selector

+------------------------------------------+
| Mandanten-Kontext: [Dropdown v]          |
| +--------------------------------------+ |
| |  Firma A GmbH                    [x] | |  <-- currently selected
| |  Firma B AG                          | |
| |  Test-Mandant                        | |
| +--------------------------------------+ |
+------------------------------------------+
  • Position: Top of marketplace page, above title, only visible for SUPER_ADMIN role
  • Container: bg-muted/50 rounded-lg p-3 mb-4 border border-border
  • Label: "Mandanten-Kontext:" 12px semibold text-muted-foreground
  • Dropdown: Native <select> styled with bg-card border border-border rounded-md px-3 py-1.5 text-sm
  • Width: auto (fits content), min-width 200px
  • Behavior: Changing tenant re-fetches all module activation states. Shows loading spinner during fetch.

Interaction Contracts

Marketplace Filtering

Status Tabs (Claude's Discretion decision: Tab bar + badge combination):

  • Three tabs: "Alle", "Aktiviert", "Verfuegbar"
  • Tab style: underline indicator, text-sm font-medium
  • Active tab: text-foreground border-b-2 border-primary
  • Inactive tab: text-muted-foreground hover:text-foreground
  • Tab counts shown as inline badge: (3) in muted text after label
  • Reason for tabs over pure badges: Clear top-level filtering for the most important dimension (activated vs. not). Badges on cards provide per-card status, tabs provide batch filtering.

Category Chips:

  • Horizontal scrollable row of pill buttons below status tabs
  • First chip: "Alle" (always present, default selected)
  • Remaining chips: One per unique category from module list
  • Selected: bg-primary text-primary-foreground
  • Unselected: bg-muted text-foreground hover:bg-muted/80
  • Chip size: px-3 py-1 text-xs font-medium rounded-full
  • Gap between chips: 8px
  • Overflow: horizontal scroll with overflow-x-auto and hidden scrollbar

Search:

  • Full-width input above tabs
  • Debounce: 300ms
  • Filters module name and description (client-side, all modules loaded)
  • Clear button (X icon) appears when text is entered
  • Empty search shows all modules (respecting active tab + category filter)

Module Activation Flow

Activation (Claude's Discretion decision: Immediate action with success toast):

  1. User clicks "Aktivieren" button on card or detail page
  2. Button shows loading spinner (replace text with spinner, button disabled)
  3. POST /modules/{id}/activate fires
  4. On success: Button changes to "Deaktivieren" (outline style), status badge updates to green "Aktiviert", success toast appears: "Modul erfolgreich aktiviert"
  5. On error: Error toast: "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut."
  6. Sidebar refreshes active modules list (re-fetch /modules/active)

Deactivation (Claude's Discretion decision: Confirmation dialog for deactivation only):

  1. User clicks "Deaktivieren" button
  2. Confirmation dialog appears (modal overlay):
    • Title: "Modul deaktivieren"
    • Body: "Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren? Das Modul wird aus der Seitenleiste entfernt."
    • Cancel button: "Abbrechen" (outline style)
    • Confirm button: "Deaktivieren" (destructive style: bg-destructive text-destructive-foreground)
  3. On confirm: POST /modules/{id}/deactivate, button/badge update, sidebar refresh
  4. On cancel: Dialog closes, no action

Reason for asymmetric UX: Activation is low-risk and easily reversible. Deactivation could disrupt users who depend on the module, so a confirmation prevents accidental removal.

Sidebar Module Navigation

Clicking a module in sidebar:

  1. Navigates to /modules/{category}/{slug} (existing route from Phase 3)
  2. Module opens in main content area within AppShell
  3. Sidebar highlights the active module with bg-sidebar-accent text-sidebar-accent-foreground

Sidebar active state: Current page URL determines which sidebar item gets the active class. Compare pathname against link href.

Toast Notifications

  • Position: Bottom-right of viewport
  • Duration: 4000ms auto-dismiss
  • Style: bg-card border border-border shadow-lg rounded-lg p-4
  • Success icon: Green checkmark
  • Error icon: Red X circle
  • Max visible: 3 stacked, newest on top
  • Animation: Slide in from right, fade out

Copywriting Contract

All copy in both DE and EN via next-intl. Below shows DE as primary, EN in parentheses.

Element DE Copy EN Copy
Page title "Marktplatz" "Marketplace"
Page subtitle "Entdecken und aktivieren Sie Module fuer Ihren Mandanten" "Discover and activate modules for your tenant"
Primary CTA "Modul aktivieren" "Activate Module"
Deactivation CTA "Deaktivieren" "Deactivate"
Search placeholder "Module suchen..." "Search modules..."
Sidebar search placeholder "Module suchen..." "Search modules..."
Status tab: All "Alle" "All"
Status tab: Active "Aktiviert" "Activated"
Status tab: Available "Verfuegbar" "Available"
Category chip: All "Alle" "All"
Empty state heading "Noch keine Module verfuegbar" "No modules available yet"
Empty state body "Module werden vom Systemadministrator bereitgestellt. Schauen Sie spaeter noch einmal vorbei." "Modules are provided by the system administrator. Check back later."
Empty state (filtered) heading "Keine Ergebnisse" "No results"
Empty state (filtered) body "Versuchen Sie einen anderen Suchbegriff oder aendern Sie die Filter." "Try a different search term or adjust your filters."
Error state "Module konnten nicht geladen werden. Bitte laden Sie die Seite neu." "Could not load modules. Please reload the page."
Activation success toast "Modul erfolgreich aktiviert" "Module activated successfully"
Deactivation success toast "Modul erfolgreich deaktiviert" "Module deactivated successfully"
Activation error toast "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut." "Activation failed. Please try again."
Deactivation dialog title "Modul deaktivieren" "Deactivate Module"
Deactivation dialog body "Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren? Das Modul wird aus der Seitenleiste entfernt." "Do you want to deactivate {moduleName} for this tenant? The module will be removed from the sidebar."
Deactivation dialog confirm "Deaktivieren" "Deactivate"
Deactivation dialog cancel "Abbrechen" "Cancel"
Tenant selector label "Mandanten-Kontext" "Tenant Context"
Back to marketplace "Zurueck zum Marktplatz" "Back to Marketplace"
Detail: version "Version {version}" "Version {version}"
Detail: status active "Aktiviert fuer diesen Mandanten" "Activated for this tenant"
Detail: status inactive "Nicht aktiviert" "Not activated"
Sidebar no results "Keine Module gefunden" "No modules found"

States Matrix

View Loading Empty Populated Error Filtered Empty
Marketplace grid Spinner centered in grid area (existing pattern: h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent) Illustration + heading + body from copywriting contract Card grid with modules Error banner (border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive) + retry hint "Keine Ergebnisse" + filter suggestion
Module detail Spinner centered N/A (404 redirect) Full detail layout Error banner N/A
Sidebar modules No loading state (instant from cache/store) "Keine Module" (existing) Category accordion with modules Silent fail (existing pattern) "Keine Module gefunden" when search yields nothing
Tenant selector "Laden..." in dropdown N/A (Super-Admin always has at least 1 tenant) Tenant list dropdown Toast error N/A

Accessibility

Requirement Implementation
Keyboard navigation All interactive elements reachable via Tab. Cards focusable via group-focus-visible:ring-2. Activation buttons in natural tab order.
Screen reader labels aria-label on icon-only buttons. Status badges use role="status". Toggle buttons use role="switch" with aria-checked (existing pattern).
Focus management Dialog traps focus when open (ActivationDialog). Return focus to trigger button on close.
Color contrast All text meets WCAG 2.1 AA (4.5:1 for body, 3:1 for large text). Status badges use text + background, not color alone.
Reduced motion Loading spinner respects prefers-reduced-motion with motion-safe:animate-spin. Toast slide animation gated on motion preference.

Registry Safety

Registry Blocks Used Safety Gate
shadcn official none (project does not use components.json) not applicable
Third-party none not applicable

Note: Project uses hand-rolled components following shadcn CSS variable naming conventions but without the shadcn CLI or components.json. No registry dependencies exist. All components for this phase are custom-built.


Discretion Decisions Summary

Decisions made by this design contract for areas marked as "Claude's Discretion" in 04-CONTEXT.md:

Area Decision Rationale
Filter/Status UI Tab bar ("Alle" / "Aktiviert" / "Verfuegbar") + category chips Tabs provide clear top-level filtering for the primary dimension. Category chips offer secondary filtering. Badges on cards still show per-item status.
Activation confirmation Immediate activation with success toast. Deactivation gets confirmation dialog. Asymmetric risk: activation is easily reversed, deactivation affects active users.
Visual feedback Toast notification + inline badge/button state update Consistent with existing admin/modules toggle pattern. Toast adds explicit confirmation.
Sidebar categories Keep existing accordion pattern Already established in Phase 1, works well, categories expand to show individual modules.
Sidebar collapsed mode Show only main nav icons (Dashboard, Marketplace) and admin icons. Hide module list. Module list needs text labels to be useful. Icons alone at 64px width would be confusing.
Sidebar search Inline search field within sidebar, between main nav and categories section PRTAL-05 requires sidebar search. Inline placement keeps it contextual. Header search reserved for future global search.
Module detail depth Compact single-column detail view Appropriate for v1 module count. Full description, status, activation button. No screenshots/reviews (v2).
Navigation pattern Separate pages: /marketplace for browsing, /marketplace/[slug] for detail, /modules/{cat}/{slug} for module usage Clean URL separation. Marketplace is for discovery/activation. Module routes are for usage. App Router route groups keep them distinct.

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