From 7590da4d964f5857d7066a631bfb5666122115af Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 27 Sep 2026 22:57:45 +0200 Subject: [PATCH] fix: layout-edit dry runs name only editable live templates (ruling Q22) ThemeBuilderEdits::showing() splits the live templates that show a layout into editable ones (named under templates) and a hidden_templates count; confirm is still decided on all of them, so a layout shown only by uneditable templates still needs confirm. The live split is read once per write for the confirm check and the field warnings, and field warnings now also survive a non-confirm refusal such as the P9 backup refusal (M-12 a). replace-text passes hidden_templates on; the write schema lists it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7 --- docs/guides/theme-builder.md | 6 +- ...6-09-27-etappe-3-entscheidungen-entwurf.md | 4 +- .../Divi/Pages/DiviReplaceTextAbility.php | 2 +- .../Divi/Pages/DiviWriteAbility.php | 4 +- src/Integrations/Divi/Pages/DiviWriter.php | 35 ++++-- .../Divi/ThemeBuilder/ThemeBuilderEdits.php | 100 ++++++++++++++---- .../Divi/Pages/DiviWriterThemeBuilderTest.php | 52 +++++++++ .../ThemeBuilder/ThemeBuilderEditsTest.php | 54 +++++++++- 8 files changed, 221 insertions(+), 36 deletions(-) diff --git a/docs/guides/theme-builder.md b/docs/guides/theme-builder.md index f6475bf..1e7e5f4 100644 --- a/docs/guides/theme-builder.md +++ b/docs/guides/theme-builder.md @@ -13,7 +13,8 @@ Kopf- und Fußzeile heißen „global“, weil andere Vorlagen sie mitbenutzen. Nutzer fragen → dieselbe Anfrage mit `confirm: true`. - Auch alles andere, was Besucher sofort sähen, ist zuerst ein Probelauf (`confirm_required`) und braucht `confirm: true`: Bedingungen einer aktiven Vorlage ändern (`lit/tb-assign`) und ein Layout ändern, das eine - aktive Vorlage zeigt (die `lit-divi-…`-Werkzeuge; die Antwort nennt die Vorlagen unter `templates`). Vor dem + aktive Vorlage zeigt (die `lit-divi-…`-Werkzeuge; die Antwort nennt die Vorlagen unter `templates`, solche + ohne Berechtigung nur als Anzahl `hidden_templates`). Vor dem Speichern eines sichtbaren Layouts entsteht eine Revision; sind Revisionen abgeschaltet, eine Sicherungskopie im Papierkorb (`backup_id`). - `lit/divi-replace-text` ändert sichtbare Layouts nicht; solche Änderungen mit `lit-divi-update` und @@ -85,7 +86,8 @@ bleiben die Felder ungeprüft (die Antwort sagt das). Kopf- und Fußzeile werden - Divi → Rollen-Editor: „Theme Builder“ – standardmäßig nur für Administratoren. - Fehlt eines davon, gibt es für dieses Konto keine Vorlagen und Layouts (`not_found` wie bei einer unbekannten ID bzw. `forbidden`). Vorlagen, die das Konto nicht bearbeiten darf, erscheinen nirgends; aktive davon nur als - Anzahl (`hidden_live` in `lit/tb-list`, `hidden_overlaps` im Probelauf). + Anzahl (`hidden_live` in `lit/tb-list`, `hidden_overlaps` im Probelauf, `hidden_templates` beim Ändern eines + Layouts). - Einstellungen → LIT MCP → Tools: Gruppe „Divi Theme Builder“ auf „nur lesen“ sperrt jedes Schreiben, auch über die `lit-divi-…`-Werkzeuge; deaktiviert gibt es keine Vorlagen und Layouts. diff --git a/docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md b/docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md index 11feafb..e1ac5c6 100644 --- a/docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md +++ b/docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md @@ -242,8 +242,8 @@ Rulings Q1–Q25: `.superpowers/sdd/2026-09-27-etappe-3b-theme-builder/progress. - E3 für den Theme Builder: `edit_others_posts`, `edit_theme_options` (Q15) und Divis Rollen-Recht „Theme Builder“, je Vorlage/Layout `edit_post`, zum Anlegen die Veröffentlichen-Rechte. Nicht bearbeitbare Vorlagen antworten wie fehlende (Q18); aktive davon nur als Anzahl in Liste und Probeläufen - von `lit/tb-assign`/`lit/tb-activate` (Q21; für die Probeläufe der Layout-Änderungen folgt Q22 in der - Schlussrunde). + von `lit/tb-assign`/`lit/tb-activate` (Q21) und der Layout-Änderungen (`hidden_templates`; die Rückfrage + gilt trotzdem für alle aktiven Vorlagen, Q22). - Bedingungen nur aus Divis Katalog; Objekte lesbar (Begriffe: `assign_term`); strenger als Divi: Autoren-Bedingungen (`archive:user:id:`) nur mit `list_users` (Q10). - E9: eigene Gruppe „Divi Theme Builder“ (`divi-theme-builder`); alle neuen Tools indirekt, 22 direkte Tools. diff --git a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php index 7edb026..4d69fab 100644 --- a/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php +++ b/src/Integrations/Divi/Pages/DiviReplaceTextAbility.php @@ -406,7 +406,7 @@ final class DiviReplaceTextAbility implements Ability 'error' => Errors::clientMessage($error) . ' Sichtbare Theme-Builder-Layouts nicht mit ' . 'lit/divi-replace-text, sondern nach Rückfrage mit lit-divi-update (confirm: true) ändern.', 'templates' => $data['templates'], - ]; + ] + (isset($data['hidden_templates']) ? ['hidden_templates' => $data['hidden_templates']] : []); } return [ diff --git a/src/Integrations/Divi/Pages/DiviWriteAbility.php b/src/Integrations/Divi/Pages/DiviWriteAbility.php index db031ca..22f331f 100644 --- a/src/Integrations/Divi/Pages/DiviWriteAbility.php +++ b/src/Integrations/Divi/Pages/DiviWriteAbility.php @@ -41,8 +41,10 @@ abstract class DiviWriteAbility implements Ability 'affected_count' => ['type' => 'integer'], 'hidden_count' => ['type' => 'integer'], 'backup_id' => ['type' => 'integer'], - // Theme Builder layouts: the enabled templates that show the layout. + // Theme Builder layouts: the enabled templates that show the layout – those the user may edit; the + // others only as a count (ruling Q22). 'templates' => ['type' => 'array', 'items' => ['type' => 'object']], + 'hidden_templates' => ['type' => 'integer'], ], ['tree_rev']); } diff --git a/src/Integrations/Divi/Pages/DiviWriter.php b/src/Integrations/Divi/Pages/DiviWriter.php index c3ca304..09850db 100644 --- a/src/Integrations/Divi/Pages/DiviWriter.php +++ b/src/Integrations/Divi/Pages/DiviWriter.php @@ -218,22 +218,45 @@ final class DiviWriter return $this->save($ability, $page, $change, $input, SaveScope::ThemeBuilderLayout); } - // E12: fields in a body checked against the templates' post types – warnings only, also in the dry run. - $fieldWarnings = $this->themeBuilder->fieldWarnings($page, $change); + // E12: fields in a body checked against the templates' post types – warnings only, also in the dry run + // and in any refusal (review M-12 a). The live templates are read once for both checks. + $live = $this->themeBuilder->live(); + $fieldWarnings = $this->themeBuilder->fieldWarnings($page, $change, $live); foreach ($fieldWarnings as $warning) { $change->warning($warning); } - $prepared = $this->themeBuilder->prepare($page, $input); + $prepared = $this->themeBuilder->prepare($page, $input, $live); if ($prepared instanceof WP_Error) { - return $fieldWarnings !== [] && Errors::codeOf($prepared) === 'confirm_required' - ? VisibleEdits::merged($prepared, 'Hinweise zu Feldern siehe warnings.', ['warnings' => $fieldWarnings]) - : $prepared; + return $fieldWarnings === [] ? $prepared : self::withWarnings($prepared, $fieldWarnings); } return $this->savePrepared($ability, $page, $change, $input, $prepared, 'des Theme-Builder-Layouts'); } + /** + * $error with the field warnings of the change (key warnings): a confirm_required also says so in its message. + * + * @param list $warnings + */ + private static function withWarnings(WP_Error $error, array $warnings): WP_Error + { + if (Errors::codeOf($error) === 'confirm_required') { + return VisibleEdits::merged($error, 'Hinweise zu Feldern siehe warnings.', ['warnings' => $warnings]); + } + + $data = $error->get_error_data(); + $data = is_array($data) ? $data : []; + + return Errors::make( + Errors::codeOf($error), + Errors::clientMessage($error), + (string) ($data['hint'] ?? ''), + (int) ($data['status'] ?? 400), + array_merge(array_diff_key($data, ['status' => true, 'hint' => true]), ['warnings' => $warnings]) + ); + } + /** * save() with the scope GlobalEdits/ThemeBuilderEdits prepared; their details join the result, and a * failure after their backup names it (ruling P14, VisibleEdits). $entry names the entry in that error. diff --git a/src/Integrations/Divi/ThemeBuilder/ThemeBuilderEdits.php b/src/Integrations/Divi/ThemeBuilder/ThemeBuilderEdits.php index f6d967d..3eb0d14 100644 --- a/src/Integrations/Divi/ThemeBuilder/ThemeBuilderEdits.php +++ b/src/Integrations/Divi/ThemeBuilder/ThemeBuilderEdits.php @@ -21,8 +21,10 @@ defined('ABSPATH') || exit; * ausdrücklicher Freigabe"): Divi reads TB layouts live (F22), so a layout that a live template (attached to the * live Theme Builder and enabled, whatever its post status – ruling Q8) shows changes the website at once – * without confirm: true nothing is saved and the answer lists those templates (dry run of the fully validated - * change, like global modules, answer F2; ruling Q9). A confirmed edit gets the restore point of every visible - * change (VisibleEdits, ruling Q2): a backup when revisions are off, refused when it could not be faithful (P9). + * change, like global modules, answer F2; ruling Q9) – by name only those the user may edit, the others as the + * count hidden_templates; confirm is decided on all of them (ruling Q22). A confirmed edit gets the restore point + * of every visible change (VisibleEdits, ruling Q2): a backup when revisions are off, refused when it could not be + * faithful (P9). * Layouts of disabled or detached templates save at once. The save scope sets the Divi flags and clears every * page's CSS and the Theme Builder cache. * @@ -39,28 +41,49 @@ final class ThemeBuilderEdits ) { } - /** @return list