视觉画布设计
canvas-design
基于设计原则创作静态视觉作品,用于海报与品牌图形。
building-canvases【文档与办公】Create or edit a PostHog freeform canvas — a sandboxed browser application (data board, document, form, small tool, graphics experiment) stored in PostHog and rendered by the desktop/web app. Use when a task asks to build, generate, update, or fix a standalone canvas app, or when a freeform canvas id is given as the publish target. For grid/home canv…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
A canvas is a client-side browser application that runs in a sandboxed iframe inside PostHog.
Its source lives in PostHog — not in a repository — and you read and write it through the
canvas-* tools. Never write a canvas to a local file; publishing through the tool is what
saves it.
Canvas work can start from any ordinary task. A dedicated canvas mode or pre-created canvas is not required. When the user asks for a board, document, form, visualization, or small app that should live in PostHog, treat that as a canvas request and follow this skill.
This skill owns freeform canvases (standalone apps). Two other canvas kinds exist: grid
canvases (widget grids, including the user's home canvas) and component canvases (reusable
widgets grids place). When the target is a grid or home canvas, a placement, or a reusable
widget/component, load composing-grid-canvases instead — it owns the store search → configure →
fork → build ladder and the layout patch loop. Authoring a component's source still uses the
implementation companions below.
channel_context block or the generation instructions). List that channel's canvases with
canvas-list (scope with channel). If one is clearly what the request refers to — an earlier
iteration of the same board or tool — build on it instead of creating a near-duplicate, and say
so in your reply so the user knows where the result landed.canvas-create in that same channel, named
with a short descriptive title drawn from the request — never "Untitled canvas".channel-list only to resolve a channel
the USER named to its id. Its listing puts the personal #me channel first, and #me is never a
default — a canvas filed there is invisible to everyone else. If the task names neither a canvas
nor a channel, ask which channel to use instead of guessing.This skill owns canvas selection and the authoring lifecycle. The companion skills hold the implementation contracts. Load every companion that applies before writing source:
building-react-quill-canvases for dashboards, data boards, forms, tools, application-like
state, or anything that should look native to PostHog. It owns allowed imports, Quill composition,
theming, charts, loading and error states, and the date picker.building-html-canvases for documents, articles, focused experiments, generative graphics,
<canvas>, or WebGL work where application components add no useful structure. It owns semantic
markup, direct browser APIs, animation cleanup, and non-Quill theming.querying-canvas-data whenever the canvas reads PostHog data, captures events, or navigates.
It owns the ph SDK, saved-insight preference, result shapes, variables, date ranges, progressive
per-query loading, and declared data capabilities. Load it alongside either implementation skill
when data is involved.validating-and-publishing-canvases for every canvas. It owns project shape, capability
declarations, validation diagnostics, guarded publishes, drafts, builds, and conflict recovery.Mix implementation approaches when appropriate: React can own application chrome while browser graphics code owns a canvas element, or a mostly static page can mount one interactive island.
This is a judgment call, not a persisted mode — ask the user only when the choice changes a user-visible requirement you cannot infer.
A canvas can contain premade blocks: numbers, goals, trends, top lists, funnels, retention, saved insights, live event feeds, SQL tables, filters, compare and refresh controls, and callouts.
They are React components in src/blocks/ that people drag into the canvas in the desktop app, so they are part of your source.
Read references/blocks.md before you change a canvas that has src/blocks/runtime.tsx.
Expose the values a person may want to change as params. They can then change them in the desktop editor without a new task. Params are optional, but add them to each panel, card, or tool that has values worth changing: titles, copy, events, properties, thresholds, limits, colors. Params need React components. A plain HTML canvas has no params.
editable()In the desktop editor, a person can select any element of the canvas: a heading, a paragraph, a section. They can move, duplicate, or delete it, and double-click text to change it. This works with no editable() call.
editable() makes a component one block. The editor then treats the block as one unit:
So use editable() on the parts a person thinks of as one thing and configures as one thing: a card, a chart, a table, a form. Keep the page layout around them as plain JSX. The person can then select a card as a block, and still select a page heading or section directly. If the component that holds the full page uses editable(), the full page becomes one block, and each click selects all of it.
editable(name, props, params) from @posthog/canvas-sdk on the root element of the component. name is the component name. params maps each prop name to its field.<SignupFunnel blockId="b-k3m9x2" title="Signup" windowDays={14} />. The editor writes changes into these props, so do not compute them inline.blockId prop, for example <SignupFunnel blockId="b-k3m9x2" … />, so a person can point the agent at one card. Use only letters, digits, _, -, ., and :, or the editor ignores the ID. In a list, build it from a stable key of the item: blockId={`kpi-${item.id}`}.import { editable } from '@posthog/canvas-sdk'
export function SignupFunnel(props: { blockId?: string; title?: string; steps?: string[]; windowDays?: number }) {
const { title = 'Signup funnel', steps = ['$pageview', 'signed_up'], windowDays = 14 } = props
return (
<Card
{...editable('SignupFunnel', props, {
title: { type: 'text', label: 'Title', default: 'Signup funnel' },
steps: { type: 'events', label: 'Steps', default: ['$pageview', 'signed_up'] },
windowDays: { type: 'number', label: 'Conversion window (days)', min: 1, max: 90, default: 14 },
})}
>
…
</Card>
)
}
Field types: text, longtext, number (min, max, step; with both min and max the editor shows a slider), boolean, select (options: strings or { value, label }), event, events, property, insight (a saved insight's short id), color. Each field can also have a description.
Give each field the same default as its prop default, for example { type: "boolean", label: "Show legend", default: true }. The editor shows default when the prop is not set.
A color param defaults to a theme token such as var(--primary), so the canvas follows light and dark mode until a person picks a hex in the editor.
Pass only JSON values (strings, numbers, booleans, string arrays) through params. Keep functions and query results out of them.
Use public media library URLs for images in a canvas. Call posthog:media-images-list with
purpose="canvas" first and reuse a suitable image when one already exists.
To add a local image:
posthog:media-image-upload-start with the file name and purpose="canvas".upload_url as multipart form data. Include every
returned form_fields entry and put the file part last.posthog:media-image-upload-complete with the returned id and use its permanent url as
the image src.project.capabilities.network.origins. Canvas validation checks
this declaration, and the published artifact uses it in its Content Security Policy.Canvas media URLs are public and do not require authentication. Never upload secrets, credentials, customer data, or sensitive screenshots. Images must be under 4 MB and decode as PNG, JPEG, GIF, WebP, AVIF, or BMP. Never base64-encode image bytes into a tool call.
Use these as routing examples, not fixed templates:
building-react-quill-canvases (references/checklist-example.md) — team-shared
progress via per-step ph.state keys. Do not imply persistence that the available APIs do not
provide.When the task carries a legacy requested pattern such as dashboard or web-analytics, apply the
matching shape above. The pattern is a hint; the user's actual request remains authoritative.
canvas-source-retrieve.
Remember current_version_id — your publish must be guarded on it.querying-canvas-data (saved insights loaded via the ph SDK — never
fetch or your own PostHog client), make every figure verifiable — an insight-backed metric
links its saved insight in PostHog, an ad-hoc query shows the exact query that ran, per that
skill's "Verifiability" section — and
declare every ph call in project.capabilities (insight short ids in
capabilities.posthog.insights, captured events in captureEvents, inlineQueries: true for
ad-hoc queries, and agentRequests: true for ph.agent.request) — the host enforces these at
runtime and validation rejects undeclared calls.validating-and-publishing-canvases: validate with canvas-validate-create as often as
needed and fix every error-severity diagnostic.current_version_id is null): publish the complete project with
canvas-publish-create, passing expected_current_version_id: null.current_version_id is set): send only the edits with canvas-edit-create, using str_replace operations for changes inside a file.
Pass the live current_version_id as expected_current_version_id.
Publish the complete project with canvas-publish-create only to replace the whole project.canvas-draft-create only when the user asked for a draft, a preview, or
a review step before going live.
Follow the validating-and-publishing-canvases skill for diagnostics and conflict recovery.build.build_status; use it when it is ready or failed. Otherwise poll canvas-builds-retrieve
(every few seconds, up to ~2 minutes) until your build is ready or failed. On failed,
read the build's error diagnostics, fix the project, and save again — do not finish the
task with a failed build.Save once per requested change, when the canvas is ready — not after every micro-edit. When the
user asked for a draft, end your reply by saying a draft is ready to preview and promote; the
validating-and-publishing-canvases skill covers the draft → build → preview → promote flow.
End your reply by naming the channel the canvas is in and linking it with the url field the
canvas tools return (canvas-create, canvas-list, and the publish/source responses carry it).
That field is the only valid link to a canvas — never construct one yourself; guessed URLs
(project pages, web routes) do not resolve.
ph.state — durable key-value memory: ph.state.get(key, { scope }),
ph.state.set(key, value, { scope }) (a null value deletes the key), ph.state.list({ scope }).
Scope "user" (the default) is private to each viewer; "shared" is one value per canvas,
visible to the whole team. Declare the scopes you use in capabilities.posthog.state.
Values are JSON, capped at 64 KB serialized and 256 keys per scope — store big data in
PostHog (insights, the warehouse) and reference it. Never put secrets or viewer PII in state.
ph.actions.invoke(verb, payload) — write into PostHog as the viewer. Declare every verb
in capabilities.posthog.actions; undeclared or unregistered verbs fail validation and the
host refuses them at runtime. Wire actions to explicit user gestures (a button the viewer
clicks), never to load or render. The registry is the source of truth: list it with the
canvases-actions-retrieve tool and follow each verb's usage (payload/result shape,
behavior, and the confirmation copy it warrants) before wiring it.
ph.connectors.call(provider, tool, args) — read live third-party data (GitHub, or any
MCP store server) with the VIEWER's own connection at view time. Never call GitHub, Calendly,
or another service yourself and paste the result into the source: that snapshot is stale on
publish and shows every viewer the author's data. Declare each provider and tool in
capabilities.connectors; discover them with the canvas-connectors-retrieve tool. See
querying-canvas-data for the result and not-connected handling.
ph.agent.request(prompt) — ask the canvas's authoring agent for a change, with the viewer's
approval. Declare agentRequests: true in capabilities.posthog. Call it only from a direct
click or form submission — the host shows the exact prompt and asks the viewer to accept before
spending compute, and rejects calls made during render, mount, or polling. The agent publishes
the change as a new version; a non-creator's request is filed in the authoring task's thread
instead of starting a run.
index.html as the entry shell returned by the source tool.src/canvas.tsx remains the conventional React entry component, but it may import additional
relative TypeScript, TSX, JavaScript, JSON, SVG, CSS, and admitted asset files from the project../worker.ts?worker. A worker must not import
another local module.assets map as base64 content with an admitted content type. WOFF/WOFF2, WebAssembly, and generic
octet-stream assets are supported.