You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The composition-first end state ("deployment = base runtime + plugin composition; ALL menus + ALL standard views become content of a pre-installed platform-UI plugin"). Wave 2–4 modularized the compiled packs; this designs the CONTENT lane for the UI shell itself.
Inventory (measured on current main)
Node/mesh menus: a reactive provider pipeline (INodeMenuProvider DI + AddNodeMenuItems delegates per context) with the defaults compiled in DefaultNodeMenuProvider/DefaultMeshMenuProvider; presentation is already editable data (MenuPresentation catalog) but deliberately cosmetic/subtractive only.
Settings tabs: GlobalSettingsMenuItemProvider collections; ~20 compiled Add*SettingsTab calls in the portal composition (the ContentIndex tab already rides its module).
Top-bar AI menu / side panel: compiled AddNodeMenuItems(AiMenuContext…) / ("SidePanel"…).
Home tabs: HomeTab NODES — already pure data; the precedent this design generalizes.
Per-NodeType menu items: plugins already have FULL power via in-mesh AddNodeMenuItems delegates in their NodeTypes' configuration lambdas (plugin-gate CI'd). Not a gap.
The gaps: plugins cannot contribute (a) every-node menu items, (b) settings tabs, (c) shell menus — and the defaults are compiled.
The crux: the security boundary
MenuPresentationOverlay is cosmetic-only for a documented reason: permission filtering happens by providers not emitting an item, so an item conjured from data would carry no permission gate. The mechanism therefore moves enforcement into the compiled aggregator with a CLOSED declarative gate vocabulary:
RequiredPermission — enforced by the aggregator against the live permission stream (per-viewer reactive, exactly like today);
Anything beyond the vocabulary stays in code (in-mesh per-NodeType delegates, or compiled providers). A contribution can only ever NARROW visibility relative to what the aggregator enforces — never widen it.
The contribution node
nodeType UiContribution (lives in the contributing plugin's partition): { Context: Node|Mesh|Settings|AiMenu|SidePanel, Title (localizable per #1626's translations model), Icon, Area, Order, Group, RequiredPermission, Gates{...} }
A contribution only adds a MENU ENTRY pointing at a layout area the plugin already ships — rendering stays the existing layout pipeline; no new render surface.
Read-at-hub-build + generation bump
Aggregators run ONE nodeType:UiContribution snapshot query per hub build (never N live streams per hub — cost control; per-viewer permission reactivity is separate and stays live). An Admin/UiGeneration counter bumps on plugin install/update/uninstall (the installer knows); hubs lazily recycle on generation mismatch — the existing recycle machinery.
The PlatformUI pack
A preInstalled plugin carries the DEFAULT item set (the Edit/Pin/Move/Copy/Delete/Files/Data/Versions/StopSync/Recycle table, the mesh menu, the standard settings tabs) as UiContribution nodes. The compiled defaults retire to a MINIMAL fallback (Data + Versions + Delete — enough to operate a zero-plugin mesh; the fallback-slot philosophy). Phase 2 moves standard AREAS into the pack's in-mesh source where feasible.
A malicious/broken pack flooding menus — contributions are per-partition, so the existing install/grant model bounds who can contribute; the aggregator caps per-context contributions and logs the source partition per item.
Query cost at hub build — one indexed snapshot query; measure on the batch-bake-scale meshes before step 4.
Per the standing WS7 directive this design leads; implementation starts with step 1 + the settings-tab slice.
The composition-first end state ("deployment = base runtime + plugin composition; ALL menus + ALL standard views become content of a pre-installed platform-UI plugin"). Wave 2–4 modularized the compiled packs; this designs the CONTENT lane for the UI shell itself.
Inventory (measured on current main)
INodeMenuProviderDI +AddNodeMenuItemsdelegates per context) with the defaults compiled inDefaultNodeMenuProvider/DefaultMeshMenuProvider; presentation is already editable data (MenuPresentationcatalog) but deliberately cosmetic/subtractive only.GlobalSettingsMenuItemProvidercollections; ~20 compiledAdd*SettingsTabcalls in the portal composition (the ContentIndex tab already rides its module).AddNodeMenuItems(AiMenuContext…)/("SidePanel"…).HomeTabNODES — already pure data; the precedent this design generalizes.AddNodeMenuItemsdelegates in their NodeTypes' configuration lambdas (plugin-gate CI'd). Not a gap.The crux: the security boundary
MenuPresentationOverlayis cosmetic-only for a documented reason: permission filtering happens by providers not emitting an item, so an item conjured from data would carry no permission gate. The mechanism therefore moves enforcement into the compiled aggregator with a CLOSED declarative gate vocabulary:RequiredPermission— enforced by the aggregator against the live permission stream (per-viewer reactive, exactly like today);nodeTypes(suffix-awareMatchessemantics),syncedOnly,excludePartitionRoot,excludeViewerHome,adminOnly(IsGlobalAdmin).Anything beyond the vocabulary stays in code (in-mesh per-NodeType delegates, or compiled providers). A contribution can only ever NARROW visibility relative to what the aggregator enforces — never widen it.
The contribution node
nodeType
UiContribution(lives in the contributing plugin's partition):{ Context: Node|Mesh|Settings|AiMenu|SidePanel, Title (localizable per #1626's translations model), Icon, Area, Order, Group, RequiredPermission, Gates{...} }A contribution only adds a MENU ENTRY pointing at a layout area the plugin already ships — rendering stays the existing layout pipeline; no new render surface.
Read-at-hub-build + generation bump
Aggregators run ONE
nodeType:UiContributionsnapshot query per hub build (never N live streams per hub — cost control; per-viewer permission reactivity is separate and stays live). AnAdmin/UiGenerationcounter bumps on plugin install/update/uninstall (the installer knows); hubs lazily recycle on generation mismatch — the existing recycle machinery.The PlatformUI pack
A
preInstalledplugin carries the DEFAULT item set (the Edit/Pin/Move/Copy/Delete/Files/Data/Versions/StopSync/Recycle table, the mesh menu, the standard settings tabs) asUiContributionnodes. The compiled defaults retire to a MINIMAL fallback (Data + Versions + Delete — enough to operate a zero-plugin mesh; the fallback-slot philosophy). Phase 2 moves standard AREAS into the pack's in-mesh source where feasible.Migration order (each shippable)
UiContributiontype + gated aggregation + generation bump — additive, nothing moves.Risks
Titlemust ride the same translations model Skills and agents must be localized; a plugin's skill/agent paths install with the plugin #1626 defines for skills/agents — one localization story, not two.Per the standing WS7 directive this design leads; implementation starts with step 1 + the settings-tab slice.