fix: lit/divi-design-detach nennt Snapshot und diff auch, wenn die erste Seite nach dem Preset-Schritt scheitert; Tests für Sperre, Markup und fehlende design_rev

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 17:15:40 +02:00
co-authored by Claude Opus 5.5
parent 607e0a21ca
commit 5e56cc38f0
2 changed files with 187 additions and 13 deletions
@@ -10,7 +10,10 @@ use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Abilities\ToolResult;
use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Log;
use WP_Error;
defined('ABSPATH') || exit;
@@ -293,6 +296,8 @@ final class DiviDesignDetachAbility implements Ability
* @param array<string, mixed> $input
* @param array<string, mixed> $target
* @return array<string, mixed>|WP_Error
* @throws WriteFailed When the preset step fails, or the first page fails without a preset step.
* @throws DiviLocked Likewise.
*/
private function confirmed(array $input, array $target): array|WP_Error
{
@@ -310,13 +315,24 @@ final class DiviDesignDetachAbility implements Ability
}
$wrotePresets = $presets !== null && $presets['diff'] !== [];
$pages = $this->pages->apply(
$this->name(),
$target['query'],
$target['rewrite'],
$target['post_ids'],
$target['change']
);
try {
$pages = $this->pages->apply(
$this->name(),
$target['query'],
$target['rewrite'],
$target['post_ids'],
$target['change']
);
} catch (WriteFailed | DiviLocked $e) {
// Thrown when the first page fails: the registrar would answer "nothing was saved" – untrue after the
// preset step. Without one, its mapping stays as for every Divi write.
if (! $wrotePresets) {
throw $e;
}
Log::failure($this->name(), $e);
return self::afterPresets(self::firstPageError($e), $presets);
}
if ($pages instanceof WP_Error) {
return $wrotePresets ? self::afterPresets($pages, $presets) : $pages;
}
@@ -341,6 +357,23 @@ final class DiviDesignDetachAbility implements Ability
->toArray();
}
/**
* The client error for a failure on the first page, mapped like AbilityRegistrar::errorFor(), but a clean rollback
* no longer claims that nothing was saved (afterPresets() adds what the preset step changed).
*/
private static function firstPageError(WriteFailed|DiviLocked $e): WP_Error
{
if ($e instanceof WriteFailed && ! $e->rolledBack) {
return $e->toError();
}
$locked = DiviLocked::in($e);
if ($locked !== null) {
return $locked->toError();
}
return $e->withDetails([], state: 'Keine Seite wurde gespeichert.')->toError();
}
/**
* A page error after the preset step wrote: the client learns that the nested references are gone already.
*