@controller
@controller(identifier) registers a RadiantController class for data-controller discovery.
It is equivalent to calling registerController(identifier, ControllerClass) directly.
If controller runtimes are already running, newly registered identifiers are reconciled against the live DOM immediately.
Example
import { RadiantController, controller } from '@ecopages/radiant';
import { startControllers } from '@ecopages/radiant/controller-registry';
@controller('search')
class SearchController extends RadiantController {
connect() {
super.connect();
}
}
startControllers(document);<form data-controller="search"></form>Identifier Rules
- the identifier must be unique across the page runtime
- re-registering the same constructor is a no-op
- registering a different constructor for an existing identifier keeps the original controller by default
That default prevents an accidental second registration from mutating already-connected controller instances.
When tooling does need mutation, use the explicit runtime APIs instead of changing the default decorator contract.
Tooling Hooks
The registry also exposes explicit mutation hooks. Prefer the focused @ecopages/radiant/controller-registry subpath when a module only needs registry APIs:
replaceController(...)enableControllerReplacementForHmr()
Use those APIs for HMR or code-generation workflows that intentionally replace a controller class after the first registration.
Related Runtime API
The registry also exposes an explicit runtime surface through the focused @ecopages/radiant/controller-registry subpath, and also re-exports it from @ecopages/radiant for convenience:
registerController(...)replaceController(...)enableControllerReplacementForHmr()startControllers(...)stopControllers()CONTROLLER_ATTRIBUTEControllerRegistryRuntime
Prefer the focused subpath for setup modules and tooling hooks. Reach for the root entry only when that module is already importing broader controller APIs and the convenience matters more than the narrower import boundary.