Add Codex as read-only reviewer, retry blocked video autoplay
CMS quality / php (8.2) (push) Waiting to run
CMS quality / php (8.4) (push) Waiting to run
CMS quality / browser (push) Waiting to run
CMS quality / mysql (push) Waiting to run

scripts/codex-review.ps1 lets the Codex CLI review a state read-only in
a clean git worktree without .env or var/, as in Domira: by default
everything since the last release tag, or -Seit, -Commit, -Offen,
-Alles. The final answer is also saved under reports/. Review rules for
KantSites live in scripts/codex-review-prompt.md; README and AGENTS.md
describe the workflow.

video.js: when the browser refuses to start a loop (NotAllowedError),
the first touch or key press starts it; a click on the pause button
only counts as that button's own decision. ADR on videos updated.

Luximmo seed: Instagram and Facebook profiles in the footer, icons
instead of numbers in the process steps, services as subgrid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-11-11 15:55:29 +01:00
1 parent 6e18a476c2
commit 4a33090905
8 files changed
+168 -18

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
Du prüfst Code von KantSites, einem selbst betriebenen CMS für Kundenwebsites (PHP 8.4, Symfony, Doctrine, Twig, SQLite oder MySQL/MariaDB, Shared Hosting ohne Node). Die Regeln stehen in AGENTS.md, Entscheidungen in docs/ARCHITECTURE.md. Zusätzlich gilt: keine nativen Browser-Dialoge (`confirm()`, `alert()`, `prompt()`); Rückfragen laufen über einen Dialog der Anwendung.
Ändere nichts. Melde nur echte Fehler, bei denen du ein konkretes Fehlerszenario benennen kannst:
1. Sicherheit: Umgehung von Anmeldung oder Rollen in der Verwaltung, fehlende Rechteprüfung, CSRF, XSS (zum Beispiel `|raw` in Twig, Inhalte aus Blöcken, Formularen oder Plugins), offene Redirects, Pfadmanipulation bei Uploads, Medien, Inhaltspaketen und Updater, Verstöße gegen die Schutzkopfzeilen.
2. Korrektheit: Logikfehler beim Speichern, Publizieren und Laden von Seiten, Verlust von Entwürfen, nicht erkannte veraltete Schreibzugriffe (`StaleWrite`), fehlerhafte Migrationen von Dokumentschema oder Datenbank, Unterschiede zwischen SQLite und MySQL, veralteter Cache nach dem Publizieren, Fehler in der Übersetzungsebene, im Formularversand und im Updater.
3. Öffentliche Ausgabe: Editorcode im öffentlichen Paket, fehlender Hauptinhalt im HTML, Rückschritte bei Barrierefreiheit (fehlende Beschriftungen, Tastaturbedienung, Kontrast), Leistung (LCP, CLS, render-blockierende Ressourcen) und SEO (Canonical, Sitemap, strukturierte Daten).
4. Fehlende Tests für riskante Pfade.
5. Verstöße gegen die Regeln in AGENTS.md, zum Beispiel native Browser-Dialoge.
Keine Stilfragen, keine Vorschläge für Refactorings. Die Skripte `scripts/seed-*.php` und `scripts/reset-*.sh` bauen Referenzwebsites mit absichtlich festen Inhalten auf. Ignoriere `vendor/`, `node_modules/`, `public/assets/build/`, `public/assets/fonts/`, `composer.lock` und `package-lock.json`.
Antworte auf Deutsch. Für jeden Befund: Datei und Zeile, Schwere (hoch, mittel, niedrig), konkretes Fehlerszenario, Vorschlag zur Behebung. Wenn nichts zu melden ist, sag das in einem Satz.
+63
View File
@@ -0,0 +1,63 @@
# Laesst Codex einen Stand pruefen, nur lesend und in einer sauberen Kopie ohne .env.
# Die Datei bleibt reines ASCII, weil Windows PowerShell 5.1 sonst Umlaute falsch liest.
# .\scripts\codex-review.ps1 alles seit dem letzten Release-Tag
# .\scripts\codex-review.ps1 -Seit v1.42.0 alles seit einem Tag oder Commit
# .\scripts\codex-review.ps1 -Commit <sha> einen einzelnen Commit
# .\scripts\codex-review.ps1 -Offen die noch nicht committeten Aenderungen
# .\scripts\codex-review.ps1 -Alles den gesamten Code
param(
[string]$Seit = "",
[string]$Commit = "",
[switch]$Offen,
[switch]$Alles
)
$utf8 = New-Object System.Text.UTF8Encoding $false
$OutputEncoding = $utf8
[Console]::OutputEncoding = $utf8
$repo = (git rev-parse --show-toplevel).Trim()
$regeln = Get-Content -Raw -Encoding UTF8 (Join-Path $repo "scripts\codex-review-prompt.md")
$ziel = if ($Commit) { $Commit } else { "HEAD" }
if ($Alles) {
$umfang = "Scope: the whole code under src/, templates/, public/assets/ (without build/ and fonts/), plugins/, migrations/, config/ and zentrale/."
} elseif ($Commit) {
$umfang = "Scope: the changes from: git show $Commit"
} elseif ($Offen) {
$umfang = "Scope: the uncommitted changes, visible with: git diff HEAD (new untracked files are not included)."
} else {
if (-not $Seit) { $Seit = (git -C $repo describe --tags --abbrev=0 HEAD).Trim() }
if ($LASTEXITCODE -ne 0 -or -not $Seit) { Write-Error "Kein Release-Tag gefunden, bitte -Seit angeben."; exit 1 }
$umfang = "Scope: the changes from: git diff $Seit..HEAD"
}
Write-Host $umfang
$kopie = Join-Path $env:TEMP ("kantsites-review-" + [guid]::NewGuid().ToString("N").Substring(0, 8))
git -C $repo worktree add --quiet --detach $kopie $ziel
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
try {
if ($Offen) {
$patch = Join-Path $env:TEMP ("kantsites-review-" + [guid]::NewGuid().ToString("N").Substring(0, 8) + ".patch")
git -C $repo diff HEAD --binary --output="$patch"
if ((Get-Item $patch).Length -eq 0) { Write-Host "Keine offenen Aenderungen."; Remove-Item $patch; exit 0 }
git -C $kopie apply --whitespace=nowarn "$patch"
$angewendet = $LASTEXITCODE
Remove-Item $patch
if ($angewendet -ne 0) { Write-Error "Offene Aenderungen liessen sich nicht in die Kopie uebernehmen."; exit $angewendet }
}
$berichte = Join-Path $repo "reports"
if (-not (Test-Path $berichte)) { New-Item -ItemType Directory $berichte | Out-Null }
$bericht = Join-Path $berichte ("codex-review-" + (Get-Date -Format "yyyy-MM-dd-HHmm") + ".md")
"$umfang`n`n$regeln" | codex exec --sandbox read-only --ephemeral --cd $kopie --output-last-message $bericht -
$status = $LASTEXITCODE
if (Test-Path $bericht) {
Write-Host "`n===== Ergebnis (auch in $bericht) ====="
Get-Content -Raw -Encoding UTF8 $bericht | Write-Host
}
} finally {
git -C $repo worktree remove --force $kopie
}
exit $status
+27 -13
View File
@@ -494,6 +494,11 @@ $fuss = [
$column('fuss-marke', [
['id' => 'fuss-logo', 'type' => 'html', 'props' => ['html' => $logoSvg('role="img" aria-label="'.$marke.'"')], 'style' => ['desktop' => ['width' => '96px', 'maxWidth' => '96px', 'margin' => $seiten('0px')]], 'css' => $logoCss],
$text('fuss-spruch', 'Ihr neuer Immobilienpartner vor Ort. Ein Lichtblick für Ihre Immobilie.', ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.6, 'color' => '@text', 'maxWidth' => '280px', 'margin' => ['top' => '20px', 'bottom' => '0px']], 'mobile' => ['maxWidth' => 'none']]),
// Adressen aus Einstellungen → Unternehmensdaten → Soziale Profile; ohne Eintrag
// bleibt der Baustein unsichtbar.
['id' => 'fuss-profile', 'type' => 'social', 'props' => ['size' => 20, 'shape' => 'circle', 'layout' => 'horizontal', 'labels' => false, 'networks' => ['instagram', 'facebook']],
'style' => ['desktop' => ['margin' => ['top' => '22px', 'bottom' => '0px']]],
'css' => "& a{color:#141414;border:1px solid #E6DED2;transition:background-color .2s,color .2s,border-color .2s}\n& a:hover{background:#141414;color:#fff;border-color:#141414}"],
]),
$column('fuss-anschrift', [
$kleinzeile('fuss-anschrift-titel', 'Adresse', '@gold-dunkel', '8px'),
@@ -543,6 +548,8 @@ $vorteil = static fn (int $n, string $icon, string $titel, string $satz) => $col
"transition:transform .3s ease,background-color .3s ease\n&:hover{transform:translateY(-4px);background-color:#F2ECE2}");
/** Eine Leistung: Foto, Titel, Satz, aufklappbar „Was dazugehört“. */
// Die vier Teile liegen als Subgrid auf gemeinsamen Zeilen des Rasters: Die Aufklapp-Linien stehen so
// auf einer Höhe, auch bei ungleich langen Sätzen, und ein geöffneter Eintrag verschiebt die anderen nicht.
$leistung = static function (string $id, array $foto, string $alt, string $titel, string $satz, array $punkte) use ($column, $bild, $heading, $text, $nurOben): array {
return $column($id, [
$bild($id.'-bild', $foto, $alt, [
@@ -551,7 +558,7 @@ $leistung = static function (string $id, array $foto, string $alt, string $titel
'mobile' => ['imageHeight' => '260px'],
], null, "overflow:hidden;border-radius:24px\n& img{display:block;width:100%;transition:transform .6s ease}\n&:hover img{transform:scale(1.04)}"),
$heading($id.'-titel', $titel, 3, ['desktop' => ['fontSize' => '24px', 'fontWeight' => '500', 'letterSpacing' => '-0.02em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '6px']]]),
$text($id.'-satz', $satz, ['desktop' => ['fontSize' => '17px', 'lineHeight' => 1.55, 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
$text($id.'-satz', $satz, ['desktop' => ['fontSize' => '17px', 'lineHeight' => 1.55, 'color' => '@text', 'margin' => ['bottom' => '0px']]], 'overflow-wrap:break-word'),
['id' => $id.'-mehr', 'type' => 'accordion', 'props' => ['faq' => false],
'style' => ['desktop' => ['borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'margin' => ['top' => '20px']]],
'children' => [
@@ -564,13 +571,17 @@ $leistung = static function (string $id, array $foto, string $alt, string $titel
],
],
],
]);
], ['tablet' => ['padding' => ['bottom' => '56px']]], 'display:grid;grid-row:span 4;grid-template-rows:subgrid;grid-template-columns:minmax(0,1fr);row-gap:0');
};
/** Ein Schritt des Wechsels: große Ziffer in Gold, Titel, Satz. */
$schritt = static fn (int $n, string $titel, string $satz) => $column('schritt-'.$n, [
$text('schritt-'.$n.'-nummer', '0'.$n, ['desktop' => ['fontSize' => '15px', 'fontWeight' => '500', 'letterSpacing' => '0.08em', 'color' => '@gold-dunkel', 'margin' => ['bottom' => '28px']]],
"display:flex;align-items:center;gap:12px\n&::after{content:'';flex:1;height:1px;background:#E6DED2}"),
/** Ein Schritt des Wechsels: Symbol im weißen Kreis mit Linie zum nächsten Schritt, Titel, Satz. */
$schritt = static fn (int $n, string $icon, string $titel, string $satz) => $column('schritt-'.$n, [
$column('schritt-'.$n.'-kopf', [
$column('schritt-'.$n.'-kreis', [$symbol('schritt-'.$n.'-icon', $icon, 24, ['desktop' => ['color' => '@gold-dunkel']])], ['desktop' => [
'display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'flexShrink' => 0, 'width' => '52px', 'height' => '52px', 'backgroundColor' => '@papier', 'borderRadius' => $rund('50%'),
]]),
], ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'gap' => '12px', 'margin' => ['bottom' => '24px']]],
"&::after{content:'';flex:1;height:1px;background:#E6DED2}"),
$heading('schritt-'.$n.'-titel', $titel, 3, ['desktop' => ['fontSize' => '22px', 'fontWeight' => '500', 'letterSpacing' => '-0.015em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '10px']]]),
$text('schritt-'.$n.'-satz', $satz, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
]);
@@ -660,7 +671,7 @@ $startseite = [
'Marktgerechte Bewertung',
'Persönliche Begleitung bis zum erfolgreichen Verkauf',
]),
$leistung('leistung-weg', $medien['weg'], 'Wohnanlage mit mehreren Gebäuden und grünem Innenhof', 'WEG-Verwaltung', 'Strukturierte, transparente und zuverlässige Verwaltung von Wohnungseigentümergemeinschaften.', [
$leistung('leistung-weg', $medien['weg'], 'Wohnanlage mit mehreren Gebäuden und grünem Innenhof', 'WEG-Verwaltung', "Strukturierte, transparente und zuverlässige Verwaltung von Wohnungseigentümer\u{AD}gemeinschaften.", [
'Eigentümerversammlung vorbereiten, leiten, protokollieren',
'Beschlüsse umsetzen und Beschlusssammlung führen',
'Wirtschaftsplan und Jahresabrechnung',
@@ -682,8 +693,9 @@ $startseite = [
'Übergaben und Wohnungsabnahmen',
]),
], [
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'columnGap' => '28px', 'rowGap' => '56px', 'alignItems' => 'flex-start'],
'tablet' => ['gridColumns' => 2],
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'columnGap' => '28px', 'rowGap' => '0px'],
// Der Abstand zwischen den Reihen sitzt in den Karten, ein Zeilenabstand läge zwischen ihren Teilen.
'tablet' => ['gridColumns' => 2, 'margin' => ['bottom' => '-56px']],
'mobile' => ['gridColumns' => 1],
]),
], $abschnitt('0px', '0px'), 'leistungen', '', 'wide'),
@@ -699,10 +711,10 @@ $startseite = [
'mobile' => ['margin' => ['bottom' => '40px']],
]),
$column('wechsel-raster', [
$schritt(1, 'Anfrage', 'Sie schildern uns kurz Ihr Objekt – online in drei Schritten oder im Gespräch.'),
$schritt(2, 'Kennenlernen', 'Wir sehen uns das Objekt vor Ort an und klären, was Ihnen wichtig ist.'),
$schritt(3, 'Angebot', 'Sie erhalten ein klares Angebot mit allen Leistungen – ohne Kleingedrucktes.'),
$schritt(4, 'Übergabe', 'Wir übernehmen Unterlagen, Konten und Verträge und informieren Mieter und Eigentümer.'),
$schritt(1, 'mail', 'Anfrage','Sie schildern uns kurz Ihr Objekt – online in drei Schritten oder im Gespräch.'),
$schritt(2, 'people', 'Kennenlernen','Wir sehen uns das Objekt vor Ort an und klären, was Ihnen wichtig ist.'),
$schritt(3, 'document', 'Angebot','Sie erhalten ein klares Angebot mit allen Leistungen – ohne Kleingedrucktes.'),
$schritt(4, 'key', 'Übergabe','Wir übernehmen Unterlagen, Konten und Verträge und informieren Mieter und Eigentümer.'),
], [
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '40px'],
'tablet' => ['gridColumns' => 2, 'rowGap' => '48px'],
@@ -1134,6 +1146,8 @@ $settings->save([
'organisationLogoMediaId' => $medien['logo']['id'],
'faviconMediaId' => $medien['favicon']['id'],
'stickyHeader' => false, 'headerOverlay' => false,
// Instagram und Facebook stehen im Fuß; leer bleiben sie unsichtbar.
'social' => ['instagram' => getenv('LUXIMMO_INSTAGRAM') ?: '', 'facebook' => getenv('LUXIMMO_FACEBOOK') ?: ''],
'motion' => 'always',
'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz',
// Die Seite lädt nichts von Dritten; deshalb keine Einwilligungsleiste.