wiki/b-reference/ui-nyla/layout.md

15 KiB

Layout-System -- Referenz

Uebersicht

Das Layout-System definiert, wie jede Seite in ui-nyla strukturiert wird. Es ersetzt das manuelle Pattern (adminPage/adminPageFill/pageHeader/tableContainer aus Admin.module.css) und alle Ad-hoc-Tab-Implementierungen durch ein einheitliches Komponentensystem.

Quell-Plan: pagelayout_component_system_fd9fde3a.plan.md (v3)

Komponente Datei Zweck
StackLayout components/Layout/StackLayout.tsx Seiten-Container mit Slots (Header, Toolbar, Tabs, Body, Footer)
Panel components/Layout/Panel.tsx Typisierte Region (Card, Table, Dashboard, Toolbar, Editor, Wizard) mit Collapse/Expand
LayoutTabs components/Layout/LayoutTabs.tsx Einziges Tab-System, URL als Source-of-Truth
ViewStack components/Layout/ViewStack.tsx Master-Detail-Navigation (list/catalog/detail via URL)
useScrollMode hooks/useScrollMode.ts Scroll-Modus-Erkennung (bounded/document)

Begriffe (WICHTIG, nicht verwechseln)

Drei verschiedene Konzepte, die sauber getrennt werden muessen:

Begriff Anzahl Werte Bedeutung
Slot 5 Header, Toolbar, Tabs, Body, Footer Benannte Bereiche des StackLayout-Seitenrahmens (Compound-Components, z.B. <StackLayout.Body>)
StackLayout-Variant 4 table, scroll, form, dashboard Scroll-Verhalten der gesamten Seite (Prop variant an StackLayout)
Region beliebig Ein Panel-Inhaltsblock im Body. Das ist der collapsible/expandable Block, den der User sieht.
Regionstyp 6 card, table, dashboard, toolbar, editor, wizard Die Variante einer Region (Prop variant an Panel)

Merksatz:

  • Eine Seite = ein StackLayout (mit variant fuer Scroll) und seinen Slots.
  • Jeder Inhaltsblock im Body = eine Region (ein Panel mit variant = Regionstyp).
  • toolbar existiert sowohl als Slot (StackLayout.Toolbar) als auch als Regionstyp (Panel variant="toolbar"). In der Praxis werden Filter-/Action-Bars als Region (Panel variant="toolbar") im Body platziert; der Slot StackLayout.Toolbar ist optional fuer seitenweite Toolbars.

Architektur-Prinzipien

  1. URL ist Source-of-Truth fuer Navigation (aktiver Tab, aktive View, entityId). Kein useState fuer Tabs.
  2. Jeder Seiteninhalt ist eine Region (Panel mit Variant). Generische Aenderungen an einem Regionstyp wirken systemweit.
  3. Keine Fallbacks: Fehlender Kontext = Error, nicht stiller Default.
  4. Kein RBAC im UI: Sichtbarkeit via Backend-Flags.
  5. Persistence ist pluggable: Panel-Collapse in localStorage, Navigation in URL, Daten in DB.

Scroll-Vertrag

Problem

Die Layout-Kette ist durchgehend overflow:hidden mit flex-shrink:0 fuer Chrome-Bereiche. Auf kleinen Viewports fuellt der Header die Seite, die Tabelle wird unbedienbar.

Loesung: scrollMode-Zustandsmaschine

Modus Bedingung Verhalten
bounded Viewport > 1024px Breite UND > 500px Hoehe overflow:hidden-Kette intakt. Tabelle scrollt intern. Header bleibt sichtbar.
document Viewport <= 1024px ODER <= 500px Hoehe overflow:visible-Kette. Seite scrollt als Dokument. Header scrollt weg. Tabelle waechst natuerlich.

Mechanismus

Hook useScrollMode() setzt document.documentElement.dataset.scrollMode. CSS-Module nutzen :global(html[data-scroll-mode="document"]) als Selektor-Prefix.

import { useScrollMode } from '../../hooks/useScrollMode';

const MyPage = () => {
  const scrollMode = useScrollMode();
  // scrollMode wird automatisch auf StackLayout propagiert
  return <StackLayout variant="table">...</StackLayout>;
};

Layout-Kette (verifiziert)

MainLayout.mainLayout        height:100dvh; overflow:hidden
MainLayout.content           overflow:hidden (desktop) / auto (mobile)
MainLayout.outletShell       overflow-y:auto
FeatureLayout.featureContent overflow:hidden; flex:1
FeatureView.viewContent      overflow:auto; padding:1.5rem
StackLayout root             data-scroll-mode + data-variant
Panel                        data-variant (card|table|dashboard|toolbar|editor|wizard)
FormGeneratorTable           height:100%; flex:1; overflow:hidden

StackLayout

Container fuer eine gesamte Seite oder einen Tab-Inhalt. Compound-Component-Pattern mit benannten Slots.

Slots

Slot Zweck Beispiel
Header Seitentitel, Breadcrumb Immer sichtbar
Toolbar Seitenweite Filter, Actions Kontextbezogen (optional)
Tabs LayoutTabs-Container Tab-Navigation
Body Hauptinhalt — enthaelt die Regionen (Panels) Tabelle, Formular, Dashboard
Footer Status, Aktionen Optional

StackLayout-Varianten (Scroll-Verhalten)

Variant Body-Verhalten
table flex:1, min-height:0, bounded scroll
scroll Normaler Scroll (Standard)
form Scroll mit Padding
dashboard Grid-freundlich, natuerliche Hoehe

Beispiel: Tabellen-Seite

<StackLayout variant="table">
  <StackLayout.Header>
    <h2>Benutzer</h2>
  </StackLayout.Header>
  <StackLayout.Body>
    <Panel variant="toolbar">
      <FilterBar />
    </Panel>
    <Panel variant="table" title="Benutzerliste" collapsible>
      <FormGeneratorTable ... />
    </Panel>
  </StackLayout.Body>
</StackLayout>

Panel (Region)

Typisierter Container fuer jeden Inhaltsblock. Jede Panel-Instanz hat einen variant, der das Layout-Verhalten bestimmt.

Props

Prop Typ Default Beschreibung
variant card | table | dashboard | toolbar | editor | wizard card Bestimmt CSS-Verhalten (Flex, Padding, Border)
title string | ReactNode - Header-Titel (wenn gesetzt, wird Header gerendert)
subtitle string | ReactNode - Untertitel im Header
actions ReactNode - Action-Buttons im Header
collapsible boolean false Collapse/Expand-Toggle im Header
defaultCollapsed boolean false Initial-Zustand
collapseKey string - localStorage-Key fuer Persistenz (panel-collapse:{key})

Varianten-Verhalten

Variant flex Padding Border Typischer Inhalt
card - 14px Standard-Card Info-Sektionen, Settings, Formulare
table flex:1, min-height:0 0 Standard-Card FormGeneratorTable
dashboard - 14px Standard-Card KPI-Grid, Stats-Cards
toolbar - 8px 14px kein Radius Filter-Bar, Actions, kompakt
editor flex:1, min-height:0 0 Standard-Card Code-Editor, Flow-Editor, Chat
wizard - 20px Standard-Card Wizard-Schritte

Generische Aenderungen

CSS-Aenderungen an [data-variant="table"] in Panel.module.css wirken auf ALLE Tabellen-Regionen im System. Beispiel:

/* Panel.module.css */
.panel[data-variant="table"] .body {
  min-height: 300px;  /* wirkt systemweit fuer alle Tabellen */
}

Beispiel: Collapsible Dashboard

<Panel variant="dashboard" title="Uebersicht" collapsible collapseKey="dashboard-overview">
  <div className={styles.kpiGrid}>
    <StatCard label="Workflows" value={7} />
    <StatCard label="Aktiv" value={5} />
  </div>
</Panel>

LayoutTabs

Einziges Tab-System. Ersetzt UiComponents/Tabs und alle manuellen Tab-Button-Implementierungen.

Props

Prop Typ Default Beschreibung
items LayoutTabItem[] - Tab-Definitionen
urlParam string - (kein Default) URL-Search-Parameter fuer aktiven Tab
defaultTab string erstes Item Fallback wenn URL-Param fehlt
preserveSearchParams boolean true Andere URL-Params beim Tab-Wechsel erhalten
aliasMap Record<string, string> - URL-Wert -> Tab-ID Mapping
syncUrl boolean !!urlParam Tab-Wechsel schreibt in URL (default: an, wenn urlParam gesetzt)
lazy boolean false Wenn true: besuchte Tabs bleiben gemountet (display:none), State bleibt erhalten. Wenn false: nur aktiver Tab gemountet, Wechsel remountet (State-Verlust). Fuer Tab-Sets mit Formularen/Streams lazy setzen.
collapsible boolean false Tab-Bar einklappbar

LayoutTabItem

interface LayoutTabItem {
  id: string;
  label: string;
  icon?: ReactNode;
  group?: string;      // Gruppierung (Kategorie-Titel)
  disabled?: boolean;
  render: () => ReactNode;
}

Beispiel: Gruppierte Tabs

<LayoutTabs
  urlParam="tab"
  defaultTab="workflows"
  items={[
    { id: 'workflows', label: 'Workflows', group: 'Ausfuehren', render: () => <WorkflowsTab /> },
    { id: 'runs',      label: 'Durchlaeufe', group: 'Ausfuehren', render: () => <RunsTab /> },
    { id: 'editor',    label: 'Editor', group: 'Erstellen', render: () => <EditorTab /> },
    { id: 'templates', label: 'Vorlagen', group: 'Erstellen', render: () => <TemplatesTab /> },
  ]}
/>

ViewStack

Master-Detail-Navigation. Modi list | catalog | detail via URL-Parameter.

Props

Prop Typ Default Beschreibung
viewParam string 'view' URL-Parameter fuer aktive View
entityParam string - URL-Parameter fuer Entity-ID (z.B. runId)
defaultView ViewMode 'list' Fallback-View

View-Modi

Modus URL-Beispiel Verhalten
list /workflows Listenansicht (FormGeneratorTable)
catalog /workflows?view=catalog Katalog/Grid-Ansicht
detail /workflows?view=detail&runId=abc Detail-Ansicht mit Entity

Seiten-Aufbau-Muster

Muster 1: Einfache Tabellen-Seite

StackLayout (variant="table")
  └── Body
      ├── Panel (variant="toolbar"): Header-Actions, Filter
      └── Panel (variant="table", collapsible): FormGeneratorTable

Beispiele: AdminUsersPage, AdminMandatesPage, PromptsPage, ConnectionsPage

Muster 2: Dashboard-Seite

StackLayout (variant="dashboard")
  └── Body
      ├── Panel (variant="dashboard"): KPI-Grid
      ├── Panel (variant="card", collapsible): Themen/Module
      └── Panel (variant="card", collapsible): Details/Info

Beispiele: TrusteeDashboardView, CommcoachDashboardView, TeamsbotDashboardView

Muster 3: Seite mit Tabs

StackLayout (variant="table")
  └── Body
      ├── Panel (variant="toolbar"): Kontext-Filter (optional)
      └── LayoutTabs (urlParam="tab")
          ├── Tab "uebersicht": Panel (variant="dashboard") + Panel (variant="table")
          └── Tab "settings": Panel (variant="card")

Beispiele: WorkflowAutomationHubPage, Settings, ComplianceAuditPage

Muster 4: Wizard-Seite

StackLayout (variant="form")
  └── Body
      ├── Panel (variant="toolbar"): Step-Indicator + Navigation
      └── Panel (variant="wizard"): Step-Inhalt

Beispiele: AdminMandateWizardPage, AdminInvitationWizardPage

Muster 5: Editor/Chat-Seite

StackLayout (variant="scroll")
  └── Body
      └── Panel (variant="editor"): Editor/Chat (flex:1, volle Hoehe)

Beispiele: WorkflowEditorPage, ChatStream

Muster 6: Split-Layout (Phase 6b PanelLayout)

PanelLayout (config-getriebener Split-Tree)
  ├── Panel links: Sidebar (collapsible, resizable)
  ├── Panel mitte: Hauptinhalt
  └── Panel rechts: Detail/Preview (collapsible, resizable)

Beispiele: WorkspacePage, FilesPage, CommcoachDossierView, RedmineBrowserView

Migrations-Checkliste

Bei JEDER Seiten-Migration abarbeiten (Lessons aus Phase 4):

1. Daten & API

  • Mandantenfilter: "Alle" = kein mandateId-Param, Backend filtert per User-Mandatsliste
  • Daten-Reload bei Kontextwechsel: Dependencies muessen selectedMandateId enthalten
  • Metriken/Dashboard konsistent mit Tabelle: Gleicher Scope

2. FK-Label-Resolution

  • displayField-Konvention: {fieldName}Label (z.B. workflowId -> workflowIdLabel)
  • enrichRowsWithFkLabels in ALLEN Pfaden (Standard + filterValues)
  • Cross-Mandate FK-Lookup: getRecord() statt mandate-scoped Methoden

3. FormGeneratorTable Integration

  • hookData.refetch als einziger Datenpfad
  • Keine redundanten Filter-Buttons (FormGeneratorTable hat eingebaute Filter)
  • Kein RBAC im UI

4. Backend RBAC

  • Kein isPlatformAdmin-Bypass fuer Lesezugriffe
  • Fail-closed: Fehlender Filter = leeres Ergebnis (__impossible__-Sentinel)
  • mandateId-Validierung gegen userMandateIds

5. Layout & UI

  • StackLayout + Panel verwenden, URL als Source-of-Truth
  • Panel fuer collapsible Bloecke
  • scrollMode-Integration: min-height im document-Mode

6. Keep-Alive (nur fuer registrierte Seiten)

  • Pruefen ob Seite in config/keepAliveRoutes.tsx registriert ist
  • Bei Wiedereinblenden Re-Measure/Re-Clamp ausloesen (Hoehe war display:none -> 0)
  • matchLocation-Bedingung bei Tab-basierten Keep-Alive-Routes erhalten (z.B. ?tab=editor)
  • scopeKey (Mandant/Instanz) als React-key beibehalten — Scope-Wechsel = Neu-Mount
  • Resize-/SSE-Listener-Cleanup darf persistenten State nicht zerstoeren

Anti-Patterns

Anti-Pattern Stattdessen
styles.adminPage / adminPageFill StackLayout mit passender Variante
styles.tableContainer Panel variant="table"
styles.pageHeader mit inline Actions Panel variant="toolbar" oder StackLayout.Header
Manuelle Tab-Buttons (onClick, activeTab State) LayoutTabs mit urlParam
UiComponents/Tabs LayoutTabs
useState fuer aktiven Tab URL via urlParam
isPlatformAdmin Bypass im Backend Mandate-scoped Filter mit fail-closed
modalOverlay CSS per Seite dupliziert (siehe separater Modal-Konsolidierungsplan)

Dateien

Datei Zweck
components/Layout/StackLayout.tsx + .module.css Seiten-Container
components/Layout/Panel.tsx + .module.css Typisierte Region
components/Layout/LayoutTabs.tsx + .module.css Tab-System
components/Layout/ViewStack.tsx + .module.css Master-Detail
components/Layout/types.ts Shared TypeScript-Typen
hooks/useScrollMode.ts Scroll-Modus-Erkennung
config/keepAliveRoutes.tsx Keep-Alive-Registrierung (persistente Seiten)
layouts/MainLayout.tsx + .module.css App-Shell (Sidebar + Content)
layouts/FeatureLayout.tsx + .module.css Feature-Chrome (Breadcrumb, Kontext)
pages/FeatureView.tsx + .module.css View-Router fuer Feature-Instanzen