feat: lit-site-status, Admin-Tab und Admin-Hinweis zeigen Divi-Fähigkeiten mit Zustand, Quelle, Problemen und betroffenen Werkzeugen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 22:29:38 +02:00
co-authored by Claude Sonnet 5.5
parent b92545a8fe
commit 2aef6e1154
13 changed files with 831 additions and 31 deletions
@@ -6,31 +6,47 @@ namespace LIT\MCP\Integrations\Divi;
use LIT\MCP\Integrations\Core\EnvironmentComponent;
use LIT\MCP\Integrations\Divi\Release\DiviEnvironment;
use LIT\MCP\Integrations\Divi\Release\FeatureOverview;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
defined('ABSPATH') || exit;
/**
* Divi in lit-site-status: version, the internals that are missing (DiviCompatibility) and the release status
* (spec §5.5); inactive without problems and release when Divi is not the template.
* Divi in lit-site-status: version, missing internals (DiviCompatibility), the release status and – per feature –
* state, source, problems and the tools it affects (E3). The features come from the matrix the release gate
* already holds (cached, D7), so a site-status call never recomputes the mirror fingerprints.
*/
final class DiviEnvironmentComponent implements EnvironmentComponent
{
public function __construct(private ?DiviCompatibility $compatibility = null)
private FeatureOverview $overview;
public function __construct(private ?DiviCompatibility $compatibility = null, ?FeatureOverview $overview = null)
{
$this->overview = $overview ?? FeatureOverview::withoutTools();
}
/**
* @return array{id: string, active: bool, version: ?string, problems: list<string>, release: ?array<string, mixed>,
* features: list<array{id: string, label: string, state: string, source: ?string, problems: list<string>,
* tools: list<string>}>}
*/
public function describe(): array
{
$version = DiviEnvironment::version();
$active = $version !== null;
if ($version === null) {
return ['id' => 'divi', 'active' => false, 'version' => null, 'problems' => [], 'release' => null,
'features' => []];
}
$status = ReleaseGate::status();
return [
'id' => 'divi',
'active' => $active,
'active' => true,
'version' => $version,
'problems' => $active ? ($this->compatibility ?? new DiviCompatibility())->problems() : [],
'release' => $active ? ReleaseGate::status()->toArray() : null,
'problems' => ($this->compatibility ?? new DiviCompatibility())->problems(),
'release' => $status->toArray(),
'features' => $this->overview->rows($status->matrix),
];
}
}
+18 -4
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi;
use Closure;
use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\ToolAvailability;
use LIT\MCP\Admin\DiviTab;
use LIT\MCP\Admin\SettingsPage;
@@ -55,6 +56,7 @@ use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
use LIT\MCP\Integrations\Divi\Release\CompatibilityCheck;
use LIT\MCP\Integrations\Divi\Release\DiviCheckRoute;
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
use LIT\MCP\Integrations\Divi\Release\FeatureOverview;
use LIT\MCP\Integrations\Divi\Schema\DiviCatalogAbility;
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
@@ -95,11 +97,14 @@ final class DiviIntegration implements Integration, Bootable
* "Divi Theme Builder" gate their entries in the Divi tools (ruling P9, E9), lit/divi-insert gates
* lit/divi-node-copy, lit-library-insert and content-create's divi input (audit M2), lit/library-insert
* gates lit/divi-node-copy from a library entry (audit L5). null = no Tools tab: everything allowed.
* @param (Closure(): list<Ability>)|null $allAbilities The abilities of every integration (Plugin passes
* IntegrationRegistry::abilities()); the tools a Divi feature affects are found among them (E3).
*/
public function __construct(
private ContentSchema $schema,
private ChangeLog $log,
private ?ToolAvailability $tools = null
private ?ToolAvailability $tools = null,
private ?Closure $allAbilities = null
) {
$this->modules = new DiviModules();
$this->design = new DesignSystemReader();
@@ -177,10 +182,16 @@ final class DiviIntegration implements Integration, Bootable
return [new DiviLogoUsage()];
}
/** Port for lit-site-status: Divi's version, missing internals and release status. */
/** Which tools each Divi feature affects – for lit-site-status and the Divi tab (E3). */
public function featureOverview(): FeatureOverview
{
return new FeatureOverview($this->allAbilities ?? static fn (): array => []);
}
/** Port for lit-site-status: Divi's version, missing internals, release status and features. */
public function environmentComponent(): EnvironmentComponent
{
return new DiviEnvironmentComponent();
return new DiviEnvironmentComponent(overview: $this->featureOverview());
}
/**
@@ -210,7 +221,10 @@ final class DiviIntegration implements Integration, Bootable
(new DiviCheckRoute($check))->register();
DiviCheckCommand::register($check);
add_filter(SettingsPage::TABS_FILTER, fn (array $tabs): array => [...$tabs, new DiviTab(log: $this->log)]);
add_filter(
SettingsPage::TABS_FILTER,
fn (array $tabs): array => [...$tabs, new DiviTab(log: $this->log, overview: $this->featureOverview())]
);
}
public function abilities(): array
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use Closure;
use LIT\MCP\Abilities\Ability;
defined('ABSPATH') || exit;
/**
* The per-feature view shared by lit-site-status and the admin tab (E3): each row of the compatibility matrix plus
* the tools that need the feature. Reads the matrix the release gate already holds (cached, D7) – nothing is
* checked again here.
*/
final class FeatureOverview
{
/** @param Closure(): list<Ability> $abilities All registered abilities (IntegrationRegistry::abilities()). */
public function __construct(private Closure $abilities)
{
}
/** An overview that knows no tools (tests, and a Divi tab built without a registry). */
public static function withoutTools(): self
{
return new self(static fn (): array => []);
}
/**
* Tools whose diviFeatures([]) contain the feature, named as the MCP client sees them: direct tools
* lit-<name>, indirect ones lit/<name>. A tool that needs a feature only for certain input (content-create
* with divi) is not listed – its refusal names the feature.
*
* @return array<string, list<string>> Feature ID => tool names.
*/
public function toolsByFeature(): array
{
$map = [];
foreach (($this->abilities)() as $ability) {
if (! $ability instanceof RequiresDiviFeatures) {
continue;
}
foreach ($ability->diviFeatures([]) as $feature) {
$map[$feature->value][] = $ability->isDirect()
? str_replace('/', '-', $ability->name())
: $ability->name();
}
}
return $map;
}
/**
* @return list<array{id: string, label: string, state: string, source: ?string, problems: list<string>,
* tools: list<string>}>
*/
public function rows(CompatibilityMatrix $matrix): array
{
$tools = $this->toolsByFeature();
return array_map(
static fn (array $row): array => $row + ['tools' => $tools[$row['id']] ?? []],
$matrix->toArray()
);
}
}