AdminShell
The panel's shell, composed — not hand-written — from Milpa Components. `<milpa:dashboard-shell>` holds the panel's own sidebar (`admin-sidebar`: one group per distinct section group — ADMIN, APP, AGENT, then any other — each item with its glyph), a topbar (the active section's title, and the chips: who signed in when a gate authenticated the request, the gate in effect, the locale) and a main region carrying the section header (`admin-section-header`: the title and «declared by <Plugin>», read from the catalogue) above the active section's component, wrapped in the section body (`.admin-section__body` — the shell's, not the section's: the part of main that scrolls, and the box a section fills when it wants the remaining height), all compiled by `XhtmlComponentCompiler` over the {@see ComponentBook}. Two lifecycle pairs make it extensible without touching it: `admin.section.before_render`/`after_render` (the section's props, then its HTML) and `admin.shell.before_render`/`after_render` (the composition and items, then the HTML) — declared as data by {@see self::events()}, so the dispatcher can count them (greenhouse decisions/0228). What every section receives (greenhouse decisions/0210): the {@see ComponentContext} it mounts with carries the `principal` the gate authenticated — the same actor id the topbar shows, null when nobody — the `locale` the page answers in and the panel's `route`, so a section decides its own state by what the host knows and agrees with the topbar; and the request's query params reach it as `props['query']` (decisions/0205) — a drill-down, a filter — without the shell knowing what they mean. A section may also declare a whole {@see DeclaredView} instead of one component (greenhouse decisions/0211): then the shell compiles the guest's tree in the same place, ROOT BY ROOT, so a component that throws while mounting paints its failure inside its own region and the panel around it stands ({@see ViewMarkup}); it collects what every rendered renderer DECLARED, so the document emits each stylesheet and module once through `LiveBoot`; and it merges the view's signal seeds with the panel's own ({@see LiveSeeds}). Host facts a guest needs but no contract carries travel in `ComponentContext::$meta`: {@see self::META_GATE}, {@see self::META_SECTION}, {@see self::META_QUERY}. {@see self::compose()} returns all three (HTML, assets, seeds); {@see self::render()} is the same call when only the HTML is wanted.
AdminShell::__construct()
public function __construct(Milpa\Admin\AdminSettings $settings, Milpa\Admin\I18n\Catalog $catalog, Milpa\Live\Contracts\Transport\StateTransferCodecInterface $codec, ?Milpa\Interfaces\Event\MilpaEventDispatcherInterface $events = null, Milpa\Interfaces\Di\DIContainerInterface|string $rootOrContainer = ''):Parameters
| Name | Type | Description |
|---|---|---|
| $settings | Milpa\Admin\AdminSettings | |
| $catalog | Milpa\Admin\I18n\Catalog | |
| $codec | Milpa\Live\Contracts\Transport\StateTransferCodecInterface | |
| $events | ?Milpa\Interfaces\Event\MilpaEventDispatcherInterface | |
| $rootOrContainer | Milpa\Interfaces\Di\DIContainerInterface|string |
AdminShell::events()
public static function events(): arrayWhat this shell dispatches, as data — one declaration per name, built from the SAME constants its `dispatch()` calls use, so a renamed event cannot leave a stale declaration behind (greenhouse decisions/0228). Every subject is a mutable object the subscriber is meant to change; none carries an interception slot. {@see \Milpa\Admin\Event\AdminEvents} hands these to the dispatcher at boot.
AdminShell::withCatalog()
public function withCatalog(Milpa\Admin\I18n\Catalog $catalog): selfThe same shell answering in another catalog — a request's `?lang=` — with everything else shared.
Parameters
| Name | Type | Description |
|---|---|---|
| $catalog | Milpa\Admin\I18n\Catalog |
AdminShell::render()
public function render(Milpa\Admin\Section\SectionCatalogue $catalogue, Milpa\Admin\Section\AdminSection $active, array $query = [], ?string $principal = null): stringRenders the shell with every discovered section in the sidebar, under its group, and the active one in main under its header.
Parameters
| Name | Type | Description |
|---|---|---|
| $catalogue | Milpa\Admin\Section\SectionCatalogue | |
| $active | Milpa\Admin\Section\AdminSection | |
| $query | array<string, mixed> | the request's query params, handed to the active section as `props['query']` |
| $principal | (string | null) | who the gate let in — the authenticated actor's id, never a session id — or null when nobody is signed in; handed to every component's `ComponentContext`, the active section's included |
AdminShell::compose()
public function compose(Milpa\Admin\Section\SectionCatalogue $catalogue, Milpa\Admin\Section\AdminSection $active, array $query = [], ?string $principal = null, ?array $scopes = null): Milpa\Admin\View\ShellOutputThe whole composed page: the shell's HTML, the client files every rendered component declared, and the seeds the panel and the active section's view agreed on — what {@see AdminPage} needs to emit ONE runtime for the page (greenhouse decisions/0211).
Parameters
| Name | Type | Description |
|---|---|---|
| $catalogue | Milpa\Admin\Section\SectionCatalogue | |
| $active | Milpa\Admin\Section\AdminSection | |
| $query | array<string, mixed> | the request's query params, handed to the active section as `props['query']` and to every node as `meta['query']` |
| $principal | (string | null) | who the gate let in — the authenticated actor's id, never a session id — or null when nobody is signed in |
| $scopes | (list<string> | null) | the authenticated actor's declared scopes, without permission expansion |
AdminShell::renderEmpty()
public function renderEmpty(Milpa\Admin\Section\SectionCatalogue $catalogue, ?string $principal = null): stringThe empty-state body when no plugin declared a section — still inside the shell, chips included.
Parameters
| Name | Type | Description |
|---|---|---|
| $catalogue | Milpa\Admin\Section\SectionCatalogue | |
| $principal | (string | null) | who the gate let in, or null when nobody is signed in |
AdminShell::composeEmpty()
public function composeEmpty(Milpa\Admin\Section\SectionCatalogue $catalogue, ?string $principal = null): Milpa\Admin\View\ShellOutputThe empty state, composed like any other page — its assets (the primitives declare none today) and the panel's own seeds, so a panel with no section still emits exactly one runtime.
Parameters
| Name | Type | Description |
|---|---|---|
| $catalogue | Milpa\Admin\Section\SectionCatalogue | |
| $principal | (string | null) | who the gate let in, or null when nobody is signed in |
AdminShell::title()
public function title(Milpa\Admin\Section\AdminSection $section): stringThe title the panel shows for a section: a catalog key when it knows it, the literal otherwise.
Parameters
| Name | Type | Description |
|---|---|---|
| $section | Milpa\Admin\Section\AdminSection |