Mantle is a modular plugin with an MVC-ish PHP core and a React admin application. This section describes the boot sequence, the module system, and the conventions that hold it together.
In this section
- Admin UI — routing, layouts, and how views are rendered.
Boot sequence
From plugin load to ready
The ordering is deliberate. initialize_modules() runs first because capabilities must be registered before anything assigns them to roles.
-
Constants and autoloaders
mantle.phpdefines constants, loadsvendor/autoload.php, de-shadows core’s PHP AI Client, then loads the PHP-Scoper-prefixedthird-party/vendor/autoload.php. -
Bootstrap
On
plugins_loaded,mantle_init()instantiatesCore\Bootstrapand callsrun(). If the class is missing it renders an admin notice instead of fataling. -
Modules
Core\Moduleshooksregister_modules()andregister_settings()ontoinit, thenModule_Loader::init()registers schemas for all modules and initialises the enabled ones. -
Controllers
load_controllers()globsincludes/Controller/*.phprecursively and instantiates each concrete class, then callsregister_actions()where present. -
CLI and completion
When
WP_CLIis defined,load_cli_controllers()globsincludes/CLI/. Finallymantle_donefires.
Controller auto-discovery
Bootstrap::load_controllers() is convention-driven rather than a registry. Understanding its filters explains why some classes load and others do not.
Controller discovery rules
Bootstrap::load_controllers().-
Path patternstring -
Files under
includes/Controller/matching/Controller/(.+)\.php, excluding names ending_Interfaceor_Module. -
Skipped subdirectoriesstring -
Any class whose resolved name contains
Agents\,Contracts\,CLI\, orModules\is skipped — those are initialised by their owners instead. -
Conditional skipstring -
GravityForms controllers are skipped unless the
GFFormsclass exists. -
Reflection guardsstring -
Abstract classes and interfaces are skipped via ReflectionClass before instantiation.
-
Failure handlingstring -
Instantiation is wrapped in try/catch; a class that throws is skipped silently.
No fields match this filter.
Module system
Module registration
Three guards make registration idempotent: Module_Loader::init() returns early once initialised, Core\Modules::register_modules() guards on a static flag, and Module_Loader::register() ignores an already-registered ID.
The split between register_schema() and init() is the system's central design decision:
register_schema versus init
-
register_schema()string -
Registers the settings filter and the capabilities filter. Runs for every module regardless of state, so stored settings are never orphaned and the capability reference stays complete.
-
init()string -
Adds hooks, registers REST controllers, and adds the admin submenu. Runs only when
is_enabled()returns true.
No fields match this filter.
Directory layout
-
mantle.php -
includes/-
Core/-
Bootstrap.php -
Modules.php -
Setup.php -
View.php -
LicenseManager.php
-
-
Controller/-
Admin.php -
REST/
-
-
Model/-
Settings.php -
Abilities/ -
Users/
-
-
Modules/-
Abstract_Module.php -
Module_Loader.php -
Module_Interface.php
-
-
Helper/ -
CLI/ -
Shared/Interfaces/
-
-
src/-
constants/ -
components/ -
layouts/ -
modules/ -
slots/ -
stores/ -
views/
-
-
build/ -
third-party/ -
tests/ -
docs/
Configuration fields
-
sitestring required -
WordPress site domain or numeric site ID.
-
statusstring -
Publication status for synchronized Pages.
-
dryRunboolean -
Preview reconciliation without writing changes.
No fields match this filter.
Dependency isolation
Mantle ships two autoloaders. Ordinary dependencies load from vendor/; dependencies likely to collide with other plugins are rewritten by PHP-Scoper into a private namespace under third-party/.
For the AI Client specifically, WordPress 7.0 bundles its own copy in core. When the plugin also has one — for instance because dev dependencies were installed — mantle.php calls setPsr4( 'WordPress\AiClient\', [] ) on the Composer loader to unregister the plugin's prefix, so core's copy is the only one in play.