@evermore.work/plugin-sdk 2026.509.0-canary.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 (62) hide show
  1. package/README.md +1215 -0
  2. package/dist/bundlers.d.ts +57 -0
  3. package/dist/bundlers.d.ts.map +1 -0
  4. package/dist/bundlers.js +106 -0
  5. package/dist/bundlers.js.map +1 -0
  6. package/dist/define-plugin.d.ts +266 -0
  7. package/dist/define-plugin.d.ts.map +1 -0
  8. package/dist/define-plugin.js +85 -0
  9. package/dist/define-plugin.js.map +1 -0
  10. package/dist/dev-cli.d.ts +3 -0
  11. package/dist/dev-cli.d.ts.map +1 -0
  12. package/dist/dev-cli.js +49 -0
  13. package/dist/dev-cli.js.map +1 -0
  14. package/dist/dev-server.d.ts +34 -0
  15. package/dist/dev-server.d.ts.map +1 -0
  16. package/dist/dev-server.js +194 -0
  17. package/dist/dev-server.js.map +1 -0
  18. package/dist/host-client-factory.d.ts +272 -0
  19. package/dist/host-client-factory.d.ts.map +1 -0
  20. package/dist/host-client-factory.js +481 -0
  21. package/dist/host-client-factory.js.map +1 -0
  22. package/dist/index.d.ts +84 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +84 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/protocol.d.ts +1285 -0
  27. package/dist/protocol.d.ts.map +1 -0
  28. package/dist/protocol.js +306 -0
  29. package/dist/protocol.js.map +1 -0
  30. package/dist/testing.d.ts +166 -0
  31. package/dist/testing.d.ts.map +1 -0
  32. package/dist/testing.js +1766 -0
  33. package/dist/testing.js.map +1 -0
  34. package/dist/types.d.ts +1330 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +12 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/ui/components.d.ts +511 -0
  39. package/dist/ui/components.d.ts.map +1 -0
  40. package/dist/ui/components.js +135 -0
  41. package/dist/ui/components.js.map +1 -0
  42. package/dist/ui/hooks.d.ts +155 -0
  43. package/dist/ui/hooks.d.ts.map +1 -0
  44. package/dist/ui/hooks.js +195 -0
  45. package/dist/ui/hooks.js.map +1 -0
  46. package/dist/ui/index.d.ts +54 -0
  47. package/dist/ui/index.d.ts.map +1 -0
  48. package/dist/ui/index.js +51 -0
  49. package/dist/ui/index.js.map +1 -0
  50. package/dist/ui/runtime.d.ts +3 -0
  51. package/dist/ui/runtime.d.ts.map +1 -0
  52. package/dist/ui/runtime.js +30 -0
  53. package/dist/ui/runtime.js.map +1 -0
  54. package/dist/ui/types.d.ts +388 -0
  55. package/dist/ui/types.d.ts.map +1 -0
  56. package/dist/ui/types.js +17 -0
  57. package/dist/ui/types.js.map +1 -0
  58. package/dist/worker-rpc-host.d.ts +127 -0
  59. package/dist/worker-rpc-host.d.ts.map +1 -0
  60. package/dist/worker-rpc-host.js +1279 -0
  61. package/dist/worker-rpc-host.js.map +1 -0
  62. package/package.json +88 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Shared UI component declarations for plugin frontends.
3
+ *
4
+ * These components are exported from `@evermore.work/plugin-sdk/ui` and are
5
+ * provided by the host at runtime. They match the host's design tokens and
6
+ * visual language, reducing the boilerplate needed to build consistent plugin UIs.
7
+ *
8
+ * **Plugins are not required to use these components.** They exist to reduce
9
+ * boilerplate and keep visual consistency. A plugin may render entirely custom
10
+ * UI using any React component library.
11
+ *
12
+ * Component implementations are provided by the host — plugin bundles contain
13
+ * only the type declarations; the runtime implementations are injected via the
14
+ * host module registry.
15
+ *
16
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components In `@evermore.work/plugin-sdk/ui`
17
+ */
18
+ import { renderSdkUiComponent } from "./runtime.js";
19
+ // ---------------------------------------------------------------------------
20
+ // Component declarations (provided by host at runtime)
21
+ // ---------------------------------------------------------------------------
22
+ // These are declared as ambient values so plugin TypeScript code can import
23
+ // and use them with full type-checking. The host's module registry provides
24
+ // the concrete React component implementations at bundle load time.
25
+ /**
26
+ * Displays a single metric with an optional trend indicator and sparkline.
27
+ *
28
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
29
+ */
30
+ function createSdkUiComponent(name) {
31
+ return function EvermoreSdkUiComponent(props) {
32
+ return renderSdkUiComponent(name, props);
33
+ };
34
+ }
35
+ export const MetricCard = createSdkUiComponent("MetricCard");
36
+ /**
37
+ * Displays an inline status badge (ok / warning / error / info / pending).
38
+ *
39
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
40
+ */
41
+ export const StatusBadge = createSdkUiComponent("StatusBadge");
42
+ /**
43
+ * Sortable, paginated data table.
44
+ *
45
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
46
+ */
47
+ export const DataTable = createSdkUiComponent("DataTable");
48
+ /**
49
+ * Line or bar chart for time-series data.
50
+ *
51
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
52
+ */
53
+ export const TimeseriesChart = createSdkUiComponent("TimeseriesChart");
54
+ /**
55
+ * Renders Markdown text as HTML.
56
+ *
57
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
58
+ */
59
+ export const MarkdownBlock = createSdkUiComponent("MarkdownBlock");
60
+ /**
61
+ * Renders Evermore's shared Markdown editor.
62
+ *
63
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
64
+ */
65
+ export const MarkdownEditor = createSdkUiComponent("MarkdownEditor");
66
+ /**
67
+ * Renders a definition-list of label/value pairs.
68
+ *
69
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
70
+ */
71
+ export const KeyValueList = createSdkUiComponent("KeyValueList");
72
+ /**
73
+ * Row of action buttons wired to the plugin bridge's `performAction` handlers.
74
+ *
75
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
76
+ */
77
+ export const ActionBar = createSdkUiComponent("ActionBar");
78
+ /**
79
+ * Scrollable, timestamped log output viewer.
80
+ *
81
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
82
+ */
83
+ export const LogView = createSdkUiComponent("LogView");
84
+ /**
85
+ * Collapsible JSON tree for debugging or raw data inspection.
86
+ *
87
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
88
+ */
89
+ export const JsonTree = createSdkUiComponent("JsonTree");
90
+ /**
91
+ * Loading indicator.
92
+ *
93
+ * @see PLUGIN_SPEC.md §19.6 — Shared Components
94
+ */
95
+ export const Spinner = createSdkUiComponent("Spinner");
96
+ /**
97
+ * React error boundary that prevents plugin rendering errors from crashing
98
+ * the host page.
99
+ *
100
+ * @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
101
+ */
102
+ export const ErrorBoundary = createSdkUiComponent("ErrorBoundary");
103
+ /**
104
+ * Renders the host file tree component with a stable plugin-safe prop surface.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * import { FileTree, type FileTreeNode } from "@evermore.work/plugin-sdk/ui";
109
+ *
110
+ * const nodes: FileTreeNode[] = [
111
+ * { name: "README.md", path: "README.md", kind: "file", children: [] },
112
+ * ];
113
+ *
114
+ * <FileTree nodes={nodes} onSelectFile={(path) => console.log(path)} />;
115
+ * ```
116
+ */
117
+ export const FileTree = createSdkUiComponent("FileTree");
118
+ /**
119
+ * Renders Evermore's native issue list component for company-scoped plugin
120
+ * pages that need a standard board issue view.
121
+ */
122
+ export const IssuesList = createSdkUiComponent("IssuesList");
123
+ /**
124
+ * Renders the same host assignee picker used by the new issue pane.
125
+ */
126
+ export const AssigneePicker = createSdkUiComponent("AssigneePicker");
127
+ /**
128
+ * Renders the same host project picker used by the new issue pane.
129
+ */
130
+ export const ProjectPicker = createSdkUiComponent("ProjectPicker");
131
+ /**
132
+ * Renders Evermore's native managed routines list for plugin settings pages.
133
+ */
134
+ export const ManagedRoutinesList = createSdkUiComponent("ManagedRoutinesList");
135
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAmbpD,8EAA8E;AAC9E,uDAAuD;AACvD,8EAA8E;AAE9E,4EAA4E;AAC5E,4EAA4E;AAC5E,oEAAoE;AAEpE;;;;GAIG;AACH,SAAS,oBAAoB,CAAS,IAAY;IAChD,OAAO,SAAS,sBAAsB,CAAC,KAAa;QAClD,OAAO,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAoB,CAAC;IAC9D,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,oBAAoB,CAAkB,YAAY,CAAC,CAAC;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAmB,aAAa,CAAC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,oBAAoB,CAAiB,WAAW,CAAC,CAAC;AAE3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAuB,iBAAiB,CAAC,CAAC;AAE7F;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAqB,eAAe,CAAC,CAAC;AAEvF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAsB,gBAAgB,CAAC,CAAC;AAE1F;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAoB,cAAc,CAAC,CAAC;AAEpF;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,oBAAoB,CAAiB,WAAW,CAAC,CAAC;AAE3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,oBAAoB,CAAe,SAAS,CAAC,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAgB,UAAU,CAAC,CAAC;AAExE;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,oBAAoB,CAAe,SAAS,CAAC,CAAC;AAErE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAqB,eAAe,CAAC,CAAC;AAEvF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAgB,UAAU,CAAC,CAAC;AAExE;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,oBAAoB,CAAkB,YAAY,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAsB,gBAAgB,CAAC,CAAC;AAE1F;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAqB,eAAe,CAAC,CAAC;AAEvF;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAA2B,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1,155 @@
1
+ import type { PluginDataResult, PluginActionFn, HostLocation, HostNavigation, PluginHostContext, PluginStreamResult, PluginToastFn } from "./types.js";
2
+ /**
3
+ * Fetch data from the plugin worker's registered `getData` handler.
4
+ *
5
+ * Calls `ctx.data.register(key, handler)` in the worker and returns the
6
+ * result as reactive state. Re-fetches when `params` changes.
7
+ *
8
+ * @template T The expected shape of the returned data
9
+ * @param key - The data key matching the handler registered with `ctx.data.register()`
10
+ * @param params - Optional parameters forwarded to the handler
11
+ * @returns `PluginDataResult<T>` with `data`, `loading`, `error`, and `refresh`
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function SyncWidget({ context }: PluginWidgetProps) {
16
+ * const { data, loading, error } = usePluginData<SyncHealth>("sync-health", {
17
+ * companyId: context.companyId,
18
+ * });
19
+ *
20
+ * if (loading) return <div>Loading…</div>;
21
+ * if (error) return <div>Error: {error.message}</div>;
22
+ * return <div>Synced Issues: {data!.syncedCount}</div>;
23
+ * }
24
+ * ```
25
+ *
26
+ * @see PLUGIN_SPEC.md §13.8 — `getData`
27
+ * @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
28
+ */
29
+ export declare function usePluginData<T = unknown>(key: string, params?: Record<string, unknown>): PluginDataResult<T>;
30
+ /**
31
+ * Get a callable function that invokes the plugin worker's registered
32
+ * `performAction` handler.
33
+ *
34
+ * The returned function is async and throws a `PluginBridgeError` on failure.
35
+ *
36
+ * @param key - The action key matching the handler registered with `ctx.actions.register()`
37
+ * @returns An async function that sends the action to the worker and resolves with the result
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * function ResyncButton({ context }: PluginWidgetProps) {
42
+ * const resync = usePluginAction("resync");
43
+ * const [error, setError] = useState<string | null>(null);
44
+ *
45
+ * async function handleClick() {
46
+ * try {
47
+ * await resync({ companyId: context.companyId });
48
+ * } catch (err) {
49
+ * setError((err as PluginBridgeError).message);
50
+ * }
51
+ * }
52
+ *
53
+ * return <button onClick={handleClick}>Resync Now</button>;
54
+ * }
55
+ * ```
56
+ *
57
+ * @see PLUGIN_SPEC.md §13.9 — `performAction`
58
+ * @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
59
+ */
60
+ export declare function usePluginAction(key: string): PluginActionFn;
61
+ /**
62
+ * Read the current host context (active company, project, entity, user).
63
+ *
64
+ * Use this to know which context the plugin component is being rendered in
65
+ * so you can scope data requests and actions accordingly.
66
+ *
67
+ * @returns The current `PluginHostContext`
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * function IssueTab() {
72
+ * const { companyId, entityId } = useHostContext();
73
+ * const { data } = usePluginData("linear-link", { issueId: entityId });
74
+ * return <div>{data?.linearIssueUrl}</div>;
75
+ * }
76
+ * ```
77
+ *
78
+ * @see PLUGIN_SPEC.md §19 — UI Extension Model
79
+ */
80
+ export declare function useHostContext(): PluginHostContext;
81
+ /**
82
+ * Navigate within the Evermore host without forcing a full document reload.
83
+ *
84
+ * Use `linkProps()` for links so browser-native behavior still works:
85
+ * modifier-click, middle-click, copy-link, and open-in-new-tab all use the
86
+ * returned real `href`.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * function WikiSidebarLink() {
91
+ * const hostNavigation = useHostNavigation();
92
+ * return <a {...hostNavigation.linkProps("/wiki")}>Wiki</a>;
93
+ * }
94
+ * ```
95
+ */
96
+ export declare function useHostNavigation(): HostNavigation;
97
+ /**
98
+ * Observe the current host router location.
99
+ *
100
+ * Returns a snapshot of the active `pathname`, `search`, and `hash`. The
101
+ * component re-renders when any of these change (e.g. after the host router
102
+ * pushes a new entry, or after the browser back/forward gestures). Use this
103
+ * for URL-driven plugin UI such as a takeover sidebar with section-aware
104
+ * active state.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * function WikiSection() {
109
+ * const { pathname } = useHostLocation();
110
+ * const section = pathname.split("/").filter(Boolean).at(-1) ?? "wiki";
111
+ * return <div>Active section: {section}</div>;
112
+ * }
113
+ * ```
114
+ */
115
+ export declare function useHostLocation(): HostLocation;
116
+ /**
117
+ * Subscribe to a real-time event stream pushed from the plugin worker.
118
+ *
119
+ * Opens an SSE connection to `GET /api/plugins/:pluginId/bridge/stream/:channel`
120
+ * and accumulates events as they arrive. The worker pushes events using
121
+ * `ctx.streams.emit(channel, event)`.
122
+ *
123
+ * @template T The expected shape of each streamed event
124
+ * @param channel - The stream channel name (must match what the worker uses in `ctx.streams.emit`)
125
+ * @param options - Optional configuration for the stream
126
+ * @returns `PluginStreamResult<T>` with `events`, `lastEvent`, connection status, and `close()`
127
+ *
128
+ * @example
129
+ * ```tsx
130
+ * function ChatMessages() {
131
+ * const { events, connected, close } = usePluginStream<ChatToken>("chat-stream");
132
+ *
133
+ * return (
134
+ * <div>
135
+ * {events.map((e, i) => <span key={i}>{e.text}</span>)}
136
+ * {connected && <span className="pulse" />}
137
+ * <button onClick={close}>Stop</button>
138
+ * </div>
139
+ * );
140
+ * }
141
+ * ```
142
+ *
143
+ * @see PLUGIN_SPEC.md §19.8 — Real-Time Streaming
144
+ */
145
+ export declare function usePluginStream<T = unknown>(channel: string, options?: {
146
+ companyId?: string;
147
+ }): PluginStreamResult<T>;
148
+ /**
149
+ * Trigger a host toast notification from plugin UI.
150
+ *
151
+ * This lets plugin pages and widgets surface user-facing feedback through the
152
+ * same toast system as the host app without reaching into host internals.
153
+ */
154
+ export declare function usePluginToast(): PluginToastFn;
155
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/ui/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACd,MAAM,YAAY,CAAC;AAOpB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,EACvC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,gBAAgB,CAAC,CAAC,CAAC,CAKrB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAG3D;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAGlD;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAGlD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EACzC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,kBAAkB,CAAC,CAAC,CAAC,CAKvB;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,aAAa,CAG9C"}
@@ -0,0 +1,195 @@
1
+ import { getSdkUiRuntimeValue } from "./runtime.js";
2
+ // ---------------------------------------------------------------------------
3
+ // usePluginData
4
+ // ---------------------------------------------------------------------------
5
+ /**
6
+ * Fetch data from the plugin worker's registered `getData` handler.
7
+ *
8
+ * Calls `ctx.data.register(key, handler)` in the worker and returns the
9
+ * result as reactive state. Re-fetches when `params` changes.
10
+ *
11
+ * @template T The expected shape of the returned data
12
+ * @param key - The data key matching the handler registered with `ctx.data.register()`
13
+ * @param params - Optional parameters forwarded to the handler
14
+ * @returns `PluginDataResult<T>` with `data`, `loading`, `error`, and `refresh`
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function SyncWidget({ context }: PluginWidgetProps) {
19
+ * const { data, loading, error } = usePluginData<SyncHealth>("sync-health", {
20
+ * companyId: context.companyId,
21
+ * });
22
+ *
23
+ * if (loading) return <div>Loading…</div>;
24
+ * if (error) return <div>Error: {error.message}</div>;
25
+ * return <div>Synced Issues: {data!.syncedCount}</div>;
26
+ * }
27
+ * ```
28
+ *
29
+ * @see PLUGIN_SPEC.md §13.8 — `getData`
30
+ * @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
31
+ */
32
+ export function usePluginData(key, params) {
33
+ const impl = getSdkUiRuntimeValue("usePluginData");
34
+ return impl(key, params);
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // usePluginAction
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Get a callable function that invokes the plugin worker's registered
41
+ * `performAction` handler.
42
+ *
43
+ * The returned function is async and throws a `PluginBridgeError` on failure.
44
+ *
45
+ * @param key - The action key matching the handler registered with `ctx.actions.register()`
46
+ * @returns An async function that sends the action to the worker and resolves with the result
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * function ResyncButton({ context }: PluginWidgetProps) {
51
+ * const resync = usePluginAction("resync");
52
+ * const [error, setError] = useState<string | null>(null);
53
+ *
54
+ * async function handleClick() {
55
+ * try {
56
+ * await resync({ companyId: context.companyId });
57
+ * } catch (err) {
58
+ * setError((err as PluginBridgeError).message);
59
+ * }
60
+ * }
61
+ *
62
+ * return <button onClick={handleClick}>Resync Now</button>;
63
+ * }
64
+ * ```
65
+ *
66
+ * @see PLUGIN_SPEC.md §13.9 — `performAction`
67
+ * @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
68
+ */
69
+ export function usePluginAction(key) {
70
+ const impl = getSdkUiRuntimeValue("usePluginAction");
71
+ return impl(key);
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // useHostContext
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Read the current host context (active company, project, entity, user).
78
+ *
79
+ * Use this to know which context the plugin component is being rendered in
80
+ * so you can scope data requests and actions accordingly.
81
+ *
82
+ * @returns The current `PluginHostContext`
83
+ *
84
+ * @example
85
+ * ```tsx
86
+ * function IssueTab() {
87
+ * const { companyId, entityId } = useHostContext();
88
+ * const { data } = usePluginData("linear-link", { issueId: entityId });
89
+ * return <div>{data?.linearIssueUrl}</div>;
90
+ * }
91
+ * ```
92
+ *
93
+ * @see PLUGIN_SPEC.md §19 — UI Extension Model
94
+ */
95
+ export function useHostContext() {
96
+ const impl = getSdkUiRuntimeValue("useHostContext");
97
+ return impl();
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // useHostNavigation
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Navigate within the Evermore host without forcing a full document reload.
104
+ *
105
+ * Use `linkProps()` for links so browser-native behavior still works:
106
+ * modifier-click, middle-click, copy-link, and open-in-new-tab all use the
107
+ * returned real `href`.
108
+ *
109
+ * @example
110
+ * ```tsx
111
+ * function WikiSidebarLink() {
112
+ * const hostNavigation = useHostNavigation();
113
+ * return <a {...hostNavigation.linkProps("/wiki")}>Wiki</a>;
114
+ * }
115
+ * ```
116
+ */
117
+ export function useHostNavigation() {
118
+ const impl = getSdkUiRuntimeValue("useHostNavigation");
119
+ return impl();
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // useHostLocation
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * Observe the current host router location.
126
+ *
127
+ * Returns a snapshot of the active `pathname`, `search`, and `hash`. The
128
+ * component re-renders when any of these change (e.g. after the host router
129
+ * pushes a new entry, or after the browser back/forward gestures). Use this
130
+ * for URL-driven plugin UI such as a takeover sidebar with section-aware
131
+ * active state.
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * function WikiSection() {
136
+ * const { pathname } = useHostLocation();
137
+ * const section = pathname.split("/").filter(Boolean).at(-1) ?? "wiki";
138
+ * return <div>Active section: {section}</div>;
139
+ * }
140
+ * ```
141
+ */
142
+ export function useHostLocation() {
143
+ const impl = getSdkUiRuntimeValue("useHostLocation");
144
+ return impl();
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // usePluginStream
148
+ // ---------------------------------------------------------------------------
149
+ /**
150
+ * Subscribe to a real-time event stream pushed from the plugin worker.
151
+ *
152
+ * Opens an SSE connection to `GET /api/plugins/:pluginId/bridge/stream/:channel`
153
+ * and accumulates events as they arrive. The worker pushes events using
154
+ * `ctx.streams.emit(channel, event)`.
155
+ *
156
+ * @template T The expected shape of each streamed event
157
+ * @param channel - The stream channel name (must match what the worker uses in `ctx.streams.emit`)
158
+ * @param options - Optional configuration for the stream
159
+ * @returns `PluginStreamResult<T>` with `events`, `lastEvent`, connection status, and `close()`
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * function ChatMessages() {
164
+ * const { events, connected, close } = usePluginStream<ChatToken>("chat-stream");
165
+ *
166
+ * return (
167
+ * <div>
168
+ * {events.map((e, i) => <span key={i}>{e.text}</span>)}
169
+ * {connected && <span className="pulse" />}
170
+ * <button onClick={close}>Stop</button>
171
+ * </div>
172
+ * );
173
+ * }
174
+ * ```
175
+ *
176
+ * @see PLUGIN_SPEC.md §19.8 — Real-Time Streaming
177
+ */
178
+ export function usePluginStream(channel, options) {
179
+ const impl = getSdkUiRuntimeValue("usePluginStream");
180
+ return impl(channel, options);
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // usePluginToast
184
+ // ---------------------------------------------------------------------------
185
+ /**
186
+ * Trigger a host toast notification from plugin UI.
187
+ *
188
+ * This lets plugin pages and widgets surface user-facing feedback through the
189
+ * same toast system as the host app without reaching into host internals.
190
+ */
191
+ export function usePluginToast() {
192
+ const impl = getSdkUiRuntimeValue("usePluginToast");
193
+ return impl();
194
+ }
195
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/ui/hooks.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAW,EACX,MAAgC;IAEhC,MAAM,IAAI,GAAG,oBAAoB,CAE/B,eAAe,CAAC,CAAC;IACnB,OAAO,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,oBAAoB,CAAsC,iBAAiB,CAAC,CAAC;IAC1F,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,oBAAoB,CAA0B,gBAAgB,CAAC,CAAC;IAC7E,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,IAAI,GAAG,oBAAoB,CAAuB,mBAAmB,CAAC,CAAC;IAC7E,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,oBAAoB,CAAqB,iBAAiB,CAAC,CAAC;IACzE,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAAgC;IAEhC,MAAM,IAAI,GAAG,oBAAoB,CAE/B,iBAAiB,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,oBAAoB,CAAsB,gBAAgB,CAAC,CAAC;IACzE,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `@evermore.work/plugin-sdk/ui` — Evermore plugin UI SDK.
3
+ *
4
+ * Import this subpath from plugin UI bundles (React components that run in
5
+ * the host frontend). Do **not** import this from plugin worker code.
6
+ *
7
+ * The worker-side SDK is available from `@evermore.work/plugin-sdk` (root).
8
+ *
9
+ * @see PLUGIN_SPEC.md §19.0.1 — Plugin UI SDK
10
+ * @see PLUGIN_SPEC.md §29.2 — SDK Versioning
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Plugin UI bundle entry (dist/ui/index.tsx)
15
+ * import { usePluginData, usePluginAction } from "@evermore.work/plugin-sdk/ui";
16
+ * import type { PluginWidgetProps } from "@evermore.work/plugin-sdk/ui";
17
+ *
18
+ * export function DashboardWidget({ context }: PluginWidgetProps) {
19
+ * const { data, loading, error } = usePluginData("sync-health", {
20
+ * companyId: context.companyId,
21
+ * });
22
+ * const resync = usePluginAction("resync");
23
+ *
24
+ * if (loading) return <div>Loading…</div>;
25
+ * if (error) return <div>Error: {error.message}</div>;
26
+ *
27
+ * return (
28
+ * <div style={{ display: "grid", gap: 8 }}>
29
+ * <strong>Synced Issues</strong>
30
+ * <div>{data!.syncedCount}</div>
31
+ * <button onClick={() => resync({ companyId: context.companyId })}>
32
+ * Resync Now
33
+ * </button>
34
+ * </div>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ /**
40
+ * Bridge hooks for plugin UI components to communicate with the plugin worker.
41
+ *
42
+ * - `usePluginData(key, params)` — fetch data from the worker's `getData` handler
43
+ * - `usePluginAction(key)` — get a callable that invokes the worker's `performAction` handler
44
+ * - `useHostContext()` — read the current active company, project, entity, and user IDs
45
+ * - `useHostNavigation()` — navigate Evermore-internal links through the host router
46
+ * - `useHostLocation()` — observe the current host pathname/search/hash for URL-driven UI
47
+ * - `usePluginStream(channel)` — subscribe to real-time SSE events from the worker
48
+ */
49
+ export { usePluginData, usePluginAction, useHostContext, useHostNavigation, useHostLocation, usePluginStream, usePluginToast, } from "./hooks.js";
50
+ export { MetricCard, StatusBadge, DataTable, TimeseriesChart, MarkdownBlock, MarkdownEditor, KeyValueList, ActionBar, LogView, JsonTree, Spinner, ErrorBoundary, FileTree, IssuesList, AssigneePicker, ProjectPicker, ManagedRoutinesList, } from "./components.js";
51
+ export type { MetricTrend, MetricCardProps, StatusBadgeVariant, StatusBadgeProps, DataTableColumn, DataTableProps, TimeseriesDataPoint, TimeseriesChartProps, MarkdownBlockProps, MarkdownEditorProps, KeyValuePair, KeyValueListProps, ActionBarItem, ActionBarProps, LogViewEntry, LogViewProps, JsonTreeProps, SpinnerProps, ErrorBoundaryProps, FileTreeNode, FileTreeBadgeVariant, FileTreeBadge, FileTreeTone, FileTreeEmptyState, FileTreeErrorState, FileTreePathCollection, FileTreeProps, IssuesListFilters, IssuesListProps, AssigneePickerSelection, AssigneePickerProps, ProjectPickerProps, ManagedRoutineMissingRef, ManagedRoutinesListAgent, ManagedRoutinesListItem, ManagedRoutinesListProject, ManagedRoutinesListProps, } from "./components.js";
52
+ export type { PluginBridgeError, PluginBridgeErrorCode, HostNavigation, HostNavigationOptions, HostNavigationLinkOptions, HostNavigationLinkProps, HostLocation, PluginHostContext, PluginModalBoundsRequest, PluginRenderCloseEvent, PluginRenderCloseHandler, PluginRenderCloseLifecycle, PluginRenderEnvironmentContext, PluginLauncherBounds, PluginLauncherRenderEnvironment, PluginDataResult, PluginActionFn, PluginStreamResult, PluginToastTone, PluginToastAction, PluginToastInput, PluginToastFn, } from "./types.js";
53
+ export type { PluginPageProps, PluginWidgetProps, PluginDetailTabProps, PluginSidebarProps, PluginRouteSidebarProps, PluginProjectSidebarItemProps, PluginCommentAnnotationProps, PluginCommentContextMenuItemProps, PluginSettingsPageProps, } from "./types.js";
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;;;;;;GASG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,eAAe,EACf,aAAa,EACb,cAAc,EACd,YAAY,EACZ,SAAS,EACT,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAa,EACb,QAAQ,EACR,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,oBAAoB,EACpB,+BAA+B,EAC/B,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,EAC5B,iCAAiC,EACjC,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `@evermore.work/plugin-sdk/ui` — Evermore plugin UI SDK.
3
+ *
4
+ * Import this subpath from plugin UI bundles (React components that run in
5
+ * the host frontend). Do **not** import this from plugin worker code.
6
+ *
7
+ * The worker-side SDK is available from `@evermore.work/plugin-sdk` (root).
8
+ *
9
+ * @see PLUGIN_SPEC.md §19.0.1 — Plugin UI SDK
10
+ * @see PLUGIN_SPEC.md §29.2 — SDK Versioning
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Plugin UI bundle entry (dist/ui/index.tsx)
15
+ * import { usePluginData, usePluginAction } from "@evermore.work/plugin-sdk/ui";
16
+ * import type { PluginWidgetProps } from "@evermore.work/plugin-sdk/ui";
17
+ *
18
+ * export function DashboardWidget({ context }: PluginWidgetProps) {
19
+ * const { data, loading, error } = usePluginData("sync-health", {
20
+ * companyId: context.companyId,
21
+ * });
22
+ * const resync = usePluginAction("resync");
23
+ *
24
+ * if (loading) return <div>Loading…</div>;
25
+ * if (error) return <div>Error: {error.message}</div>;
26
+ *
27
+ * return (
28
+ * <div style={{ display: "grid", gap: 8 }}>
29
+ * <strong>Synced Issues</strong>
30
+ * <div>{data!.syncedCount}</div>
31
+ * <button onClick={() => resync({ companyId: context.companyId })}>
32
+ * Resync Now
33
+ * </button>
34
+ * </div>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ /**
40
+ * Bridge hooks for plugin UI components to communicate with the plugin worker.
41
+ *
42
+ * - `usePluginData(key, params)` — fetch data from the worker's `getData` handler
43
+ * - `usePluginAction(key)` — get a callable that invokes the worker's `performAction` handler
44
+ * - `useHostContext()` — read the current active company, project, entity, and user IDs
45
+ * - `useHostNavigation()` — navigate Evermore-internal links through the host router
46
+ * - `useHostLocation()` — observe the current host pathname/search/hash for URL-driven UI
47
+ * - `usePluginStream(channel)` — subscribe to real-time SSE events from the worker
48
+ */
49
+ export { usePluginData, usePluginAction, useHostContext, useHostNavigation, useHostLocation, usePluginStream, usePluginToast, } from "./hooks.js";
50
+ export { MetricCard, StatusBadge, DataTable, TimeseriesChart, MarkdownBlock, MarkdownEditor, KeyValueList, ActionBar, LogView, JsonTree, Spinner, ErrorBoundary, FileTree, IssuesList, AssigneePicker, ProjectPicker, ManagedRoutinesList, } from "./components.js";
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;;;;;;GASG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,eAAe,EACf,aAAa,EACb,cAAc,EACd,YAAY,EACZ,SAAS,EACT,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAa,EACb,QAAQ,EACR,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,GACpB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function getSdkUiRuntimeValue<T>(name: string): T;
2
+ export declare function renderSdkUiComponent<TProps>(name: string, props: TProps): unknown;
3
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/ui/runtime.ts"],"names":[],"mappings":"AAsBA,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAMvD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EACzC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAiBT"}
@@ -0,0 +1,30 @@
1
+ function getBridgeRegistry() {
2
+ return globalThis.__evermorePluginBridge__;
3
+ }
4
+ function missingBridgeValueError(name) {
5
+ return new Error(`Evermore plugin UI runtime is not initialized for "${name}". ` +
6
+ 'Ensure the host loaded the plugin bridge before rendering this UI module.');
7
+ }
8
+ export function getSdkUiRuntimeValue(name) {
9
+ const value = getBridgeRegistry()?.sdkUi?.[name];
10
+ if (value === undefined) {
11
+ throw missingBridgeValueError(name);
12
+ }
13
+ return value;
14
+ }
15
+ export function renderSdkUiComponent(name, props) {
16
+ const registry = getBridgeRegistry();
17
+ const component = registry?.sdkUi?.[name];
18
+ if (component === undefined) {
19
+ throw missingBridgeValueError(name);
20
+ }
21
+ const createElement = registry?.react?.createElement;
22
+ if (typeof createElement === "function") {
23
+ return createElement(component, props);
24
+ }
25
+ if (typeof component === "function") {
26
+ return component(props);
27
+ }
28
+ throw new Error(`Evermore plugin UI component "${name}" is not callable`);
29
+ }
30
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/ui/runtime.ts"],"names":[],"mappings":"AAWA,SAAS,iBAAiB;IACxB,OAAQ,UAA2B,CAAC,wBAAwB,CAAC;AAC/D,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO,IAAI,KAAK,CACd,sDAAsD,IAAI,KAAK;QAC7D,2EAA2E,CAC9E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,IAAY;IAClD,MAAM,KAAK,GAAG,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC;IACjD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,KAAa;IAEb,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,QAAQ,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,aAAa,GAAG,QAAQ,EAAE,KAAK,EAAE,aAAa,CAAC;IACrD,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE,CAAC;QACxC,OAAO,aAAa,CAAC,SAAS,EAAE,KAAgC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,mBAAmB,CAAC,CAAC;AAC5E,CAAC"}