mono/packages/ui-next/docs/widgets.md
2026-04-09 13:52:07 +02:00

5.5 KiB

Widget system (@polymech/ui-next)

This document describes the scaffold under src/widgets/: registry, plugins, hooks, and extension slots. It implements a subset of the full contract in packages/ui/docs/widgets-api.md (Polymech UI).


1. High-level architecture

Plugins run at bootstrap, receive a capability-limited PluginAPI, and register widgets, hooks, and slot components. The host UI renders ExtensionSlot at fixed slot IDs; injected components appear there without the core editor importing plugin packages directly.

flowchart TB
  subgraph bootstrap["Bootstrap"]
    Boot([bootWidgetSystem])
    Boot --> PM[PluginManager.register]
  end

  subgraph plugins["Plugins"]
    P1[coreWidgetsPlugin]
    P2[demoToolbarPlugin]
  end

  subgraph registries["Registries"]
    WR[(WidgetRegistry)]
    HR[(HookRegistry)]
    SR[(SlotRegistry)]
  end

  subgraph react["React tree"]
    ES[ExtensionSlot slotId]
    W[Widget components]
  end

  PM --> P1
  PM --> P2
  P1 --> API[PluginAPI]
  P2 --> API
  API --> WR
  API --> HR
  API --> SR
  WR --> W
  SR --> ES

2. Plugin lifecycle

Registration order matters for requires: dependencies must already be registered. Higher priority runs first for ordered hooks (see §3).

sequenceDiagram
  participant App
  participant PM as PluginManager
  participant API as PluginAPI
  participant Plugin

  App->>PM: register(plugin)
  PM->>PM: validate requires deps
  PM->>API: createPluginAPI(scoped to plugin id)
  PM->>Plugin: setup(api)
  Plugin->>API: registerWidget / addHook / injectSlot
  API-->>Plugin: void
  PM->>PM: plugins.set(id, plugin)

  Note over App,Plugin: unregister(pluginId)
  App->>PM: unregister(id)
  PM->>Plugin: teardown()
  PM->>PM: removeHooksForPlugin, clearSlot entries

3. Hook pipeline (conceptual)

Hook handlers are stored with plugin id and priority. When the host calls HookRegistry.runSync, handlers run in descending priority (see widgets-api §13). Not every HookName is wired in the scaffold yet; the registry is ready for editor/layout integration.

flowchart LR
  subgraph pipeline["editor:widgetPalette (example)"]
    H1["Handler priority 100"]
    H2["Handler priority 10"]
    H3["Handler priority 0"]
  end

  InputIn["widgets[] in"] --> H1
  H1 --> H2
  H2 --> H3
  H3 --> OutputOut["widgets[] out"]

4. Extension slots

PluginAPI.injectSlot(slotId, Component) appends a React component to that slot. ExtensionSlot subscribes to SlotRegistry via useSyncExternalStore and re-renders when injections change. Multiple plugins (or multiple calls from one plugin) can add entries; each entry gets a stable entryId.

flowchart TB
  subgraph chrome["Editor chrome (conceptual)"]
    TBStart["editor:toolbar:start"]
    TBEnd["editor:toolbar:end"]
    Side["editor:sidebar:top / …"]
  end

  Plugin["Plugin setup"]
  Plugin --> Inject[injectSlot]
  Inject --> SR[(SlotRegistry)]
  SR --> ES[ExtensionSlot]
  ES --> DOM[Rendered React tree]

5. Data flow: from plugin to screen

flowchart LR
  subgraph pluginSide["Plugin"]
    R["registerWidget(metadata.id)"]
    I["injectSlot(editor:toolbar:end)"]
  end

  subgraph state["State"]
    WR[(WidgetRegistry)]
    SR[(SlotRegistry)]
  end

  subgraph ui["UI"]
    Palette["Future: palette reads registry"]
    Slot[ExtensionSlot]
  end

  R --> WR
  I --> SR
  WR -.-> Palette
  SR --> Slot

The demo page WidgetSystemDemoPage reads widgetRegistry.getAll() and mounts ExtensionSlot for editor:toolbar:end, plus a direct TextBlockWidget preview with mock BaseWidgetProps.


6. Module map (this package)

Module Role
types.ts WidgetDefinition, WidgetPlugin, PluginAPI, HookName, SlotId, …
WidgetRegistry CRUD for widget definitions
HookRegistry Hook lists + runSync
SlotRegistry Slot entries + subscribe
createPluginAPI Facade passed into plugin.setup
PluginManager register / unregister
system.ts Process-wide singletons for the POC
ExtensionSlot Renders slot content
boot.ts bootWidgetSystem()

7. Relationship to the full widgets API

Full doc topic Status in this scaffold
BaseWidgetProps, metadata, configSchema Types + sample text-block
PluginAPI (widgets, hooks, slots) Implemented (minimal getStore / subscribe)
Container types, registerContainerType Not implemented — extend PluginAPI when needed
Real LayoutStore (Zustand) Stub layoutStore object
Nested layouts, export, i18n Not implemented

For the authoritative interface proposal, keep using packages/ui/docs/widgets-api.md.


8. Try it

  1. Run the app (npm run dev in packages/ui-next).
  2. Open /examples/widgets-system (or Migration examples → widgets + plugins).
  3. Confirm the toolbar slot pill and the text-block registry entry after bootWidgetSystem() runs.