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> » {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}">← 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}">← 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> » 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> »
<a href="/{resource}/<?= $item['id'] ?>">{Eintrag} #<?= $item['id'] ?></a> »
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> » Bearbeiten'
correct: '<a href="/tasks">Tasks</a> » <a href="/tasks/1">Task #1</a> » 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
Letzte Validierungen
| Datum |
Ergebnis |
Critical |
Major |
Minor |
Dauer |
| 2025-12-20 11:07:52 |
passed |
0 |
0 |
0 |
15ms |
← Zurück zur Übersicht