@openpen/module-api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/package.json +53 -0
  4. package/src/define-module.ts +55 -0
  5. package/src/host/color-utils.ts +18 -0
  6. package/src/host/event-bus.ts +37 -0
  7. package/src/host/host-commands.ts +9 -0
  8. package/src/host/index.ts +53 -0
  9. package/src/host/module-context-registry.ts +34 -0
  10. package/src/host/passthrough.ts +10 -0
  11. package/src/host/popup-anchor.ts +14 -0
  12. package/src/host/slot-entries.ts +9 -0
  13. package/src/host/stroke-store.ts +20 -0
  14. package/src/host/stroke-style.ts +15 -0
  15. package/src/index.ts +106 -0
  16. package/src/inject-keys.ts +143 -0
  17. package/src/locale.ts +52 -0
  18. package/src/slots.ts +135 -0
  19. package/src/types/contributions.ts +298 -0
  20. package/src/types/control-bar-layout.ts +174 -0
  21. package/src/types/module.ts +225 -0
  22. package/src/types/notification.ts +40 -0
  23. package/src/types/tool.ts +82 -0
  24. package/src/uikit/components/.gitkeep +0 -0
  25. package/src/uikit/components/AppBanner.vue +131 -0
  26. package/src/uikit/components/AppButton.vue +135 -0
  27. package/src/uikit/components/AppDialog.vue +393 -0
  28. package/src/uikit/components/AppPopover.vue +268 -0
  29. package/src/uikit/components/AppSegmented.vue +126 -0
  30. package/src/uikit/components/AppSelect.vue +235 -0
  31. package/src/uikit/components/AppSlider.vue +204 -0
  32. package/src/uikit/components/AppTabs.vue +163 -0
  33. package/src/uikit/components/AppToggle.vue +96 -0
  34. package/src/uikit/components/AppTooltip.vue +118 -0
  35. package/src/uikit/components/DialogHost.vue +260 -0
  36. package/src/uikit/composables/dialog-internal-keys.ts +17 -0
  37. package/src/uikit/composables/useDialog.ts +141 -0
  38. package/src/uikit/composables/useDialogPluginComponent.ts +27 -0
  39. package/src/uikit/index.ts +79 -0
  40. package/src/uikit/internal.ts +12 -0
  41. package/src/uikit/primitives.ts +150 -0
  42. package/src/uikit/tokens.css +205 -0
  43. package/src/use-module-context.ts +50 -0
  44. package/src/validation.ts +31 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 navishachiku
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @openpen/module-api
2
+
3
+ The public contract surface for **OpenPen modules** — both first-party (`src/core/modules/*`) and third-party plugins (`~/.openpen/plugins/*`).
4
+
5
+ This is the **only** import path that built-in modules and plugins are allowed to use (apart from relative imports, `node:*`, and third-party npm packages). The boundary is enforced by `tests/unit/boundaries/*` in the OpenPen main repo.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @openpen/module-api zod
11
+ ```
12
+
13
+ > **Import `z` from `@openpen/module-api`, not from `'zod'` directly.**
14
+ > The build externalises `'zod'` — a direct `import { z } from 'zod'` in a
15
+ > plugin file will fail at build time with an unresolved-import error. Use
16
+ > `import { z } from '@openpen/module-api'` everywhere in your plugin.
17
+
18
+ ## Quick start
19
+
20
+ ```typescript
21
+ import { defineModule, z } from '@openpen/module-api'
22
+
23
+ export default defineModule({
24
+ id: 'my-module',
25
+ name: { en: 'My Module', 'zh-TW': '我的模組' },
26
+ settingsSchema: z.object({
27
+ enabled: z.boolean().default(true),
28
+ }),
29
+ contributes: {
30
+ tools: [
31
+ {
32
+ id: 'my-tool',
33
+ // ...
34
+ },
35
+ ],
36
+ },
37
+ })
38
+ ```
39
+
40
+ ### `setup(ctx)` — Module initialisation
41
+
42
+ `defineModule` supports an optional `setup(ctx: ModuleSetupContext)` hook, called once when the module loads. `ctx` provides:
43
+
44
+ | Method | Description |
45
+ |--------|-------------|
46
+ | `ctx.getSettings<T>()` | Read this module's parsed settings (requires `settingsSchema` to be declared) |
47
+ | `ctx.callMain(action, payload?)` | Call this module's main-side handler |
48
+ | `ctx.onDispose(fn)` | Register a cleanup callback (called in reverse order when the module is unloaded) |
49
+ | `ctx.notify(payload)` | Show a short-lived toast in the overlay window; returns `NotifyHandle` for early `dismiss()` |
50
+
51
+ ```typescript
52
+ import { defineModule } from '@openpen/module-api'
53
+ import type { NotifyPayload } from '@openpen/module-api'
54
+
55
+ export default defineModule({
56
+ id: 'my-module',
57
+ name: { en: 'My Module' },
58
+ contributes: { /* ... */ },
59
+
60
+ setup(ctx) {
61
+ // Show a toast when the module finishes loading.
62
+ ctx.notify({
63
+ message: { en: 'My Module ready' },
64
+ variant: 'success',
65
+ duration: 1500,
66
+ })
67
+
68
+ // Release resources (timers, listeners, etc.) on unload.
69
+ const timer = setInterval(() => { /* ... */ }, 5000)
70
+ ctx.onDispose(() => clearInterval(timer))
71
+ },
72
+ })
73
+ ```
74
+
75
+ See [docs/reference/notify-api.md](../../docs/reference/notify-api.md).
76
+
77
+ See the OpenPen [docs](https://github.com/openpen/openpen/blob/main/docs/README.md) for guides, slot catalogue, and UIKit reference.
78
+
79
+ ## Long-term stability of the UIKit
80
+
81
+ The UIKit (`@openpen/module-api/uikit`) wraps a headless behaviour library
82
+ internally. That library is an implementation detail — it is not part of the
83
+ public API surface.
84
+
85
+ If the underlying library is ever replaced, the host will follow a documented
86
+ fallback order: Headless UI Vue → Ark UI → self-written primitives. The wrapper
87
+ props, events, and slots documented here will remain stable across any such
88
+ swap. Plugin authors who import only from `@openpen/module-api/uikit` will be
89
+ unaffected. Authors who drop to the primitive layer (named primitive exports)
90
+ will see a major-version bump and a small targeted port. Authors who bypass the
91
+ UIKit entirely and import a headless library directly in their plugin's
92
+ `package.json` are responsible for porting that surface themselves.
93
+
94
+ This guarantee is why the wrapper layer is the recommended starting point for
95
+ all plugin UI.
96
+
97
+ ## License
98
+
99
+ MIT
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@openpen/module-api",
3
+ "version": "1.0.0",
4
+ "description": "OpenPen Module API — public contract for built-in modules and third-party plugins",
5
+ "author": "navishachiku <navishachiku@gmail.com>",
6
+ "homepage": "https://github.com/openpen-platform/openpen#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/openpen-platform/openpen.git",
10
+ "directory": "packages/module-api"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/openpen-platform/openpen/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "license": "MIT",
19
+ "type": "module",
20
+ "main": "./src/index.ts",
21
+ "types": "./src/index.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./src/index.ts",
25
+ "import": "./src/index.ts"
26
+ },
27
+ "./uikit": {
28
+ "types": "./src/uikit/index.ts",
29
+ "import": "./src/uikit/index.ts"
30
+ },
31
+ "./uikit/tokens.css": "./src/uikit/tokens.css",
32
+ "./uikit/internal": {
33
+ "types": "./src/uikit/internal.ts",
34
+ "import": "./src/uikit/internal.ts"
35
+ },
36
+ "./host": {
37
+ "types": "./src/host/index.ts",
38
+ "import": "./src/host/index.ts"
39
+ }
40
+ },
41
+ "engines": {
42
+ "node": ">=20",
43
+ "npm": ">=9"
44
+ },
45
+ "keywords": ["openpen", "plugin", "module", "sdk"],
46
+ "dependencies": {
47
+ "reka-ui": "~2.9.6"
48
+ },
49
+ "peerDependencies": {
50
+ "vue": "^3.5.0",
51
+ "zod": "^3.23.0"
52
+ }
53
+ }
@@ -0,0 +1,55 @@
1
+ import { isValidModuleId } from './validation'
2
+ import { CONTRIBUTION_KEY_TO_SLOT_ID } from './slots'
3
+ import type { OpenPenModule } from './types/module'
4
+
5
+ /**
6
+ * Identity helper for declaring an OpenPen module with full type
7
+ * inference and runtime sanity checks. Throws `Error` (preflight) when:
8
+ *
9
+ * - `id` is missing or has invalid characters
10
+ * - `contributes` is missing entirely
11
+ * - `contributes` has no contribution fields
12
+ * - `contributes` references a slot key not in the catalogue
13
+ *
14
+ * This is intentionally a small surface — full validation
15
+ * (id collisions across modules, slot quota, settingsSchema parse, etc.)
16
+ * happens later in the host app's `module-validator`. This helper
17
+ * catches the obvious developer mistakes at the module's own boundary
18
+ * so errors surface in the module's repo, not deep inside OpenPen.
19
+ */
20
+ export function defineModule<TModule extends OpenPenModule>(
21
+ spec: TModule
22
+ ): TModule {
23
+ if (!isValidModuleId(spec.id)) {
24
+ throw new Error(
25
+ `[@openpen/module-api] Invalid module id: ${JSON.stringify(spec.id)}. ` +
26
+ `Module ids must use \`@scope/name\` format with lowercase ASCII alphanumeric + hyphen, ` +
27
+ `max 39 chars per segment (e.g. \`@alice/my-plugin\`).`
28
+ )
29
+ }
30
+
31
+ if (!spec.contributes || typeof spec.contributes !== 'object') {
32
+ throw new Error(
33
+ `[@openpen/module-api] Module "${spec.id}" must declare a non-empty 'contributes' object.`
34
+ )
35
+ }
36
+
37
+ const keys = Object.keys(spec.contributes)
38
+ if (keys.length === 0) {
39
+ throw new Error(
40
+ `[@openpen/module-api] Module "${spec.id}" must contribute to at least one slot. ` +
41
+ `Add at least one field to 'contributes' (e.g. 'tools', 'settingsTabs', 'shortcuts').`
42
+ )
43
+ }
44
+
45
+ for (const key of keys) {
46
+ if (!(key in CONTRIBUTION_KEY_TO_SLOT_ID)) {
47
+ throw new Error(
48
+ `[@openpen/module-api] Module "${spec.id}" contributes to unknown slot key '${key}'. ` +
49
+ `Known keys: ${Object.keys(CONTRIBUTION_KEY_TO_SLOT_ID).sort().join(', ')}.`
50
+ )
51
+ }
52
+ }
53
+
54
+ return spec
55
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @openpen/module-api/host — Color Utilities
3
+ *
4
+ * Re-exports color helpers that module components need for rendering.
5
+ * The canvas-rendering helper resolveColorStyle and the color manipulation
6
+ * utilities are exposed here so modules don't need cross-layer imports.
7
+ */
8
+
9
+ export {
10
+ resolveColorStyle,
11
+ hexToRgb,
12
+ rgbToHex,
13
+ hsvToRgb,
14
+ rgbToHsv,
15
+ hexToHsv,
16
+ hsvToHex,
17
+ isValidHex,
18
+ } from '../../../../src/services/color-utils'
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @openpen/module-api/host — Domain Event Bus
3
+ *
4
+ * Re-exports the renderer-side event-bus emit/on so module components
5
+ * can publish and subscribe without reaching into host runtime internals.
6
+ * The bus is a process-local pub/sub for cross-module coordination.
7
+ *
8
+ * **Host-emitted events (consume via `on(name, cb)`):**
9
+ *
10
+ * | Event name | Payload shape | Fires when |
11
+ * |---------------------------|-------------------------------------------------|----------------------------------------------------------------------|
12
+ * | `'tool-changed'` | `{ tool: string } & Partial<ToolConfig>` | The active drawing tool changes (user click, IPC, or shortcut) |
13
+ * | `'stroke-style-changed'` | `{ color?: StrokeColor; lineWidth?: number }` | Color or stroke width changes |
14
+ * | `'canvas-redraw-requested'` | (no payload) | A module wants the canvas to repaint (eraser, transformer, etc.) |
15
+ *
16
+ * **Module-emitted events:** modules MAY emit any event name they
17
+ * choose; the bus is namespace-free. Use a prefix (e.g. `'my-plugin:'`)
18
+ * to avoid colliding with another module's events.
19
+ *
20
+ * `on(name, cb)` returns an unsubscribe function — register it via
21
+ * `ctx.onDispose(unsubscribe)` so the listener is cleaned up when the
22
+ * module unloads.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { on } from '@openpen/module-api/host'
27
+ *
28
+ * setup(ctx) {
29
+ * const off = on('tool-changed', (payload) => {
30
+ * console.log('tool now', payload.tool)
31
+ * })
32
+ * ctx.onDispose(off)
33
+ * }
34
+ * ```
35
+ */
36
+
37
+ export { emit, on } from '../../../../src/core/runtime/event-bus'
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @openpen/module-api/host — Host Commands
3
+ *
4
+ * Re-exports hostCommands for module components that need to trigger
5
+ * host infrastructure actions (pin toggle, canvas clear, undo/redo,
6
+ * drawing mode toggle).
7
+ */
8
+
9
+ export { hostCommands } from '../../../../src/services/host-commands'
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @openpen/module-api/host — Host Services Sub-module
3
+ *
4
+ * Exposes host shared services to module components via a clean sub-path
5
+ * export. Module code MUST import from here instead of using cross-layer
6
+ * '../../../' relative imports into src/composables or src/services.
7
+ *
8
+ * Module code MUST import from `@openpen/module-api/host` instead of
9
+ * using cross-layer relative imports into src/.
10
+ *
11
+ * Design principle: this sub-module MUST NOT leak host internal
12
+ * implementation types. All types are OpenPen SDK types (from ../types/).
13
+ */
14
+
15
+ // ── Stroke Style ──────────────────────────────────────────────────────────────
16
+ export { useStrokeStyle } from './stroke-style'
17
+ export type { StrokeColor } from './stroke-style'
18
+
19
+ // ── Color Utilities ───────────────────────────────────────────────────────────
20
+ export {
21
+ resolveColorStyle,
22
+ hexToRgb,
23
+ rgbToHex,
24
+ hsvToRgb,
25
+ rgbToHsv,
26
+ hexToHsv,
27
+ hsvToHex,
28
+ isValidHex,
29
+ } from './color-utils'
30
+
31
+ // ── Stroke Store ──────────────────────────────────────────────────────────────
32
+ export { getAllStrokes, removeStrokeById, pushCommand } from './stroke-store'
33
+ export type { Stroke } from './stroke-store'
34
+
35
+ // ── Popup Anchor ──────────────────────────────────────────────────────────────
36
+ export { usePopupAnchor, calculatePopupAnchor } from './popup-anchor'
37
+
38
+ // ── Passthrough Guard ─────────────────────────────────────────────────────────
39
+ export { usePassthroughGuard } from './passthrough'
40
+
41
+ // ── Host Commands ─────────────────────────────────────────────────────────────
42
+ export { hostCommands } from './host-commands'
43
+
44
+ // ── Event Bus ─────────────────────────────────────────────────────────────────
45
+ export { emit, on } from './event-bus'
46
+
47
+ // ── Slot Entries ──────────────────────────────────────────────────────────────
48
+ export { getSlotEntries } from './slot-entries'
49
+
50
+ // ── Module Context Registry ───────────────────────────────────────────────────
51
+ // Used by the renderer host (module-loader.ts) to populate/clear the registry
52
+ // that backs `useModuleContext`. Plugin code MUST NOT import these directly.
53
+ export { setModuleContext, clearModuleContext } from './module-context-registry'
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Host-side registry that backs `useModuleContext`.
3
+ *
4
+ * The renderer host (module-loader.ts) writes to this registry via
5
+ * `setModuleContext` / `clearModuleContext` immediately around each
6
+ * module's `setup()` call. Plugin-authored Vue components then read
7
+ * from it via the public `useModuleContext` composable.
8
+ *
9
+ * Module code MUST NOT import from this file directly — use
10
+ * `useModuleContext` from `@openpen/module-api` instead.
11
+ */
12
+ import type { ModuleSetupContext } from '../types/module'
13
+
14
+ const registry = new Map<string, ModuleSetupContext>()
15
+
16
+ /** Register (or replace) the context for a module. Called by module-loader before setup(). */
17
+ export function setModuleContext(moduleId: string, ctx: ModuleSetupContext): void {
18
+ registry.set(moduleId, ctx)
19
+ }
20
+
21
+ /** Remove the context for a module. Called by module-loader in rollbackModule(). */
22
+ export function clearModuleContext(moduleId: string): void {
23
+ registry.delete(moduleId)
24
+ }
25
+
26
+ /** Look up the context for a module. Returns undefined if not registered. */
27
+ export function getModuleContext(moduleId: string): ModuleSetupContext | undefined {
28
+ return registry.get(moduleId)
29
+ }
30
+
31
+ /** Resets the entire registry. Test-only — do not call in production code. */
32
+ export function resetModuleContextRegistryForTest(): void {
33
+ registry.clear()
34
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @openpen/module-api/host — Passthrough Guard
3
+ *
4
+ * Re-exports usePassthroughGuard so module components (and UIKit wrappers)
5
+ * can import from @openpen/module-api/host without cross-layer imports.
6
+ *
7
+ * Exposed here so module code can import without cross-layer relative paths.
8
+ */
9
+
10
+ export { usePassthroughGuard } from '../../../../src/composables/usePassthroughGuard'
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @openpen/module-api/host — Popup Anchor
3
+ *
4
+ * Re-exports the usePopupAnchor composable and its pure helper
5
+ * calculatePopupAnchor for module components that need anchor-relative
6
+ * popup positioning.
7
+ *
8
+ * Exposed here so module code can import without cross-layer relative paths.
9
+ */
10
+
11
+ export {
12
+ usePopupAnchor,
13
+ calculatePopupAnchor,
14
+ } from '../../../../src/composables/usePopupAnchor'
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @openpen/module-api/host — Slot Entries Reader
3
+ *
4
+ * Re-exports getSlotEntries so module components can render UI driven
5
+ * by other modules' contributions to the same slot (for example,
6
+ * the shape-picker rendering all canvas.shapes contributions).
7
+ */
8
+
9
+ export { getSlotEntries } from '../../../../src/core/runtime/contribution-store'
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @openpen/module-api/host — Stroke Store
3
+ *
4
+ * Re-exports the stroke store APIs that module tools need.
5
+ * Only the minimum surface required by stroke-eraser (and similar tools)
6
+ * is exposed: getAllStrokes, removeStrokeById, pushCommand.
7
+ *
8
+ * Host-internal state mutation helpers (clearAll, addStroke, undo, redo)
9
+ * are intentionally NOT exported — modules must use hostCommands for
10
+ * undo/redo and canvas clear.
11
+ */
12
+
13
+ export {
14
+ getAllStrokes,
15
+ removeStrokeById,
16
+ pushCommand,
17
+ } from '../../../../src/services/stroke-store'
18
+
19
+ // Re-export Stroke type via the public SDK types, not host internals.
20
+ export type { Stroke } from '../types/tool'
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @openpen/module-api/host — Stroke Style
3
+ *
4
+ * Re-exports the public API of the host useStrokeStyle composable.
5
+ * Module components MUST import from here, not from src/composables directly.
6
+ *
7
+ * Exposed here so module components can access it without cross-layer imports.
8
+ */
9
+
10
+ // Re-export the public API functions that modules need.
11
+ // initStrokeStyleFromSettings is host-boot-only and intentionally NOT exported here.
12
+ export { useStrokeStyle } from '../../../../src/composables/useStrokeStyle'
13
+
14
+ // Re-export the StrokeColor type via the public SDK types (not host internals).
15
+ export type { StrokeColor } from '../types/tool'
package/src/index.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @openpen/module-api — public contract surface for OpenPen modules.
3
+ *
4
+ * This is the **only** import path that built-in modules and plugins
5
+ * are allowed to use, alongside relative imports, `node:*`, and
6
+ * third-party npm packages. Boundary tests in the OpenPen main repo
7
+ * enforce this rule on `src/core/modules/**` and on plugin code.
8
+ */
9
+
10
+ // Core helpers
11
+ export { defineModule } from './define-module'
12
+ export { useModuleContext } from './use-module-context'
13
+ export { isValidModuleId, MODULE_ID_RE } from './validation'
14
+ export { resolveLabel, sanitizeIdForI18n } from './locale'
15
+ export type { LocaleMap } from './locale'
16
+
17
+ // Slot catalogue
18
+ export {
19
+ ALL_SLOTS,
20
+ V1_ACTIVE_SLOTS,
21
+ V1_RESERVED_SLOTS,
22
+ CONTRIBUTION_KEY_TO_SLOT_ID,
23
+ isKnownSlot,
24
+ getSlot,
25
+ } from './slots'
26
+ export type { SlotDefinition, SlotCategory, SlotStatus } from './slots'
27
+
28
+ // Tool primitives (Point, Stroke, StrokeStyle, Tool, …)
29
+ export type {
30
+ Point,
31
+ StrokeColor,
32
+ StrokeStyle,
33
+ Stroke,
34
+ PointerModifiers,
35
+ Tool,
36
+ } from './types/tool'
37
+
38
+ // Control bar layout types + helpers
39
+ export type { ControlBarLayout, LayoutGroup, GroupInset, ControlBarContribution } from './types/control-bar-layout'
40
+ export {
41
+ ControlBarLayoutSchema,
42
+ LayoutGroupSchema,
43
+ GroupInsetSchema,
44
+ DEFAULT_CONTROL_BAR_LAYOUT,
45
+ repairLayoutL3a,
46
+ repairLayoutL3b,
47
+ } from './types/control-bar-layout'
48
+
49
+ // Notification types
50
+ export type { NotifyPayload, NotifyHandle } from './types/notification'
51
+
52
+ // Module + Contribution types
53
+ export type { OpenPenModule, ModuleSetupContext } from './types/module'
54
+ export type {
55
+ ModuleContributions,
56
+ ToolContribution,
57
+ ShapeContribution,
58
+ StrokeStyleContribution,
59
+ HistoryCommandContribution,
60
+ CanvasLayerContribution,
61
+ HtmlOverlayContribution,
62
+ StrokeTransformerContribution,
63
+ SettingsTabContribution,
64
+ SettingsPanelContribution,
65
+ CursorContribution,
66
+ StatusContribution,
67
+ ModalContribution,
68
+ TrayMenuContribution,
69
+ ContextMenuContribution,
70
+ ThemeTokenContribution,
71
+ ShortcutContribution,
72
+ WindowBehaviorContribution,
73
+ LocaleContribution,
74
+ EventSubscriptionContribution,
75
+ LifecycleContribution,
76
+ StorageContribution,
77
+ MainHandlerContribution,
78
+ FileDropContribution,
79
+ } from './types/contributions'
80
+
81
+ // Host ↔ module inject keys (Vue provide/inject bridge)
82
+ export {
83
+ STROKE_STYLE_CONTEXT_KEY,
84
+ SNAP_EDGE_KEY,
85
+ IS_VERTICAL_KEY,
86
+ MODAL_MANAGER_KEY,
87
+ WRAPPER_EL_KEY,
88
+ ANCHOR_EL_KEY,
89
+ CONTROL_BAR_ANIMATING_KEY,
90
+ HOST_DIALOG_OPEN_COUNT_KEY,
91
+ POPOVER_PLACEMENT_HINT_KEY,
92
+ } from './inject-keys'
93
+ export type { StrokeStyleContext, SnapEdge, ModalManager, PopoverPlacementHint } from './inject-keys'
94
+
95
+ // Re-export zod so modules don't need a separate import path.
96
+ export { z } from 'zod'
97
+
98
+ // Host-internal registry writers — re-exported here so the host's module
99
+ // loader pulls them through the SAME externalised entry point
100
+ // (`@openpen/module-api`, runtime bundle: openpen-runtime/module-api.js)
101
+ // that `useModuleContext` reads from. Importing them via
102
+ // `@openpen/module-api/host` instead would inline a duplicate
103
+ // module-context-registry into the host bundle, breaking state-sharing
104
+ // between writers and readers in production builds (see
105
+ // project_dev_prod_parity.md). Plugin authors MUST NOT use these.
106
+ export { setModuleContext, clearModuleContext } from './host/module-context-registry'