diff --git a/README.md b/README.md index 1c04e47..d2e67b1 100644 --- a/README.md +++ b/README.md @@ -200,11 +200,13 @@ Zentrale Partials: ## Asset- und Frontend-System -- CSS-Layer-Entrypoint: `/web/css/app-layers.css` +- Core-CSS-Entrypoint: `/web/css/core.css` +- CSS-Layer/Vendor-Basis: `/web/css/app-layers.css` - Vendor-Overrides: `/web/css/vendor-overrides` - Asset-Gruppen: `/config/assets.php` - Template-Styles: `renderTemplateStyles('default'|'login')` - Seiten-Styles per Action: `Buffer::set('style_groups', ...)` +- CSS-Contract-Check: `bin/css-contract-check.sh` - JS-Bootstrap: - `/web/js/app-boot.js` (frueh, ohne Module) - `/web/js/app-init.js` (Standardseiten) diff --git a/bin/css-contract-check.sh b/bin/css-contract-check.sh new file mode 100755 index 0000000..4867478 --- /dev/null +++ b/bin/css-contract-check.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/.." && pwd)" +cd "${repo_root}" + +if ! command -v rg >/dev/null 2>&1; then + echo "[ERROR] rg is required for css-contract-check." >&2 + exit 2 +fi + +failures=0 + +ok() { + echo "[OK] $*" +} + +fail() { + echo "[FAIL] $*" >&2 + failures=$((failures + 1)) +} + +expected_layer_order='@layer tokens, vendor, layout, vendor-overrides, components, pages, utilities;' +if rg -Fq "${expected_layer_order}" web/css/app-layers.css; then + ok "Layer order declaration found in web/css/app-layers.css" +else + fail "Layer order declaration is missing or changed in web/css/app-layers.css" +fi + +while IFS= read -r file; do + expected_layer='' + case "${file}" in + web/css/base/*|modules/*/web/css/base/*) + expected_layer='tokens' + ;; + web/css/layout/*|modules/*/web/css/layout/*) + expected_layer='layout' + ;; + web/css/components/*|modules/*/web/css/components/*) + expected_layer='components' + ;; + web/css/pages/*|modules/*/web/css/pages/*) + expected_layer='pages' + ;; + web/css/vendor-overrides/*|modules/*/web/css/vendor-overrides/*) + expected_layer='vendor-overrides' + ;; + esac + + if [[ -z "${expected_layer}" ]]; then + continue + fi + + if [[ "${file}" == "web/css/pages/app-error.css" || "${file}" == "web/css/pages/app-error-debug.css" ]]; then + continue + fi + + detected_layer="$(sed -n '1,120p' "${file}" | sed -nE 's/.*@layer[[:space:]]+([^[:space:]{]+).*/\1/p' | head -n1)" + if [[ -z "${detected_layer}" ]]; then + fail "${file}: missing @layer declaration near file top" + continue + fi + + if [[ "${detected_layer}" != "${expected_layer}" ]]; then + fail "${file}: @layer ${detected_layer} does not match expected ${expected_layer}" + fi +done < <(rg --files web/css modules | rg '\.css$') + +remote_import_pattern='@import[[:space:]]+url\(["'"'"']?(https?:)?//|@import[[:space:]]+["'"'"']?(https?:)?//' +remote_hits="$(rg -n -g '*.css' -e "${remote_import_pattern}" web/css modules || true)" +if [[ -n "${remote_hits}" ]]; then + fail "Remote CSS import detected (must vendor locally):" + echo "${remote_hits}" >&2 +else + ok "No remote CSS imports detected" +fi + +if [[ ! -f web/css/core.css ]]; then + fail "web/css/core.css is missing" +else + first_import="$(rg -n '^@import' web/css/core.css | head -n1 || true)" + if [[ "${first_import}" == *'app-layers.css'* ]]; then + ok "web/css/core.css imports app-layers.css first" + else + fail "web/css/core.css must import app-layers.css as first @import" + fi +fi + +if [[ "${failures}" -gt 0 ]]; then + echo "[FAIL] CSS contract check failed with ${failures} issue(s)." >&2 + exit 1 +fi + +echo "[OK] CSS contract check passed." diff --git a/bin/qa-required.sh b/bin/qa-required.sh index 9d264f3..fab5510 100755 --- a/bin/qa-required.sh +++ b/bin/qa-required.sh @@ -41,6 +41,9 @@ run_step "QG-008 Docs link integrity" \ run_step "QG-008 Docs drift contract" \ bin/docs-drift-check.sh +run_step "CSS contract check" \ + bin/css-contract-check.sh + run_step "QG-009 Codex skills sync" \ bin/codex-skills-sync.sh --check diff --git a/config/assets.php b/config/assets.php index c6e531b..c043afb 100644 --- a/config/assets.php +++ b/config/assets.php @@ -19,37 +19,9 @@ return [ 'css/components/app-brand.css', 'css/components/app-file-upload.css', ], - 'default' => [ - 'css/vendor-overrides/multi-select.css', - 'css/components/app-icon-button.css', - 'css/components/app-buttons.css', - 'css/vendor-overrides/gridjs.css', - 'css/layout/app-topbar.css', - 'css/layout/app-sidebar.css', - 'css/components/app-search.css', - 'css/components/app-list-titlebar.css', - 'css/components/app-details-titlebar.css', + 'core' => [ + 'css/core.css', 'css/components/app-details-validation-summary.css', - 'css/components/app-dashboard-titlebar.css', - 'css/components/app-empty-state.css', - 'css/components/app-details.css', - 'css/components/app-details-card.css', - 'css/components/app-form-info-tile.css', - 'css/components/app-list-table.css', - 'css/components/app-list-tabs.css', - 'css/components/app-tabs.css', - 'css/components/app-list-toolbar.css', - 'css/components/app-filter-drawer.css', - 'css/components/app-active-filter-chips.css', - 'css/components/app-confirm-dialog.css', - 'css/components/app-breadcrumb.css', - 'css/components/app-tile.css', - 'css/components/app-tooltips.css', - 'css/components/app-lookup-field.css', - 'css/components/app-page-editor.css', - 'css/vendor-overrides/editorjs.css', - 'css/components/app-page-copy.css', - 'css/layout/app-aside-icon-bar.css', ], 'login' => [ 'css/pages/app-login.css', @@ -65,7 +37,7 @@ return [ ], ], 'templates' => [ - 'default' => ['base', 'shared', 'default'], + 'default' => ['base', 'core'], 'login' => ['base', 'shared', 'login'], 'error' => ['base', 'error'], 'page' => ['base', 'shared', 'page-layout'], diff --git a/docs/reference-frontend-css.md b/docs/reference-frontend-css.md index 5c669d7..8317ec1 100644 --- a/docs/reference-frontend-css.md +++ b/docs/reference-frontend-css.md @@ -1,6 +1,6 @@ # Frontend CSS -Letzte Aktualisierung: 2026-03-19 +Letzte Aktualisierung: 2026-04-20 ## Ziel @@ -8,8 +8,10 @@ Diese Seite beschreibt die kompakte Standardstruktur für CSS in diesem Projekt: ## Struktur +- `/web/css/core.css` + - zentraler Core-Entrypoint für die Default-Admin-Oberfläche. - `/web/css/app-layers.css` - - zentraler Entrypoint für Layer-Reihenfolge. + - zentrale Layer-Reihenfolge + Vendor-Basis. - `/web/css/base` - Variablen, Reset, Typografie, globale Tokens. - `/web/css/layout` @@ -50,6 +52,11 @@ Kurzregel: Asset-Gruppen werden in `/config/assets.php` registriert. Seiten laden bei Bedarf Gruppen über `Buffer::set('style_groups', ...)`. +Default-Template: + +- `base` lädt Layer-/Token-Basis. +- `core` lädt den konsolidierten Core-Stack über `/web/css/core.css`. + Beispiel (Action): ```php @@ -76,6 +83,7 @@ Regel: Vendor-Overrides nur für Bibliotheks-Selektoren; projektspezifische UI-S 3. Wird die Gruppe auf der Zielseite wirklich geladen (`style_groups`)? 4. Gibt es ungewollte Seiteneffekte auf anderen Seiten? 5. Sind bestehende Variablen/Konventionen eingehalten? +6. Contract-Check lokal ausführen: `bin/css-contract-check.sh` ## Module-CSS Token-Regel diff --git a/web/css/core.css b/web/css/core.css new file mode 100644 index 0000000..ce7fbb4 --- /dev/null +++ b/web/css/core.css @@ -0,0 +1,48 @@ +@charset "UTF-8"; + +/* + * Core CSS framework entrypoint for the default admin shell. + * Keep imports in deterministic order; file-level @layer declarations define precedence. + */ +@import url("app-layers.css"); + +/* Shared foundation used by multiple templates */ +@import url("components/app-badges.css"); +@import url("layout/app-shell.css"); +@import url("components/app-dialog.css"); +@import url("components/app-blockquote.css"); +@import url("components/app-forms.css"); +@import url("components/app-flash.css"); +@import url("components/app-brand.css"); +@import url("components/app-file-upload.css"); + +/* Default admin experience */ +@import url("vendor-overrides/multi-select.css"); +@import url("components/app-icon-button.css"); +@import url("components/app-buttons.css"); +@import url("vendor-overrides/gridjs.css"); +@import url("layout/app-topbar.css"); +@import url("layout/app-sidebar.css"); +@import url("components/app-search.css"); +@import url("components/app-list-titlebar.css"); +@import url("components/app-details-titlebar.css"); +@import url("components/app-dashboard-titlebar.css"); +@import url("components/app-empty-state.css"); +@import url("components/app-details.css"); +@import url("components/app-details-card.css"); +@import url("components/app-form-info-tile.css"); +@import url("components/app-list-table.css"); +@import url("components/app-list-tabs.css"); +@import url("components/app-tabs.css"); +@import url("components/app-list-toolbar.css"); +@import url("components/app-filter-drawer.css"); +@import url("components/app-active-filter-chips.css"); +@import url("components/app-confirm-dialog.css"); +@import url("components/app-breadcrumb.css"); +@import url("components/app-tile.css"); +@import url("components/app-tooltips.css"); +@import url("components/app-lookup-field.css"); +@import url("components/app-page-editor.css"); +@import url("vendor-overrides/editorjs.css"); +@import url("components/app-page-copy.css"); +@import url("layout/app-aside-icon-bar.css"); diff --git a/web/css/pages/app-docs.css b/web/css/pages/app-docs.css index a021f97..1ec1db8 100644 --- a/web/css/pages/app-docs.css +++ b/web/css/pages/app-docs.css @@ -1,4 +1,4 @@ -@layer components { +@layer pages { /* ─── Layout ─────────────────────────────────────────────────── */ .app-docs-container {