PostHog Canvas 自由画布开发指南:从解析目标到受保护发布与构建的完整工作流
2026/9/14 6:37:02 网站建设 项目流程

PostHog Canvas 自由画布开发指南:从解析目标到受保护发布与构建的完整工作流

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

本篇指南基于 PostHog 仓库中 Canvas 产品的building-canvases技能文档,系统讲解如何为 PostHog 创建或编辑freeform自由画布——一种运行在沙箱 iframe 中的独立浏览器应用(数据看板、文档、表单、小工具、图形实验)。读完你可以掌握:如何解析或创建目标画布、如何在 React + Quill 与纯 HTML 两种实现路线间选型、图片资源的标准处理流程、"读 → 改 → 校验 → 发布 → 等构建"的迭代闭环,以及ph.stateph.actionsph.connectorsph.agent.request四个运行时桥接 API 的声明规则;并进一步结合后端模型与校验源码,理解平台契约(固定依赖、能力声明、CSP 沙箱)的落地机制。

画布是什么:源码存在 PostHog 里的浏览器应用

Canvas(画布)是一个客户端浏览器应用,运行在 PostHog 内部的沙箱 iframe中。与常规前端工程最本质的区别是:它的源码存放在 PostHog 里,而不是代码仓库中。你通过canvas-*系列工具读写它,通过工具发布才算保存——永远不要把画布写到本地文件。

画布工作可以从任何普通任务发起,不要求存在专门的画布模式或预先创建的画布。只要用户要求"一个应该住在 PostHog 里的看板、文档、表单、可视化或小应用",就应当按画布请求处理。

PostHog 中画布分为三种类型(对应后端模型Canvas.kind字段的三个取值,见 models.py):

类型含义由哪个技能负责
freeform独立应用,源项目编译为单个制品building-canvases(本文主体)
grid组件网格,包括用户的首页画布composing-grid-canvases
component可复用组件(widget),被 grid 画布放置composing-grid-canvases

building-canvases技能只拥有freeform画布。当目标是 grid 或首页画布、某个 placement 布局、或可复用组件时,应改加载 composing-grid-canvases——它拥有"商店搜索 → 配置 → fork → 构建"的阶梯与布局补丁循环。但注意:编写组件的源码仍然使用本文所述的实现类伴随技能。从源码结构看,这一划分与后端模型注释完全一致:grid的"源码"是布局文档,发布时校验并版本化布局而不排队构建;componentfreeform共用同一套源码/构建管线,额外携带配置 schema 与网格尺寸契约。

第一步:解析目标画布

在动笔写任何代码之前,先确定"写给哪个画布"。规则按优先级排列:

  1. 任务指定了 canvas id(画布发起的任务都会带上):那就是目标,不要再创建新的。
  2. 否则目标频道是任务创建时所在的频道——任务上下文(channel_context块或生成指令)中会写明频道名。用canvas-list工具(以channel参数限定范围)列出该频道的画布。如果其中有一个明显是所指对象(同一看板的早期迭代、同一工具的前身),就在其上继续构建而不是创建一个近似的重复品,并在回复中说明结果落在哪里。
  3. 只有当现有画布都不匹配时,才用canvas-create在同一频道创建新画布,名字取请求中简短的描述性标题——绝不能用 "Untitled canvas"。
  4. 永远不要自行浏览频道来挑选目标channel-list只用于把用户点名的频道解析为 id。它的列表会把个人频道#me排在最前,而#me永远不应作为默认值——放在那里的画布对其他人不可见。如果任务既没有点名画布也没有点名频道,直接询问用户用哪个频道,而不是猜测。

canvas-create支持kind参数:freeform(独立应用,默认)、component(可复用组件,其已发布项目必须声明component放置契约)、grid(组件组合,通过canvas-layout-patch编辑)。工具定义见 mcp/tools.yaml。

选择实现路线:加载伴随技能

building-canvases拥有"画布选择 + 创作生命周期",而实现契约放在四个伴随技能中。在写源码之前,先加载所有适用的伴随技能

  • building-react-quill-canvases——用于看板、数据板、表单、工具、应用式状态,或任何希望看起来"原生"于 PostHog 的场景。它拥有允许导入清单、Quill 组件组合、主题化、图表、加载/错误状态与日期选择器。
  • building-html-canvases——用于文档、文章、聚焦实验、生成式图形、<canvas>或 WebGL,即应用组件不带来任何有用结构的场合。它拥有语义化标记、直接浏览器 API、动画清理与非 Quill 主题化。
  • querying-canvas-data——只要画布要读 PostHog 数据、埋点或导航就必须加载。它拥有phSDK、"优先用已保存 insight"的数据层级、结果形状、变量、日期范围、按查询渐进加载与声明数据能力。涉及数据时,与任一实现技能一起加载。
  • validating-and-publishing-canvases——每个画布都要加载。它拥有项目形状、能力声明、校验诊断、受保护发布、草稿、构建与冲突恢复。

实现方式可以混搭:React 可以拥有应用外壳,而浏览器图形代码拥有画布元素;或一个基本静态的页面挂载一个交互式"岛屿"。这是一个判断决策而不是持久化模式——只有当选择改变了一个你无法推断的用户可见需求时,才向用户提问。

从源码结构看,两种路线共享同一入口约束:building-html-canvases 明确要求所有画布都保留src/canvas.tsx作为被挂载的 React 入口组件(默认导出、无 props),React 层保持薄壳,体验写在其中的 HTML/CSS/浏览器 API 里——纯 HTML 路线的文档就是"效果上是语义化 HTML 的 JSX"。

图片资源:走公共媒体库,不要 base64

画布中的图片使用公共媒体库 URL。标准流程:

  1. 先调用posthog:media-images-list(带purpose="canvas"),已有合适的图片就直接复用。
  2. 要添加本地图片时,调用posthog:media-image-upload-start,传文件名字段和purpose="canvas"
  3. 在 shell 中把文件以 multipart 表单数据 POST 到返回的upload_url。包含所有返回的form_fields条目,并把文件部分放在最后。
  4. 调用posthog:media-image-upload-complete,传回返回的 id,用其永久url作为图片src
  5. 把该 URL 的精确 origin 加入project.capabilities.network.origins。画布校验会检查这个声明,已发布制品会在其 Content Security Policy 中使用它。

约束与安全边界:

  • 画布媒体 URL 是公开的、不需要认证。绝不要上传秘密、凭证、客户数据或敏感截图。
  • 图片必须小于 4 MB,且可解码为 PNG、JPEG、GIF、WebP、AVIF 或 BMP。
  • 绝不把图片字节 base64 编码进工具调用。

从后端实现看,第 5 步之所以强制,是因为声明的 origin 会被直接拼进入口制品的 CSP:contract.py 中artifact_csp()把通过canonical_network_origin()校验的 origin 注入connect-srcstyle-srcimg-srcfont-srcmedia-srcframe-src指令。而canonical_network_origin()本身是一道安全闸:只接受精确 HTTPS origin(无路径、无凭证、无查询/片段、无通配符),并且主机必须是公共的——回环地址、私有 IP、单标签域名(如intranet)、以及.local/.localhost/.internal/.home.arpa后缀全部被拒绝。因此像https://localhost:8010这样的本地开发主机会以invalid_network_origin诊断失败校验。

常见请求模式:路由示例而非固定模板

这些模式用于把请求路由到实现方案,不是必须套用的模板:

  • 产品看板、Web 分析板、指标浏览器:React + Quill + 数据查询(querying-canvas-data)。
  • 清单、表单、轻量工作流:React + Quill;如需 PostHog 读取、埋点或导航,再加数据查询。对于清单或 runbook,从building-react-quill-canvases的实例 references/checklist-example.md 起步——通过每步一个ph.state键实现团队共享进度。不要暗示现有 API 并不提供的持久化。
  • 文档或叙述式报告:HTML 提供基本静态的阅读体验;需要 PostHog 实时数据、过滤器或应用式交互时改用 React + Quill + 数据查询。
  • 生成式图形或动画:HTML + 浏览器图形 API;只有当 React 能实质简化应用状态或外壳时才加入 React。

如果任务携带诸如dashboardweb-analytics之类的遗留请求模式,应用上面匹配的形态。模式只是提示;用户的实际请求始终是权威。

迭代循环:读 → 改 → 校验 → 发布 → 等构建

这是整个技能的操作核心。完整循环如下:

1. 读取当前源码与版本指针

canvas-source-retrieve读取当前源码与版本指针,记住current_version_id——你的发布必须用它做守卫。返回的项目形状为:schemaVersion(1)、files(路径 → 内容)、entryHtml"index.html")、dependencies(平台固定精确版本)、canvasSdkVersioncapabilities。从未发布过的画布current_version_idnull,首次发布时原样传null

2. 按选定的实现技能编辑项目文件

对画布展示的任何 PostHog 数据,遵循querying-canvas-data技能:数据只走ph桥(已保存 insight 通过phSDK 加载——绝不自行fetch或自带 PostHog 客户端),且让每个数字可验证:insight 支撑的指标通过ph.openExternal链接其已保存 insight,ad-hoc 查询在卡片旁展示实际执行的精确查询。同时project.capabilities中声明每一个ph调用

  • insight 短 id 进capabilities.posthog.insights
  • 埋点事件名进capabilities.posthog.captureEvents
  • ad-hoc 查询置inlineQueries: true
  • ph.agent.requestagentRequests: true

宿主在运行时强制这些声明,校验拒绝未声明的调用。校验逻辑在 source.py 中可以逐条对照:_validate_capabilities()用正则扫描源码中的ph.loadInsight/ph.query/ph.capture/ph.state/ph.actions.invoke/ph.agent.request/ph.connectors.call调用点,与声明清单比对后产出capability_missing_insightcapability_missing_inline_queriescapability_missing_capture_eventcapability_missing_statecapability_missing_actioncapability_missing_agent_requestscapability_missing_connector等 error 级诊断。

3. 校验直到干净

canvas-validate-create无副作用,可以随需随调;修复所有 error 级诊断后才能发布。诊断条目带severity、稳定的codemessage,以及文件级问题的pathline。常见 error:import_not_allowed(裸导入被限制在源项目返回的依赖集内)、forbidden_dynamic_import/forbidden_require/forbidden_inline_scriptinvalid_path、各类capability_missing_*dependency_not_admitted/dependency_version_mismatchplatform_token_redeclared(声明了与 Quill 平台 token 同名的 CSS 变量——平台样式表把--background--border--muted--primary等设置在每个元素上,:roothtml.dark里同名声明永远到不了任何元素,导致文字颜色失效;自己的变量必须加前缀),以及路径/尺寸越限。warning 级(如network_fetch/network_xhr)不阻断,但意味着代码在直接伸手网络,应声明精确 origin 或改用ph桥。

4. 受保护地发布(publish 即保存,默认且立即生效)

发布是保存变更的默认方式,首次版本与后续编辑一视同仁,且立即生效:

  • 首版current_version_id为 null):用canvas-publish-create发布完整项目,传expected_current_version_id: null
  • 已上线current_version_id已设置):用canvas-edit-create按文件发布变更(每个 operation 设置某文件的完整内容,content: null表示删除),或用canvas-publish-create发布完整项目——都把当前current_version_id作为expected_current_version_id传入。canvas-edit-create的守卫是强制的,因为 diff 的语义依赖其基线。
  • 草稿canvas-draft-create仅在用户要求草稿、预览或上线前评审时暂存。草稿是一个真实的、可构建的版本但永远不会成为 head:在线画布继续渲染当前版本,直到有人 promote 它。草稿响应会返回capability_widening——草稿相对 live 版本新增声明的 insights、埋点事件、内联查询与网络 origin,应在 promote 前向用户明示(这是变更将新授予的访问面)。这个"能力扩张"信号由后端 capabilities.py 的capability_widening()计算,按"after 相对 before 新增了哪些声明"结构化输出。

发布约定:一次请求变更只发布一次;用户在之后要求再改时,重新读取源码(head 可能已移动)再发布——不要把无关变更打进同一版本,也不要在每次微编辑后发布半成品。429 表示团队构建容量暂时耗尽,等待约 30 秒后重试同一发布,此时尚未保存任何东西。发布响应返回新的current_version_id

版本语义:每次发布向 append-only 的版本序列追加一个完整源版本并移动 head 指针(CanvasSourceVersion行只增不改,内容存对象存储,行上记录 SHA-256source_hash、能力清单快照与任务归属,见 models.py)。用户可以在应用里回退到旧版本(回退会重新发布并重新构建)。守卫的意义正在于此:基于你实际读到的版本来发布,才能避免用户的回退、其他 agent 的发布和你的编辑互相静默抹除。

409 version_conflict 的恢复流程:409 意味着画布已越过你的基线(并发发布或回退),响应里包含 livecurrent_version_id。绝不无守卫重试硬推:重新canvas-source-retrieve读取源码 → 在新鲜源码上重新应用你的编辑(新 head 可能含他人变更,必须保留)→ 用新的current_version_id重新发布。

5. 等待构建完成

草稿和发布一样都会排队一个服务端构建。轮询canvas-builds-retrieve(每几秒一次,最长约 2 分钟),直到你的构建进入终态:

  • queued/building——进行中,稍后再轮询;
  • ready——画布的published_build_id前移到该构建(除非更新的发布已抢先取代它),画布工作完成;
  • failed——读取构建的错误诊断,修复项目,再次保存。不要带着失败的构建结束任务

从后端模型看为什么"失败的构建不会顶掉旧版":CanvasBuild注释明确写了——失败构建只记录诊断,永远不取代画布最后已知良好的制品;live 指针(Canvas.published_build)只在构建完成且其源版本仍是当前 head时才前移。这意味着如果你在这里收工,用户拿到的是陈旧画布加一次静默失败。

另有一条经验规则:运行时错误报告(渲染中画布抛错时上报到创作任务)会注明其来源构建 id。来自旧构建 id的报告是历史,不是你当前代码的证据——特别地,"某个已文档化的phAPI 未定义"(如ph.state)意味着该制品由旧宿主运行时打包,重新发布让当前构建替换它即可;永远不要通过删掉该 API 或其能力声明来"修复"。

运行时桥接:状态、动作、连接器与 agent 请求

自由画布通过宿主注入的ph桥与 PostHog 交互。以下四组 API 各有声明要求,未声明者校验失败、宿主运行时拒绝。

ph.state —— 持久键值记忆

  • API:ph.state.get(key, { scope })ph.state.set(key, value, { scope })(值为 null 删除键)、ph.state.list({ scope })
  • 作用域:"user"(默认)对每位查看者私有;"shared"每画布一个值、团队可见。使用的 scope 必须声明在capabilities.posthog.state
  • 约束:值是 JSON,序列化上限64 KB、每 scope 最多256 个键。大数据应存进 PostHog(insight、warehouse)再引用;state 里绝不放秘密或查看者 PII。

这两个数字不是文档随口说的:它们就是平台契约 manifest.json 中limits.maxStateValueBytes: 65536maxStateKeysPerScope: 256的落地,后端CanvasState模型在写入时执行边界约束,使每次访问都是点查、表增长以画布数为上限。

ph.actions.invoke(verb, payload) —— 以查看者身份写 PostHog

  • 每个 verb 必须声明在capabilities.posthog.actions;未声明或未注册的 verb 校验失败,宿主运行时也拒绝。
  • 只把动作接到显式用户手势(查看者点击的按钮),绝不接到 load 或 render。
  • 动作注册表是唯一事实来源:用canvases-actions-retrieve工具列出它,接线前遵循每个 verb 的usage(payload/结果形状、行为、它配什么样的确认文案)。

ph.connectors.call(provider, tool, args) —— 读取实时第三方数据

  • 查看者本人的连接(GitHub,或任意 MCP 商店服务器)在查看时读取数据。绝不要自己调用 GitHub、Calendly 或别的再把结果粘进源码:那样的快照在发布时就已陈旧,且会把作者的数据展示给每个查看者。
  • 每个 provider 与 tool 声明在capabilities.connectors;用canvas-connectors-retrieve工具发现它们。
  • 校验侧(_validate_connector_declarations/_validate_connector_calls):provider 必须是原生 id(如github)或mcp:<server host>形式;未知 provider、未注册的原生工具、私有 MCP 主机都会失败校验;每个声明的工具在目录中必须is_read_only: true。带 connectors 的画布不能声明 shared state(对应诊断connector_results_in_shared_state)。

ph.agent.request(prompt) —— 向创作 agent 请求变更

  • 声明capabilities.posthog.agentRequests: true
  • 只能从直接点击或表单提交调用——宿主会展示精确 prompt 并要求查看者接受后才消耗算力;渲染、挂载或轮询期间发起的调用会被拒绝。
  • agent 以新版本发布该变更;非创建者的请求会被归档到创作任务线程而不是直接启动运行。

源项目形状与平台契约

源码项目本身受一组平台契约约束,其单一事实来源是 canvas_builder/manifest.json——它同时被 Node 构建器、Python 校验器(source.py)与制品源的 CSP 加载;桌面端应用还会在契约测试中用它断言自己的副本,防止固定依赖或限制漂移。

项目结构约定(来自building-canvases的 "Source-project shape" 节):

  • 保留index.html作为源工具返回的入口 shell(entryHtml必须是"index.html",校验代码invalid_entry/missing_entry强制这一点)。
  • src/canvas.tsx是约定的 React 入口组件,但它可以从项目内导入额外的相对 TypeScript、TSX、JavaScript、JSON、SVG、CSS 与已接纳的资产文件。
  • 自包含的模块 worker 可用./worker.ts?worker导入;worker 不得再导入其他本地模块。
  • 图片走上述媒体库流程;其他二进制资产放入项目的assets映射,以 base64 内容 + 已接纳的 content type 表示。WOFF/WOFF2、WebAssembly 与通用 octet-stream 资产受支持。
  • 平台依赖映射必须原样保留,不得添加 npm 包:相对导入是项目文件,裸导入被限制在平台固定集合内。

允许导入的裸模块allowedImportSpecifiers):@posthog/canvas-sdkreactreact-domreact-dom/client@posthog/quillrechartslucide-reactdayjsd3threeframer-motionzod@tanstack/react-table@tanstack/react-virtualreact-hook-formlodash-esreact-markdownpapaparse。固定版本包括 react 19.0.0、@posthog/quill 0.3.0-beta.18、recharts 2.15.0 等;依赖缺失报dependency_not_admitted,版本漂移报dependency_version_mismatch

源码体量限制limits,校验器逐条强制执行):

限制对应诊断
maxSourceFiles64 个文件too_many_files
maxSourceFileBytes512 KB / 文件file_too_large
maxSourceTotalBytes2 MB(files + assets 及规范化 JSON 双重计量)file_too_large/project_too_large
maxStateValueBytes64 KB / state 值写入时边界
maxStateKeysPerScope256 键 / scope写入时边界

运行时安全规则(校验器中的禁止模式,全部 error 级):动态import()forbidden_dynamic_importrequire()forbidden_requireimportScripts()forbidden_import_scripts;内联<script>forbidden_inline_script。路径必须相对、非空、正斜杠,段字符集限字母数字. _ @ -,禁止...段(invalid_path)。远端脚本与动态导入被彻底封死——沙箱 CSP 本身就包含script-src 'self'default-src 'none'connect-src 'none'(声明 origin 后才有条件放开)。

收尾:链接与交付约定

  • 每次请求变更保存一次,画布就绪时——不是在每次微编辑后。
  • 用户要草稿时,结尾说明"草稿已就绪,可预览并 promote";草稿 → 构建 → 预览 → promote 流程由validating-and-publishing-canvases覆盖。
  • 回复结尾必须给出画布所在频道并附上链接——使用画布工具返回的url字段(canvas-createcanvas-list与发布/源响应都携带它)。该字段是画布唯一合法链接,永远不要自行构造:猜测的 URL(项目页、web 路由)无法解析。

小结

building-canvases技能把自由画布开发压缩成一条可验证的闭环:解析目标(不猜测频道、不造重复品)→ 按请求形态加载实现与校验技能 → 以平台契约约束的项目形状编写源码 → 用无副作用的canvas-validate-create洗掉全部 error 诊断 → 用expected_current_version_id守卫发布(或按需暂存草稿)→ 轮询构建到终态。其背后是一套相当严格的平台工程:append-only 的源版本序列与 last-good 构建指针保证失败发布不污染在线画布;能力声明 + 运行时强制 + CSP 注入构成最小权限边界;64 KB / 256 键的 state 限制与 64 文件 / 2 MB 的源码限制让每次访问保持点查、每次构建保持有界。理解这条"文档规则—校验诊断—后端模型"三层一致的链条,是可靠地为 PostHog 构建、更新和修复独立画布应用的关键。

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询