Plugin defines a plugin: its metadata and the modules it is made of. A plugin does nothing by itself; its modules
contribute everything it adds to the app.
A module declares the capabilities it provides and requires, and optionally the event it activatesOn. The plugin
manager activates a module when its event fires and every capability it requires has been contributed. A module with no
activatesOn activates when the app is idle after startup.
Helper to define an eager module body with the same spec-carrying shape as
lazyModule
— for bodies that are plain values (translations, schema) or too small
to justify a chunk, so plugin definitions stay a uniform chain of spec-carrying modules.
@param ― name The module name — used to auto-compute module IDs.
Capability.inlineModule keeps the body in the same file. Capability.lazyModule loads it from another chunk when the
module activates, so a plugin with many modules costs little at startup.
Helper to define a lazily loaded module body with an eager requires/provides spec.
The spec is available without importing the chunk (dependency ordering happens before
code-splitting resolves) and the loaded default export is type-checked against it. The
returned
Module
is opaque with respect to requires/provides/props, parameterized
only by Options — this is what eliminates TS2883 at every call site.
The lazy pairing of
makeModule
.
@param ― name The export name (e.g., 'AppGraphBuilder') - used to auto-compute module IDs.
@param ― spec The requires/provides declaration, matching the module authoring site.
Activates when this runtime event fires instead of during the dependency pass.
activatesOn:
import ActivationEvents
ActivationEvents.
const Idle: ActivationEvent
Fired once by the host at main-thread idle after the app is interactive.
For REGISTRATION contributions only — graph builders, operation handler sets, settings: the
app-wide indexes a user can reach from anywhere (the navtree, the operation registry, a
settings read) and which therefore no single surface can gate. A feature's own modules ride
its plugin start event, fired when its surface renders; putting a feature body here instead
is what turned the former DeferredStartup into a second startup pass.
This is also what omitting activatesOn normalizes to, so a module that must run at boot has
to say activatesOn: ActivationEvents.Startup explicitly. Defined in core; see
Helper to define a capability module with explicit typing.
Wraps the default export function to provide better type inference and make the pattern explicit.
This helper provides explicit typing for the module activation function,
making it clear that the function should:
Access declared requires via yield* (or the ambient Capability.Service/Plugin.Service)
Adds a module to a plugin builder. Supports both pipeline and direct call styles.
Accepts either:
A spec-carrying module (
Capability.lazyModule
/
Capability.inlineModule
):
requires/provides/activatesOn/props all come from the module's own spec (declared where
it is authored), so the call needs only an optional id override.
An inline authoring record (or a function receiving plugin options): declares
provides/requires (dependency mode) or activatesOn + requires/provides
(runtime-event mode); the activate effect's environment is constrained to the declared
requires and its return must cover the declared provides.
Creates a Plugin from a builder.
Supports both pipeline and direct call styles.
Always returns a factory function (options: T) => Plugin.
When T is void, the function takes no arguments: () => Plugin.
A module body returns the contributions it declared in provides. The compiler checks the match: a missing or
undeclared contribution is a type error at Plugin.addModule. Capability covers
declaring and requesting capabilities.
A plugin that is published describes itself in a dx.config.ts manifest. Plugin.config takes the metadata, the
registry’s fields such as tags, and the modules, each made with a maker from AppCapability that names the file to
load. It returns meta and make, so the registry reads the plugin without importing its modules.
Author or organization name. Only used for bundled plugins. For plugins published to the
registry this field is ignored — the verified publisher (handle ?? did) is used instead.
author: 'Example',
17
description?: string | undefined
description:
18
'A reading list: save links with a note, and mark them as read.',
19
icon?: {
readonly key: string;
readonly hue?: string | undefined;
} | undefined
icon: {
key: string
key: 'ph--tray-arrow-down--regular',
hue?: string | undefined
hue: 'indigo' },
20
tags?: readonly string[] | undefined
tags: ['labs'],
21
},
22
modules: readonly (Module<any> | undefined)[]
Module specs only; each body is a dynamic import, so reading the config loads no plugin code.
Module contributing schemas. Prefer the loader form — schema objects ride whatever barrels
declare them, so an inline list drags those barrels into the plugin definition's static
closure (the boot evaluation floor); a loader keeps them in the module body chunk.
Module maker contributing operation handlers. Handler sets register eagerly by default: a
keyed set is a definition→loader map with a thin closure (definitions carry no
implementations), so the operation registry is complete at boot while each handler's BODY
still loads per invocation. The deferral axis is the operation, not the module.
A feature plugin whose operations cannot be invoked before the app is interactive declares
activatesOn: ActivationEvents.Idle to register in that wave instead of on the startup pass;
the boot path (deck, layout, space, client) invokes operations during startup and stays eager.
Stated explicitly rather than inherited: omitting activatesOn now normalizes to Idle, which
would leave the registry incomplete for exactly those boot-path invocations.
Module maker contributing a typed create-object entry. Gated by default on the create flow
opening (all consumers read the entries reactively); declare activatesOn to override.
Module maker contributing app-graph node builders. Gated by default on the host's idle event
rather than the contributing plugin's own start: a builder is what puts a plugin's items in
the navtree, so gating it on the plugin's own start (fired when its surface renders) is a
deadlock — the item never appears, so it can never be opened. Declare activatesOn to override.
Module maker contributing React surfaces. Declaring roles (the role tokens the module's
surfaces bind, e.g. AppSurface.Article or a plugin's <Plugin>Surface tokens) gates the module
on those roles' demand events
(
ActivationEvents.SurfacesRequested
) — it loads when a Surface for one of its roles
first renders instead of at startup. Modules without declared roles stay eager; an explicit
activatesOn wins over the derived gate.
Module maker contributing skill definitions. Gated by default on the assistant plugin's start
event — a skill belongs to the assistant feature regardless of which plugin contributes it
(skills register into a shared registry whose consumers are reactive); declare activatesOn
to override.