Extension points let a plugin add a component or a standard control to a location owned by Sero. The host owns the location, layout, lifecycle, and validation. A plugin cannot invent a new host surface.
These manifest concepts are separate:
| Manifest field | Owner | Purpose |
|---|---|---|
sero.app.component | Plugin | The plugin's main app surface. |
sero.app.contributes.components | Plugin | Extra federated React components mounted in host-defined locations. |
sero.app.contributes.controls | Host | Standard controls rendered by Sero and backed by a plugin action. |
Every contribution needs an id that is unique inside its extension point.
The same ID can appear in two different extension points. Sero combines it
with the app ID as <app-id>:<contribution-id>, which identifies the
contribution inside one extension point only.
| Extension point | Kind | Point-specific fields | Host composition |
|---|---|---|---|
ui.global-search.panel | Component | description? | One panel or tabbed panels |
ui.explorer.view | Component | label?, icon? | Explorer activity item and main view |
ui.titlebar.control | Component | None | Inline title-bar controls |
ui.dashboard.widget | Component | name, defaultSize?, minSize?, maxSize?, description? | Dashboard grid |
workspace.create.option | Control | switch control and tool action | Create New Workspace form |
All component entries require id, extensionPoint, and component. The
component value identifies a Module Federation exposed-module key. For
example, "KnowledgeSearch" maps to the "./KnowledgeSearch" key in the
plugin's exposes configuration. The source module must have a default React
component export. Dashboard sizes default to 2 × 2 when omitted.
This app has one main component and contributes two additional components:
Each contributed component is wrapped in the standard app runtime context and plugin style scope. The host can unmount it when the surface closes or changes. Keep state that must survive an unmount in plugin-owned state.
Controls are data, not arbitrary React components. The first supported control
is switch. The first supported action invokes an app-local extension tool.
After Sero creates the workspace, it adds workspaceId, workspaceName, and
workspacePath to the action arguments. Host values override static params.
A failed optional action does not undo a workspace that was created
successfully. Declare appAgent.invokeTool in requiredHostCapabilities when
the plugin depends on this action bridge.
Electron validates all contribution data before the renderer receives it:
components and controls must be arrays when presentAn unknown or malformed optional entry is ignored and recorded as a diagnostic. It does not disable unrelated plugin features. Federated components are still subject to the plugin runtime ABI check.
Extension points are optional by default. If the plugin cannot work without a
point introduced by a newer Sero release, set an appropriate
sero.plugin.minSeroVersion.
Electron still normalises these old fields for installed external plugins:
| Compatibility field | Canonical extension point |
|---|---|
search | ui.global-search.panel |
explorerView | ui.explorer.view |
titlebar | ui.titlebar.control |
widgets[] | ui.dashboard.widget |
workspaceCreation | workspace.create.option |
Explicit canonical entries take precedence so a plugin can carry both forms
during migration without rendering duplicates. New plugins must use
sero.app.contributes.
A static widget is a federated component contribution to
ui.dashboard.widget. Use this when the widget is always available with the
plugin.
useWidgetRegistration() is a separate app-runtime API. Use it when widget
availability is decided at runtime. Its registration is sticky for the current
renderer session, even if the component that called the hook unmounts. For
manual lifecycle control, registerWidget() returns an unregister function.
Both paths use the same host-owned widget chrome and dashboard components.