SDK 与脚手架目前通过源码仓库使用,npm 发布暂缓。文档中的 registry 安装命令用于后续正式版本;当前预览请使用「第一个插件」中的源码流程。

插件文档

自定义画布类型

注册一种新卡片:带校验与版本的状态、用于搜索和导出的摘要,以及带宿主备份的迁移。

自定义画布类型是用户可以放到画布上的卡片。插件拥有组件与状态形状;GhostBoard 拥有持久化、revision、搜索索引、预览与插件缺失时的回退。

注册类型与核心节点类型

registerType 注册出来的始终是 extension 节点。它不是定义、替换或冒充宿主自带核心节点类型的手段——卡片、便签、图片、Folder、Group 与主题(Topic)都归宿主所有。

这个区别对 主题(Topic) 最重要:它是 GhostBoard 的轻量思维导图节点,标题内嵌在画布 manifest 中,父子层级完全存放在宿主管理的树边上,排布也由宿主负责。没有任何 SDK 方法可以读取主题标题、创建子主题、改变分支父级或干预其排布,也没有事件报告主题结构变化。请用 registerType 承载插件自己的版本化实例状态;卡片内部需要层级时,在自己的状态里建模,不要依赖宿主的树。

最小计数卡片

import { createElement } from "react";

function CounterCard({ context, state, updateState }) {
  return createElement(
    "button",
    {
      type: "button",
      disabled: context.readOnly,
      onClick: () => updateState({ count: state.count + 1 }),
    },
    `Count: ${state.count}`,
  );
}

app.canvas.registerType({
  id: "counter",
  title: "Counter",
  dataVersion: 1,
  component: CounterCard,
  createInitialState: () => ({ count: 0 }),
  validateState: (state) =>
    Boolean(
      state &&
      typeof state === "object" &&
      !Array.isArray(state) &&
      typeof state.count === "number",
    ),
  summarize: (state) => ({
    title: `Counter: ${state.count}`,
    plainText: `Counter value ${state.count}`,
  }),
});

持久化的类型名是 <pluginId>/<id>。createElement 避免了构建步骤;--react 与 --combined 模板展示 JSX 和更丰富的计分卡。

契约

成员 作用
dataVersion 状态形状的整数版本。形状变化时递增。
createInitialState() 新放置卡片的状态。
validateState(state) 类型守卫。持久化状态是不可信输入;拒绝任何意外形状。
migrateState(state, fromVersion) 把旧状态转换为当前形状。提高 dataVersion 时必需。
summarize(state) title 与 plainText,用于搜索、无障碍、预览、导出,以及插件缺失或停用时。
component / previewComponent 在卡片内渲染,以及(可选)在紧凑预览中渲染。
surface 默认 / 最小 / 最大尺寸、resizable,以及 openBehavior: 'detail' 获得聚焦视图。

updateState(next) 提交新的 revision 并返回它。只在瞬时交互帧(拖动滑块)中传 { history: 'ephemeral' },并以一次记录的更新收尾,让撤销有合理的步长。

插件缺失时会怎样

插件停用或卸载后,卡片保留其状态、摘要与搜索文本。用户看到摘要占位;什么都不会被删除。重新启用插件即恢复实时组件。

迁移与恢复

加载 dataVersion 较旧的卡片时,GhostBoard 先写入 preimage(宿主拥有的旧状态备份),再调用 migrateState 并以新 revision 保存结果。如果备份写入失败,迁移不会运行。降级插件永远不会用旧形状覆盖数据;遇到 revision 冲突的迁移不会触碰实时数据。

用户可以在设置 → 插件中恢复最近的 preimage(恢复迁移前备份)。恢复会停用插件并保留一份当前数据的保护副本。把迁移设计成旧状态的纯函数,并保持 validateState 严格,让坏迁移大声失败而不是持久化垃圾。

只有画布卡片状态有这份备份契约。插件键值存储没有;请在那里自行给值加版本。

测试

createFakeHost 通过 harness.inspection.canvasTypes 暴露已注册类型。直接单元测试 validateState、migrateState 与 summarize;渲染与持久化在打包后的 GhostBoard 中验证。