View Structure Contract

ID 7
UUID 88efede4-ddbb-4d2f-8390-827b3a29c266
Version 1.0
Status active
Scope Gilt für CRUD-Ressourcen mit Standard-Views (index, show, new, edit)
Erstellt 2025-12-20 10:59:58 von migration
Aktualisiert 2025-12-20 10:59:58

YAML-Inhalt

contract:
  name: "View Structure Contract"
  version: "1.0"
  status: active
  created: "2025-12-20"
  scope:
    description: "Gilt für CRUD-Ressourcen mit Standard-Views (index, show, new, edit)"
    includes:
      - "/src/View/tasks/*.php"
      - "/src/View/content/*.php"
      # Bei neuer CRUD-Ressource hier hinzufügen: "/src/View/{resource}/*.php"
    excludes:
      - "/src/View/docs/**/*.php"   # Eigener Contract: Betriebsdokumentation
      - "/src/View/chat/**/*.php"   # Interaktives Tool, kein CRUD
      - "/src/View/home/**/*.php"   # Landing Pages
      - "/src/View/layout.php"      # Layout-Template
    onboarding: |
      Neue CRUD-Ressource hinzufügen:
      1. Verzeichnis erstellen: /src/View/{resource}/
      2. Views anlegen: index.php, show.php, new.php, edit.php
      3. Contract-Scope erweitern: includes += "/src/View/{resource}/*.php"
      4. Referenz: /src/View/tasks/* als Vorlage kopieren
  principles:
    - KISS   # Keep It Simple, Stupid
    - DRY    # Don't Repeat Yourself
    - REST   # Representational State Transfer
    - SRP    # Single Responsibility Principle

# =============================================================================
# RESTful URL Schema
# =============================================================================
restful_urls:
  description: "Jede Ressource folgt dem gleichen URL-Pattern"
  pattern:
    index: "/{resource}"
    show: "/{resource}/{id}"
    new: "/{resource}/new"
    edit: "/{resource}/{id}/edit"

  rules:
    - id: URL-001
      severity: critical
      rule: "Keine /create URLs - verwende /new"
      example:
        wrong: "/content/create"
        correct: "/content/new"

    - id: URL-002
      severity: critical
      rule: "Keine /update oder /delete URLs - verwende HTTP-Methoden"
      example:
        wrong: "/tasks/1/update"
        correct: "PUT /api/v1/tasks/1"

    - id: URL-003
      severity: major
      rule: "Ressourcen-Namen im Plural"
      example:
        wrong: "/task/1"
        correct: "/tasks/1"

# =============================================================================
# View-Dateien pro Ressource
# =============================================================================
view_files:
  description: "Jede Ressource hat 4 Standard-Views"
  required:
    - index.php   # Liste aller Einträge
    - show.php    # Detail-Ansicht eines Eintrags
    - new.php     # Formular für neuen Eintrag
    - edit.php    # Formular zum Bearbeiten

  directory_pattern: "/src/View/{resource}/"

  examples:
    - "/src/View/tasks/index.php"
    - "/src/View/tasks/show.php"
    - "/src/View/content/new.php"
    - "/src/View/content/edit.php"

# =============================================================================
# Seitenstruktur: INDEX (Liste)
# =============================================================================
index_structure:
  description: "Listenansicht aller Einträge einer Ressource"

  elements_order:
    1: h1           # Titel der Ressource
    2: stats-grid   # Statistik-Karten (optional)
    3: page-actions # Button für "Neu erstellen"
    4: h2           # Untertitel vor Tabelle
    5: filters      # Suchfeld + Filter-Dropdowns
    6: table        # Datentabelle
    7: links-bar    # Links zu Dokumentation

  template: |
    <?php ob_start(); ?>

    <h1>{Ressource}</h1>

    <div class="stats-grid">
        <div class="stat-card">...</div>
        <div class="stat-card stat-card--warning">...</div>
        <div class="stat-card stat-card--info">...</div>
        <div class="stat-card stat-card--success">...</div>
    </div>

    <div class="page-actions">
        <a href="/{resource}/new" class="btn btn--primary">Neu erstellen</a>
    </div>

    <h2>{Einträge}</h2>
    <div class="filters">
        <input type="search" id="{resource}-search" class="form-input" placeholder="Durchsuchen...">
        <select id="filter-status" class="form-select--inline">...</select>
    </div>

    <table id="{resource}-table" data-sortable>
        <thead>
            <tr>
                <th data-sort="id">ID</th>
                <th data-sort="title">Titel</th>
                ...
            </tr>
        </thead>
        <tbody>...</tbody>
    </table>

    <p class="links-bar">
        <a href="/docs/{resource}">Dokumentation</a> |
        <a href="/docs/api/{resource}">API</a>
    </p>

    <script src="/js/components/data-table.js"></script>
    <script>
    document.addEventListener('DOMContentLoaded', () => {
        new DataTable('{resource}-table', {...});
    });
    </script>

    <?php $content = ob_get_clean(); ?>
    <?php require VIEW_PATH . '/layout.php'; ?>

  rules:
    - id: IDX-001
      severity: critical
      rule: "Kein page-container Wrapper"
      example:
        wrong: '<div class="page-container">...'
        correct: "<h1>Tasks</h1>"

    - id: IDX-002
      severity: critical
      rule: "Keine verschachtelten Cards"
      example:
        wrong: '<div class="card"><div class="card">...'
        correct: "Flache Struktur ohne Card-Wrapper"

    - id: IDX-003
      severity: major
      rule: "stats-grid hat genau 4 Karten"
      cards:
        - stat-card (neutral/gesamt)
        - stat-card--warning (offen/pending)
        - stat-card--info (in Arbeit)
        - stat-card--success (fertig/abgeschlossen)

    - id: IDX-004
      severity: major
      rule: "Tabelle muss DataTable-Komponente verwenden"
      requires:
        - data-sortable Attribut
        - Suchfeld
        - JavaScript-Initialisierung

# =============================================================================
# Seitenstruktur: SHOW (Detail)
# =============================================================================
show_structure:
  description: "Detail-Ansicht eines einzelnen Eintrags"

  elements_order:
    1: breadcrumb   # Navigation zurück
    2: h1           # Titel des Eintrags
    3: table        # Metadaten-Tabelle
    4: h2+content   # Inhaltsbereiche (Beschreibung, etc.)
    5: h2+actions   # Aktions-Buttons
    6: h2+tables    # Verknüpfte Daten (optional)
    7: back-link    # Link zurück zur Liste

  template: |
    <?php ob_start(); ?>

    <nav class="breadcrumb">
        <a href="/{resource}">{Ressource}</a> &raquo; {Eintrag} #{id}
    </nav>

    <h1><?= htmlspecialchars($item['title']) ?></h1>

    <table>
        <tr><th>ID</th><td><?= $item['id'] ?></td></tr>
        <tr><th>Status</th><td><span class="badge badge--<?= $item['status'] ?>"><?= $item['status'] ?></span></td></tr>
        <tr><th>Erstellt</th><td><?= $item['created_at'] ?></td></tr>
    </table>

    <?php if ($item['description']): ?>
    <h2>Beschreibung</h2>
    <p><?= nl2br(htmlspecialchars($item['description'])) ?></p>
    <?php endif; ?>

    <h2>Aktionen</h2>
    <div class="action-bar">
        <a href="/{resource}/<?= $item['id'] ?>/edit" class="btn">Bearbeiten</a>
        <button class="btn btn--success">Aktion 1</button>
        <button class="btn btn--danger">Aktion 2</button>
    </div>

    <?php if (!empty($related_items)): ?>
    <h2>Verknüpfte Daten</h2>
    <table data-sortable>...</table>
    <?php endif; ?>

    <p style="margin-top: 2rem;"><a href="/{resource}">&larr; Zurück zur Übersicht</a></p>

    <?php $content = ob_get_clean(); ?>
    <?php require VIEW_PATH . '/layout.php'; ?>

  rules:
    - id: SHW-001
      severity: critical
      rule: "Breadcrumb als erstes Element"
      format: '<nav class="breadcrumb">...</nav>'

    - id: SHW-002
      severity: critical
      rule: "Metadaten in einfacher Tabelle, keine Cards"
      example:
        wrong: '<div class="card"><div class="config-grid">...'
        correct: "<table><tr><th>ID</th><td>1</td></tr>..."

    - id: SHW-003
      severity: major
      rule: "Aktionen in action-bar, nicht verstreut"
      example:
        wrong: "Buttons an verschiedenen Stellen der Seite"
        correct: '<div class="action-bar">alle Buttons hier</div>'

    - id: SHW-004
      severity: major
      rule: "Zurück-Link am Ende der Seite"
      format: '<p style="margin-top: 2rem;"><a href="/{resource}">&larr; Zurück zur Übersicht</a></p>'

# =============================================================================
# Seitenstruktur: NEW (Erstellen)
# =============================================================================
new_structure:
  description: "Formular zum Erstellen eines neuen Eintrags"

  elements_order:
    1: breadcrumb   # Navigation
    2: h1           # "Neu erstellen"
    3: form         # Formular

  template: |
    <?php ob_start(); ?>

    <nav class="breadcrumb">
        <a href="/{resource}">{Ressource}</a> &raquo; Neu
    </nav>

    <h1>Neuer {Eintrag}</h1>

    <form class="form" method="post" action="/{resource}" style="max-width: 600px;">
        <div class="form-group">
            <label for="title">Titel *</label>
            <input type="text" id="title" name="title" class="form-input" required>
        </div>
        <div class="form-group">
            <label for="description">Beschreibung</label>
            <textarea id="description" name="description" class="form-textarea" rows="4"></textarea>
        </div>
        <div class="form-group">
            <label for="type">Typ</label>
            <select id="type" name="type" class="form-select">
                <option value="">-- Auswählen --</option>
            </select>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn--primary">Erstellen</button>
            <a href="/{resource}" class="btn">Abbrechen</a>
        </div>
    </form>

    <?php $content = ob_get_clean(); ?>
    <?php require VIEW_PATH . '/layout.php'; ?>

  rules:
    - id: NEW-001
      severity: critical
      rule: "Formular mit class='form' und max-width: 600px"
      example:
        wrong: '<form class="card">...'
        correct: '<form class="form" style="max-width: 600px;">'

    - id: NEW-002
      severity: critical
      rule: "Keine form-section oder verschachtelte Strukturen"
      example:
        wrong: '<div class="form-section"><h2 class="form-section__title">...'
        correct: '<div class="form-group"><label>...</label><input>...</div>'

    - id: NEW-003
      severity: major
      rule: "Labels ohne spezielle Klassen"
      example:
        wrong: '<label class="form-group__label">'
        correct: "<label for=\"title\">"

    - id: NEW-004
      severity: major
      rule: "form-actions am Ende mit Primary-Button zuerst"
      format: |
        <div class="form-actions">
            <button type="submit" class="btn btn--primary">Erstellen</button>
            <a href="/{resource}" class="btn">Abbrechen</a>
        </div>

# =============================================================================
# Seitenstruktur: EDIT (Bearbeiten)
# =============================================================================
edit_structure:
  description: "Formular zum Bearbeiten eines bestehenden Eintrags"

  elements_order:
    1: breadcrumb   # Navigation mit Link zum Eintrag
    2: h1           # "Bearbeiten"
    3: form         # Formular mit vorausgefüllten Werten
    4: form-message # Feedback-Container
    5: script       # JavaScript für API-Call

  template: |
    <?php ob_start(); ?>

    <nav class="breadcrumb">
        <a href="/{resource}">{Ressource}</a> &raquo;
        <a href="/{resource}/<?= $item['id'] ?>">{Eintrag} #<?= $item['id'] ?></a> &raquo;
        Bearbeiten
    </nav>

    <h1>{Eintrag} bearbeiten</h1>

    <form id="{resource}-form" class="form" style="max-width: 600px;">
        <div class="form-group">
            <label for="title">Titel *</label>
            <input type="text" id="title" name="title" class="form-input"
                   value="<?= htmlspecialchars($item['title']) ?>" required>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn--primary">Speichern</button>
            <a href="/{resource}/<?= $item['id'] ?>" class="btn">Abbrechen</a>
        </div>
    </form>
    <div id="form-message" class="form-message"></div>

    <script>
    document.getElementById('{resource}-form').addEventListener('submit', async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData.entries());

        const response = await fetch('/api/v1/{resource}/<?= $item['id'] ?>', {
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
        const result = await response.json();

        if (result.success) {
            window.location.href = '/{resource}/<?= $item['id'] ?>';
        } else {
            document.getElementById('form-message').innerHTML =
                '<span class="form-message--error">Fehler: ' + result.error + '</span>';
        }
    });
    </script>

    <?php $content = ob_get_clean(); ?>
    <?php require VIEW_PATH . '/layout.php'; ?>

  rules:
    - id: EDT-001
      severity: critical
      rule: "Breadcrumb enthält Link zum Eintrag"
      example:
        wrong: '<a href="/tasks">Tasks</a> &raquo; Bearbeiten'
        correct: '<a href="/tasks">Tasks</a> &raquo; <a href="/tasks/1">Task #1</a> &raquo; Bearbeiten'

    - id: EDT-002
      severity: major
      rule: "Formular verwendet API mit PUT-Methode"
      method: "PUT /api/v1/{resource}/{id}"

    - id: EDT-003
      severity: major
      rule: "Abbrechen-Link führt zum Eintrag, nicht zur Liste"
      example:
        wrong: '<a href="/tasks">Abbrechen</a>'
        correct: '<a href="/tasks/1">Abbrechen</a>'

# =============================================================================
# Verbotene Elemente
# =============================================================================
forbidden:
  description: "Diese Elemente/Patterns sind in Views nicht erlaubt"

  elements:
    - id: FRB-001
      element: "page-container"
      severity: critical
      reason: "Unnötige Verschachtelung"

    - id: FRB-002
      element: "page-header"
      severity: critical
      reason: "Verwende breadcrumb + h1 separat"

    - id: FRB-003
      element: "card (als Wrapper)"
      severity: critical
      reason: "Keine Cards um Formulare oder Inhalte"
      exception: "result-box für Ergebnisanzeige erlaubt"

    - id: FRB-004
      element: "form-section"
      severity: critical
      reason: "Flache Formularstruktur bevorzugen"

    - id: FRB-005
      element: "grid-2, main-column, side-column"
      severity: major
      reason: "Einspaltiges Layout bevorzugen"

    - id: FRB-006
      element: "btn--large"
      severity: minor
      reason: "Standard-Button-Größe verwenden"

    - id: FRB-007
      element: "form-group__label, form-group__hint"
      severity: major
      reason: "Einfache Labels ohne BEM-Modifikatoren"

# =============================================================================
# CSS-Klassen Referenz
# =============================================================================
css_classes:
  layout:
    - breadcrumb        # Navigation
    - stats-grid        # Container für Statistik-Karten
    - stat-card         # Einzelne Statistik-Karte
    - page-actions      # Container für Haupt-Aktionen
    - action-bar        # Container für Detail-Aktionen
    - filters           # Container für Suchfeld + Filter
    - links-bar         # Container für Dokumentations-Links
    - result-box        # Container für Ergebnisanzeige

  forms:
    - form              # Formular-Container
    - form-group        # Label + Input Gruppe
    - form-input        # Text-Input
    - form-textarea     # Textarea
    - form-select       # Select-Dropdown
    - form-select--inline  # Inline-Select (in Filtern)
    - form-actions      # Button-Container
    - form-message      # Feedback-Container
    - inline-form       # Formular in einer Zeile (für Actions)

  buttons:
    - btn               # Basis-Button
    - btn--primary      # Primäre Aktion
    - btn--success      # Erfolg/Bestätigen
    - btn--danger       # Löschen/Ablehnen
    - btn--light        # Sekundäre Aktion
    - btn--info         # Information

  badges:
    - badge             # Basis-Badge
    - badge--{status}   # Status-spezifisch (pending, completed, etc.)

  tables:
    - data-sortable     # Attribut für sortierbare Tabellen
    - data-sort         # Attribut für sortierbare Spalten
    - empty-state       # Leerer Zustand
    - empty-state--small  # Kompakter leerer Zustand

# =============================================================================
# Validierung
# =============================================================================
validation:
  pass_threshold:
    critical: 0    # Keine kritischen Fehler erlaubt
    major: 2       # Max 2 major Fehler
    minor: 5       # Max 5 minor Fehler

  automated_checks:
    - "Keine page-container Klasse"
    - "Keine card Wrapper um Formulare"
    - "Breadcrumb als erstes Element in show/new/edit"
    - "form hat max-width Style"
    - "form-actions vor </form>"
    - "Tabellen haben data-sortable"

# =============================================================================
# Beispiele: Korrekte Implementierung
# =============================================================================
examples:
  reference_files:
    index: "/src/View/tasks/index.php"
    show: "/src/View/tasks/show.php"
    new: "/src/View/tasks/new.php"
    edit: "/src/View/tasks/edit.php"

  resources_using_contract:
    - tasks
    - content

Aktionen

Bearbeiten

Letzte Validierungen

Datum Ergebnis Critical Major Minor Dauer
2025-12-20 11:07:52 passed 0 0 0 15ms

← Zurück zur Übersicht