Summary
This RFC proposes enhancing Exponential's plugin system to support lifecycle hooks, a rich App Context API, an event system, and programmatic registration - similar to Obsidian's architecture.
Motivation
Our current plugin system uses static manifest-driven registration. While this works, it lacks:
- Lifecycle management - No way for plugins to run initialization/cleanup code
- Programmatic registration - Everything must be declared in manifests upfront
- Event system - Plugins cannot react to app events
- Hot enable/disable - Requires page refresh when toggling plugins
Obsidian's plugin system (1,500+ community plugins) demonstrates that lifecycle hooks and a rich API enable powerful extensibility.
Proposed Features
1. Plugin Base Class with Lifecycle Hooks
export abstract class Plugin {
abstract onload(): void | Promise<void>;
abstract onunload(): void | Promise<void>;
protected registerNavigation(item: NavigationItem): string;
protected registerWidget(widget: DashboardWidget): string;
protected registerCommand(command: PluginCommand): string;
protected on<E extends AppEventType>(event: E, callback: AppEventCallback<E>): () => void;
}
2. App Context API
Plugins receive an app object providing controlled access to:
- Workspace context (id, slug, name)
- Event subscription
- tRPC API access
- UI helpers (notify, openModal)
- Settings persistence
3. Event System
type AppEventType =
| 'workspace:changed'
| 'action:created' | 'action:completed'
| 'goal:created' | 'goal:updated'
| 'plugin:loaded' | 'plugin:unloaded';
4. Command Registration (Keyboard Shortcuts)
this.registerCommand({
id: 'open-okrs',
name: 'Open OKRs Dashboard',
hotkey: 'mod+shift+o',
callback: () => { /* navigate */ }
});
Example: Migrated OKR Plugin
export class OkrPlugin extends Plugin {
async onload() {
// Register navigation programmatically
this.registerNavigation({
label: 'OKRs', icon: 'IconTargetArrow',
href: '/w/:workspaceSlug/okrs', section: 'workspace', order: 5
});
// Register widget with actual component
this.registerWidget({
title: 'OKR Progress',
component: OkrProgressWidget,
order: 10, gridSpan: 'half'
});
// Subscribe to events
this.on('goal:created', (payload) => {
this.app.notify({ title: 'Goal Created', message: 'Consider adding Key Results!', type: 'info' });
});
}
async onunload() {
// Cleanup handled automatically
}
}
Backwards Compatibility
- Manifest-only plugins continue to work unchanged
- Existing navigation/widgets from manifests are registered if no
onload() overrides
- tRPC routers remain statically imported
- Database schema unchanged
- Plugins can be migrated incrementally
Files to Create/Modify
New Files
src/plugins/events.ts - Event bus and types
src/plugins/app.ts - PluginApp interface
src/providers/PluginProvider.tsx - React context for lifecycle
src/plugins/okr/OkrPlugin.ts - Reference implementation
Modified Files
src/plugins/types.ts - Add Plugin class, PluginCommand
src/plugins/registry.ts - Add lifecycle methods
src/plugins/loader.ts - Support PluginClass
src/app/_components/PluginWidgets.tsx - Use PluginProvider
src/hooks/usePluginNavigation.ts - Use PluginProvider
src/app/(sidemenu)/layout.tsx - Add PluginProvider
Questions for Discussion
- Event granularity - What events should be exposed to plugins?
- Command palette - Should we add a command palette UI (like Obsidian's Cmd+P)?
- Plugin settings UI - Should plugins be able to define their own settings schema?
- Security - Any concerns with giving plugins access to the tRPC API?
References
Summary
This RFC proposes enhancing Exponential's plugin system to support lifecycle hooks, a rich App Context API, an event system, and programmatic registration - similar to Obsidian's architecture.
Motivation
Our current plugin system uses static manifest-driven registration. While this works, it lacks:
Obsidian's plugin system (1,500+ community plugins) demonstrates that lifecycle hooks and a rich API enable powerful extensibility.
Proposed Features
1. Plugin Base Class with Lifecycle Hooks
2. App Context API
Plugins receive an
appobject providing controlled access to:3. Event System
4. Command Registration (Keyboard Shortcuts)
Example: Migrated OKR Plugin
Backwards Compatibility
onload()overridesFiles to Create/Modify
New Files
src/plugins/events.ts- Event bus and typessrc/plugins/app.ts- PluginApp interfacesrc/providers/PluginProvider.tsx- React context for lifecyclesrc/plugins/okr/OkrPlugin.ts- Reference implementationModified Files
src/plugins/types.ts- Add Plugin class, PluginCommandsrc/plugins/registry.ts- Add lifecycle methodssrc/plugins/loader.ts- Support PluginClasssrc/app/_components/PluginWidgets.tsx- Use PluginProvidersrc/hooks/usePluginNavigation.ts- Use PluginProvidersrc/app/(sidemenu)/layout.tsx- Add PluginProviderQuestions for Discussion
References
src/plugins/