A local design-artifact canvas that Agents build and people review. HTML Frames are not static images — they really run with scroll, hover, click and keyboard. When a person leaves feedback on a stable target ID, the Agent edits only the minimum source for that target instead of reading the whole canvas. That is the review loop this engine provides.
Agent renders a visual artifact
→ person comments on a stable target
→ context router returns the minimum relevant source
→ Agent edits only that target and its declared dependencies
→ render, compare and review again
Core parts:
render.mjs— renders a data package into a standalone HTML canvascontext.mjs— selective context router that resolves a target ID into the minimum set of source filesverify.mjs— verifies schema, ID integrity, feedback round-trip and context costupdate.mjs— renders and verifies a package in one runserve.mjs— loopback review server that saves feedback straight into the packagefeedback.mjs— Node-side reading, resolving and summarizing of the feedback fileruntime/— pan/zoom board, Frame interact, feedback, UI Kit, rules viewertemplates/minimal/— minimal skeleton for a new Canvas package
git clone https://github.com/fxylabs/supercanvas.git
cd supercanvas
npm link # install the global supercanvas command
supercanvas update examples/reading-list
supercanvas view examples/reading-listScaffold a new package with supercanvas new <dir>, and register an existing package in the machine
registry (~/.supercanvas/registry.json) with add so every later command can take a slug instead
of a path. Run supercanvas help for the full command list; see docs/cli-design.md for the design.
The output is standalone HTML with CSS, runtime and the JSON snapshot inlined, so it also opens over
file://. Generated output is not Agent input. supercanvas view serves that file over loopback
instead, which is what lets Save feedback write the package's feedback.json in one click — a page
loaded from disk has no origin the browser will let it write from. The server stays up until Ctrl+C
and takes --port and --no-open.
Follow docs/authoring-guide.md to create a new Canvas package. Write only the data package — never
copy the engine code.
By default an Agent reads and edits nothing but the minimum source returned by the target router.
- Start from a comment ID in
feedback.jsonor a stable target ID. - Resolve the required source paths with
context.mjs. - Apply
commonRules.activefrom every context result first, and include its verification checks in your definition of done. - For UI/UX work, check the Canvas target's
libraryIndexand look up existing Library IDs first. - For Frame work, read only that
frames/<frame-id>.html. - For Relation or Note work, read only
relations.jsonornotes.jsonrespectively. - Read token/component source only when the change is a global visual one.
runtime/ and render.mjs are canvas infrastructure. You do not need to read them to design a
frame or change a policy. The generated canvas.html is a review artifact too, not source.
{
"schemaVersion": 2,
"canvas": {
"id": "canvas-unique-id",
"title": "Canvas title",
"version": "v1",
"language": "en"
},
"sources": {
"tokens": "design/tokens.json",
"library": "library.json",
"rules": "rules.json",
"relations": "relations.json",
"actions": "actions.json",
"notes": "notes.json",
"feedback": "feedback.json"
},
"styles": ["design/components.css", "styles/canvas.css"],
"frames": [
{
"id": "frame-unique-id",
"title": "Frame title",
"summary": "Enough routing context without opening frame markup",
"uses": ["design/tokens.json", "design/components.css"],
"libraryUses": ["ui-button", "layout-stack"],
"source": "frames/frame-unique-id.html",
"width": 1280,
"height": 800,
"x": 80,
"y": 80
}
]
}Frame ID rules:
- unique within the canvas
- starts with a lowercase letter
- lowercase letters, digits and hyphens only
- 3–64 characters
- stable anchor for comments, connections, tabs and exported feedback
Beyond Frames, Canvas, Group, Connection, Note and Comment as well as
Library/Foundations/Layouts/Components/Stories all carry unique stable IDs in the same format. The
renderer validates every reference in relations.json, notes.json and feedback.json together
with its target type.
Schema v2 adds action-* targets, actions.json and deterministic revision metadata. The renderer
and the context router migrate a v1 package to the v2 shape in memory when reading it, but never
overwrite the source automatically. v1 read compatibility is kept until the next major schema.
library.json is the canonical definition data read both by the Storybook-style UI Kit inside the
Canvas and by Agent context. React components or CSS files are not required source. The Agent reads
props, states, slots, events, token dependencies, accessibility and usage guidance, then builds an
implementation that fits the target project.
{
"id": "ui-button",
"name": "Button",
"description": "Semantic button that runs an explicit user action.",
"contract": {
"element": "button",
"className": "sc-button",
"props": {
"variant": {
"type": "enum",
"values": ["primary", "secondary", "danger"],
"default": "primary",
"description": "Importance and risk of the action"
}
},
"slots": ["default", "leadingIcon"],
"events": ["click"],
"states": ["default", "hover", "focus-visible", "disabled"],
"tokens": ["color.accent", "radius.md"]
},
"accessibility": ["Accessible name required", "Keep the keyboard focus indicator visible"],
"guidance": {
"use": ["Verb-phrase actions"],
"avoid": ["Rendering a navigation link as a button"]
},
"stories": [
{ "id": "story-button-primary", "title": "Primary", "props": { "variant": "primary" } }
]
}The UI Kit view in the Canvas header searches foundations, layouts and components, runs Stories,
and shows and copies the exact JSON definition of each entry. A Frame declares its Library
dependencies with libraryUses in the manifest and marks each real component root with data-ui.
<button class="sc-button" data-ui="ui-button" type="button">New project</button>The renderer checks that every data-ui names a registered component and that it is also declared
in the Frame's libraryUses. context.mjs --target ui-button returns the exact component
definition, the Frames that use it and optional token/source paths instead of the whole generated
HTML.
Separately from the UI Kit, rules.json is the rule contract that constrains the Agent's Canvas
authoring behavior. To avoid duplicating it in every package, put a shared file at the Canvas
collection root.
canvas-root/
├── _shared/rules.json
├── product-flow/
└── onboarding-review/
When the manifest has no sources.rules, the renderer and the context router automatically look up
../_shared/rules.json. Declare sources.rules: "rules.json" only when a package needs its own
rules.
{
"schemaVersion": 2,
"rulesRevision": 1,
"scope": "workspace",
"rules": [
{
"id": "rule-frame-input-ownership",
"title": "Separate Canvas and Frame input ownership",
"status": "active",
"priority": "must",
"category": "interaction",
"statement": "In board and comment mode the Canvas owns pan/zoom.",
"rationale": "Prevents Frame interaction from fighting Canvas navigation.",
"appliesTo": ["canvas", "frame"],
"source": { "type": "user-instruction", "ref": "input-modes" },
"verification": {
"type": "agent-checklist",
"checks": ["In comment mode, a wheel event over a Frame pans the Canvas."]
}
}
]
}status is active | proposed | deprecated and priority is must | should. When the user asks
for a common rule explicitly, the Agent records it as an active rule. A rule the Agent generalized
from a comment is proposed as ruleProposal.status: proposed and is never promoted to active before
the user approves it. The Common rules view in the Canvas header lets you search and review active
rules, candidates awaiting approval, provenance and verification.
Every context.mjs result embeds commonRules.active regardless of the target type.
context.mjs --target <rule-id> returns the exact rule definition and its source. The Agent applies
the active rules and marks a related Comment resolved only after confirming each verification check.
Significant state changes are captured as a separate outcome Frame instead of mutating Frame DOM in place. Frame markup declares only the stable action anchor.
<button data-action="action-open-project">Open project</button>{
"id": "action-open-project",
"label": "Open project",
"from": { "frameId": "frame-project-list", "anchor": "action-open-project" },
"trigger": "click",
"outcome": { "type": "frame", "frameId": "frame-project-detail" },
"connectionId": "conn-project-list-detail"
}The protocol recognizes the click, hover, scroll and input triggers. The current runtime
provides click-driven outcome Frame transitions plus native scroll/hover proof. Actions are global
stable targets that Notes and Comments can attach to. policy, behavior, rationale,
edge-case and content Notes are design canon, while Comments are feedback reviewing that
design — the two never substitute for each other.
Connections do not use free curves between Frame centers. route.from points at a real Action
anchor and a top | right | bottom | left port, and route.to points at a port of the outcome
Frame. The runtime routes through 90° orthogonal segments and Canvas gutter lanes. Connections in
opposite directions use different lane values so they do not overlap.
{
"id": "conn-project-list-detail",
"from": "frame-project-list",
"to": "frame-project-detail",
"route": {
"type": "orthogonal",
"from": { "type": "action", "id": "action-open-project", "port": "right" },
"to": { "type": "frame", "id": "frame-project-detail", "port": "left" },
"lane": 0
}
}The Canvas splits input ownership explicitly in two.
- Board review: the Canvas owns pan/zoom and target selection.
- Frame interact: the selected HTML Frame owns scroll/hover/click/keyboard input.
Select a Frame on the board and run it with Enter or a double click. In Frame interact, inner scrolling is not forwarded to the Canvas, and Escape returns to the board.
Planning Notes are not shown as Comment pins or popovers. Turning on the Planning notes view
places Note cards on an outer rail around the owning Frame and leaves only a small N1, N2 style
anchor on the target item. Anchors and cards are joined by orthogonal Note links. Selecting the
target item, the numbered anchor or the Note card highlights all three together and moves Canvas
focus to the Note card.
A Note card always shows the full title, kind and text of the planning canon plus the stable Note ID. Review Comments keep the existing feedback pins and resolution lifecycle, so they never mix with Planning Notes visually or in the data. The Planning Note view is available only in Board Review and closes when you enter Frame Interact.
Frame fragments hold markup only. The renderer rejects <script> and <style>; interaction uses
data-goto="frame-id" and data-toast="message". Never copy runtime code into a frame.
Relationship and note sidecars also remain thin:
{
"groups": [
{ "id": "group-onboarding", "title": "Onboarding", "members": ["frame-entry"] }
],
"connections": [
{ "id": "conn-entry-home", "from": "frame-entry", "to": "frame-home", "label": "continue" }
]
}{
"notes": [
{
"id": "note-entry-policy",
"target": { "type": "frame", "id": "frame-entry", "x": 80, "y": 10 },
"text": "Why this state exists"
}
]
}After changing data, run a single update command with the package path as its argument.
node update.mjs examples/reading-listPoint it at a Canvas collection root to regenerate and verify several packages at once.
node update.mjs --all path/to/canvas-rootUse the individual render command below only when diagnosing the engine.
node render.mjs \
--in examples/reading-list/canvas.json \
--out examples/reading-list/dist/canvas.htmlnode context.mjs \
--canvas examples/reading-list/canvas.json \
--target frame-reading-homeA Frame target returns one fragment plus conditional design dependencies, a Connection target returns the relation and endpoint summaries, and a Note target returns only the note record. Give a Comment ID as the target and it tells you the real target to edit and the common rules to apply.
An Action target returns actions.json as the only required file and leaves the origin and outcome
Frame sources conditional. Every context result includes the file count and byte count of both
required and conditional reads. A Library target makes library.json required and puts the exact
definition and the index of Frames using it directly in the result.
A Rule target makes the shared rules source required and returns the exact rule definition, its
provenance and its verification checks. Every other target includes commonRules.active in the
result as well.
A Frame Comment binds to a stable Frame target and a revision first; only then does the anchor shape matter.
{
"id": "comment-region-example",
"target": {
"type": "frame",
"id": "frame-project-home",
"anchor": { "kind": "region", "x": 12.5, "y": 24, "width": 48, "height": 18 }
},
"targetRevision": "sha256:...",
"text": "Let's lower the information density of this region",
"status": "open"
}In comment mode a click creates a point anchor and a drag inside the Frame creates a region anchor.
Coordinates and sizes are percentages relative to the Frame, so they survive Canvas pan/zoom and
Frame repositioning. Point and region markers stay on the canvas through the whole cycle: open and
discussion are numbered, and a resolved comment keeps a check-marked marker that is left out of
the header count until the reviewer clears it. In Frame Interact every marker is hidden so it cannot
block real HTML input.
Feedback runs on a review cycle separate from the Canvas version. feedbackRevision increments once
each time the Agent updates the file. Never conflate Comment workflow status with target revision
state.
- red
open: not handled yet - yellow
discussion: the Agent has a question in the thread and needs a user decision resolved: recordsresolution.summaryand the changed targets; keeps a check marker until cleared- purple/gray dashed
outdated: a derived marker, independent of the statuses above, meaning the target revision changed
The loop runs in four steps, and nothing in it asks anyone to move a file by hand.
- The reviewer comments and presses
Save feedback. On a served canvas that writes the package'sfeedback.jsonand re-renders it, with no dialog and no download. - The Agent reads the comments with
supercanvas feedback(add--jsonfor the raw envelope) and resolves each target's source withsupercanvas context --target <id>. To stay in the loop without being prompted,supercanvas feedback --waitblocks until the reviewer saves work and then prints it — run it as a background job and its exit is the signal. - After doing the work the Agent closes comments with
supercanvas resolve <comment-id ...> --summary "what changed", or asks a question withsupercanvas discuss <comment-id> --message "...". Both record the Agent as the author, incrementfeedbackRevisionand re-render, so the open page showsupdatednext to the version badge. - The reviewer reloads, opens the check-marked pins to read each change summary, and presses
Clear resolvedonce satisfied. Anything they disagree with goes back withReopenor a reply instead.
A save carries the feedbackRevision the page was loaded from, so a page left open while the Agent
resolved something is refused with 409 instead of writing its stale review over the Agent's answer;
the reviewer is told to reload, and the browser draft is kept so no typed comment is lost.
Saving and clearing are deliberately separate. A save writes the review exactly as it stands,
resolved comments included, so an Agent's answer can never disappear before the reviewer has looked
at it. Clear resolved is what rotates the cycle: resolved comments move into archive under the
review cycle that closed them, while open and discussion stay in comments. A comment ID is in
one array or the other, never both. The runtime filters archived comment IDs out of the review list,
so a stale browser draft cannot resurrect one, and a clear that follows an earlier clear in the same
page session carries the earlier archive forward — clearing twice before the next render can never
drop closed history.
An archive entry keeps the review cycle as it stood when the comment was closed, so an ongoing cycle
is archived with status: active. The Agent flips those buckets to completed when it opens a new
review cycle.
The browser draft key combines the Canvas ID and review.id. A stored draft keeps
baseFeedbackRevision and submittedAt, and an already submitted past draft is not merged once a
higher canonical feedbackRevision is rendered. That way localStorage can never overwrite the
Agent's resolved results back into open/outdated.
The feedback menu offers only Save feedback, Clear resolved, Clear all comments and
Canvas info. Clearing all drops every current comment ID from both the canonical file and the local
draft without archiving it, which is the reset for a review that went nowhere; Clear resolved is
the one to use for finished work. On a canvas opened from disk, every menu action falls back to the
save modal — markdown plus Download feedback.json and Copy JSON — and you replace the package
file yourself and run update again. Canvas info opens a read-only modal that
reports which engine version rendered the file, the revision and schema version, the Canvas and
review cycle, the feedbackRevision, and the Frame and Comment counts.
Use the following prompt when a new Agent session edits an existing Canvas package.
Use the Supercanvas engine at <supercanvas clone path>.
The Canvas source package is <package path>.
Read <supercanvas clone path>/docs/authoring-guide.md before acting.
Begin from a feedback comment ID or stable target ID and run context.mjs before reading source.
Read review work with supercanvas feedback, close it with supercanvas resolve, and wait for the
next round with supercanvas feedback --wait as a background job.
Preserve Action/Note/revision contracts, orthogonal port routing and the
Board Review / Frame Interact input boundary. Never edit dist/canvas.html as source.
Run update.mjs after changes.
Task target: <stable-target-id>
Requested change: <describe the change>
If you do not know the target ID yet, run the context router with the Canvas ID as the target, inspect only the Frame and Action index, then pick a concrete target.
docs/authoring-guide.md— workflow for authoring a new Canvas packagedocs/roadmap.md— plans for schema versioning, module boundaries and design system work
Apache License 2.0. See LICENSE.