Launch OpenGPEX

Plugin System

How OpenGPEX achieves full extensibility through a metadata-driven plugin registry — every UI element, tool, and feature is a plugin, including all built-in functionality. This subsystem carries over from v1 largely intact; v2 extends it with dynamic loading, safe mode, and version gating.


Design Philosophy

OpenGPEX follows strict Inversion of Control (IoC): the core framework provides layout slots and lifecycle management, while plugins provide all business functionality. The editor ships zero hardcoded UI — every panel, tool, overlay, and backstage service is a plugin.

┌──────────────────────────────────────┐
│       Core Workspace (Framework)      │
│                                       │
│  ┌─────────────┐  ┌───────────────┐  │
│  │  Slot Mgr   │  │   Registry    │  │
│  │  (renders)  │◄─┤  (discovers)  │  │
│  └─────────────┘  └───────┬───────┘  │
└───────────────────────────┼───────────┘
                            │ registration chain
        ┌───────────────────┼────────────────────┐
        ▼                   ▼                    ▼
  [LayersDrawer]      [ColorOptions]        [CloudMenu]
  [SmartGuides]       [FileLoader]          [TabDock]
        ...                                    ...

Registration Chain

At workspace init, plugins register in a fixed order:

  1. Core registry — 33 built-in plugins, generated by a build-tool sync step (pnpm sync-plugins) and registered first.
  2. User registry — local plugins from the repository's user registry file.
  3. Dynamic user plugins — fetched at runtime via an internal plugin list endpoint and imported as ES modules; shared modules are exposed on a well-known window global.

Safety Modes

  • ?safe-mode=true loads core plugins only.
  • User plugins are disabled by default and individually opt-in (persisted in localStorage).
  • Each plugin declares core-version compatibility; incompatible plugins are gated off at load.
  • Uninstall paths uniformly deregister commands, shortcuts, and contributions.

Plugin Descriptor Contract

Every plugin exports a descriptor containing:

  • manifest — identity (author, id, version, description); runtime UID derives from the author id
  • slot — mount point (see table below)
  • side / order — drawer side and sort order; crossing multiples of 100 auto-inserts separators
  • component — React component for the plugin's UI
  • commands — dispatchable command definitions (shortcuts, menu actions)
  • signals — reactive state signals the plugin subscribes to
  • contributions — additional slot mounts beyond the primary slot
  • initialConfig — defaults incl. preferred sidebar width; scoped persistence per plugin

Slot System

v2 keeps the slot model and refines the physical layout. Slots map to page regions as follows:

Slot Page location Examples
SIDE_BAR Floating left/right drawer panels (320px default, do not squeeze the canvas) Layers, Adjustment, AI Tools drawers
OPTION_BAR Top centered pill toolbar, content follows the active tool Color / Clip / Viewport options
TOOL_MENU / TOOL_SETTINGS Main menu panel and its settings submenu Smart guides toggle, settings panels
XTEND_SLOT Top-right extend button popover Cloud menu
DOCK Full viewport-area overlay (pointer-events none by default) Tab dock
TL/TR/BL/BR Viewport corner HUD Zoom controls
VIEWPORT_OVERLAY Over the canvas viewport Smart guides
STAGE_OVERLAY / STAGE_GIZMOS Tool interaction layer above the transformed canvas Brush, text editor, crop handles
ROOT_OVERLAY Workspace root File loader, onboarding
HIDDEN No UI — backstage services Font loader, TimeTraveler

Rendering is centralized in one PluginSlot component: it collects plugins by slot plus contributions, sorts by order, and wraps each in an error boundary + Suspense + plugin context. Visibility is gated on frame existence (e.g. show: "frame-required").

Floating UI never pushes the canvas around ad hoc: all spatial negotiation goes through a LayoutContext in which regions register push/overlay slots, a stability state machine settles measurements, and the resulting safe rectangle is bridged back into Redux and CSS variables.


Command System

Commands are identified by scoped UIDs:

  • Within a plugin: short IDs auto-resolve to the plugin's namespace (e.g. cmd.reset → opengpex.drawers.my_tool.cmd.reset)
  • Cross-plugin: full UID required

Commands support interceptors, async execution, and automatic keyboard-shortcut registration from the manifest.


Interaction Handlers

Plugins register gesture handlers with priority-based routing:

Priority Range Use Case
100–200 Overlay handles (crop box, resize corners)
50–99 Active tool interactions (brush, text)
10–49 Layer selection & move
0–9 Viewport pan/zoom (fallback)

The dispatcher tests handlers in priority order; first match wins and owns the entire gesture lifecycle (start → move → end).


Safety & Isolation

  • Error boundaries — plugin render failures are caught per-slot and cannot cascade.
  • Scoped state — each plugin gets isolated config storage.
  • Action broadcasting — plugin action hooks run asynchronously and cannot block the dispatch pipeline.
  • Busy state — long-running operations expose a standardized busy indicator without freezing the editor.
  • Services, not singletons — plugins receive stable service handles (engine, assets, files, geometry) through context; they never import engine internals directly.

Last updated: 2026-10-12