Clarify plugin authoring and external dev workflow

This commit is contained in:
Dotta
2026-03-14 10:40:21 -05:00
parent cb5d7e76fb
commit 30888759f2
36 changed files with 693 additions and 410 deletions

View File

@@ -12,7 +12,6 @@
* @see PLUGIN_SPEC.md §19.0.2 — Bundle Isolation
*/
import type { ReactNode } from "react";
import {
usePluginData,
usePluginAction,
@@ -60,61 +59,11 @@ export function initPluginBridge(
react,
reactDom,
sdkUi: {
// Bridge hooks
usePluginData,
usePluginAction,
useHostContext,
usePluginStream,
usePluginToast,
// Placeholder shared UI components — plugins that use these will get
// functional stubs. Full implementations matching the host's design
// system can be added later.
MetricCard: createStubComponent("MetricCard"),
StatusBadge: createStubComponent("StatusBadge"),
DataTable: createStubComponent("DataTable"),
TimeseriesChart: createStubComponent("TimeseriesChart"),
MarkdownBlock: createStubComponent("MarkdownBlock"),
KeyValueList: createStubComponent("KeyValueList"),
ActionBar: createStubComponent("ActionBar"),
LogView: createStubComponent("LogView"),
JsonTree: createStubComponent("JsonTree"),
Spinner: createStubComponent("Spinner"),
ErrorBoundary: createPassthroughComponent("ErrorBoundary"),
},
};
}
// ---------------------------------------------------------------------------
// Stub component helpers
// ---------------------------------------------------------------------------
function createStubComponent(name: string): unknown {
const fn = (props: Record<string, unknown>) => {
// Import React from the registry to avoid import issues
const React = globalThis.__paperclipPluginBridge__?.react as typeof import("react") | undefined;
if (!React) return null;
return React.createElement("div", {
"data-plugin-component": name,
style: {
padding: "8px",
border: "1px dashed #666",
borderRadius: "4px",
fontSize: "12px",
color: "#888",
},
}, `[${name}]`);
};
Object.defineProperty(fn, "name", { value: name });
return fn;
}
function createPassthroughComponent(name: string): unknown {
const fn = (props: { children?: ReactNode }) => {
const ReactLib = globalThis.__paperclipPluginBridge__?.react as typeof import("react") | undefined;
if (!ReactLib) return null;
return ReactLib.createElement(ReactLib.Fragment, null, props.children);
};
Object.defineProperty(fn, "name", { value: name });
return fn;
}

View File

@@ -257,14 +257,8 @@ function getShimBlobUrl(specifier: "react" | "react-dom" | "react-dom/client" |
case "sdk-ui":
source = `
const SDK = globalThis.__paperclipPluginBridge__?.sdkUi ?? {};
const { usePluginData, usePluginAction, useHostContext, usePluginStream, usePluginToast,
MetricCard, StatusBadge, DataTable, TimeseriesChart,
MarkdownBlock, KeyValueList, ActionBar, LogView, JsonTree,
Spinner, ErrorBoundary } = SDK;
export { usePluginData, usePluginAction, useHostContext, usePluginStream, usePluginToast,
MetricCard, StatusBadge, DataTable, TimeseriesChart,
MarkdownBlock, KeyValueList, ActionBar, LogView, JsonTree,
Spinner, ErrorBoundary };
const { usePluginData, usePluginAction, useHostContext, usePluginStream, usePluginToast } = SDK;
export { usePluginData, usePluginAction, useHostContext, usePluginStream, usePluginToast };
`;
break;
}
@@ -294,8 +288,6 @@ function rewriteBareSpecifiers(source: string): string {
"'@paperclipai/plugin-sdk/ui'": `'${getShimBlobUrl("sdk-ui")}'`,
'"@paperclipai/plugin-sdk/ui/hooks"': `"${getShimBlobUrl("sdk-ui")}"`,
"'@paperclipai/plugin-sdk/ui/hooks'": `'${getShimBlobUrl("sdk-ui")}'`,
'"@paperclipai/plugin-sdk/ui/components"': `"${getShimBlobUrl("sdk-ui")}"`,
"'@paperclipai/plugin-sdk/ui/components'": `'${getShimBlobUrl("sdk-ui")}'`,
'"react/jsx-runtime"': `"${getShimBlobUrl("react/jsx-runtime")}"`,
"'react/jsx-runtime'": `'${getShimBlobUrl("react/jsx-runtime")}'`,
'"react-dom/client"': `"${getShimBlobUrl("react-dom/client")}"`,