Skip to main content

sp-canvas

Start and operate the local tldraw design canvas that shows HTML artboards. Start the canvas app against a project's board folders, add or switch boards, place boards, images, video and notes anywhere on a canvas with sp canvas, and act on annotated screenshots of the canvas. Use when asked to open/launch the canvas, put a mockup, an image or a video on the canvas, annotate or draw on it, fix overlapping frames after a layout.json edit, or respond to a screenshot of the canvas with notes drawn on it.

ソース情報

リポジトリ
ReScienceLab/super-prototyping
ソースの最終更新活動
2026年9月29日 09:34
検出された SKILL.md の言語
英語
スター
89
フォーク
4

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
5 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
sp-canvas
description
Start and operate the local tldraw design canvas that shows HTML artboards. Start the canvas app against a project's board folders, add or switch boards, place boards, images, video and notes anywhere on a canvas with sp canvas, and act on annotated screenshots of the canvas. Use when asked to open/launch the canvas, put a mockup, an image or a video on the canvas, annotate or draw on it, fix overlapping frames after a layout.json edit, or respond to a screenshot of the canvas with notes drawn on it.
license
Apache-2.0
compatibility
Requires the Super Prototyping app (macOS or Windows), which puts the sp command on PATH, and uv, which sp runs with. scripts/install.sh (install.ps1 on Windows) installs both. Where the app cannot run, sp start needs node or bun and a modern browser.
metadata
{"managed-by":"super-prototyping"}
# Prototype canvas A local tldraw app that discovers every `.html` file under `canvases/<slug>/` and renders it as a shape. There is no shape map to edit and no code change needed to add a board. A project is a folder under `~/Documents/Super Prototyping` (`PROTOTYPING_PROJECTS_DIR` moves it), and its boards are in that folder's `canvases`. No folder anywhere else is ever a project, so deleting one from the app can only ever trash what was made there. Working in another repository, write the boards to `~/Documents/Super Prototyping/<project>/canvases/<slug>/`, not to the repo. The app is installed outside every project, so upgrading it never touches a board you wrote. Every launch of the app puts `sp`, `refkit` and `artgen` on PATH and links these skills into each agent's skills directory. ## Start ```bash sp open ``` That opens the Super Prototyping app on its home page, starting it if it is not running, and prints the canvas address and the projects folder. It is the way to show the user the canvas. A project's boards are at `<address>p/<project>/`. `sp` not found? Run `sh <this skill's dir>/scripts/install.sh` (Windows: `powershell -ExecutionPolicy Bypass -File <this skill's dir>/scripts/install.ps1`). It installs uv and the app and opens the app, which links `sp`. Then follow `references/install.md` to put it on this shell's PATH and go back to the task. **`[super-prototyping:notice]` on stderr** from `sp`, `refkit` or `artgen` means the app found a newer release. The line says what to do: finish the step you are on, run `sp upgrade`, and tell the user what it printed. Do not upgrade in the middle of a step, and do not stop the user's task over it. `sp start` serves the canvas without the app, for where it cannot run: CI, Linux, a remote box. On first run it fetches the canvas built for its version into `~/.cache/super-prototyping/<version>/`, serves it on 127.0.0.1:5173 with node or bun, waits for the port to bind, and prints the address. A checkout being worked on serves its own `canvas/dist` instead, rebuilt with bun when a source is newer. It refuses a port that already answers rather than reusing it, and says so when that port is the app's. - **Boards** are `canvases` under a project in the projects folder, each project served at `/p/<name>/`. The address printed is the home page. - **Port** with `--port N`, or `SP_CANVAS_PORT` for a machine that always uses another one. A port that already answers is never reused: it may be another project's canvas, so `start` refuses rather than showing you the wrong boards. - **Two `sp start`s on two ports can run two canvases.** The session name, the log and the pidfile are all keyed by port, so a second `start` on a free port leaves the first one alone. `stop` and `status` take `--port` for the same reason, and `stop` only ever kills the canvas it started. - `sp root` prints which tree it found, the app's or a checkout's, and with `-v` everywhere it looked. The first thing to run when the canvas is not what you expected. `sp status` says whether the app is running, and on which port. - **It writes two directories**: that cache, and `~/.local/state/super-prototyping/` for its pidfile and log, the same on macOS as on Linux; the app keeps its own state beside them. `SUPER_PROTOTYPING_HOME` moves both under one root. `sp paths` prints them and every variable in use; `sp clean` removes the cache and `start`'s own files, never the app's. - Deep-link a page with `?canvas=<slug>`, e.g. `http://127.0.0.1:5173/?canvas=notion-ios`, and one board of it with `#<file>` after that, e.g. `?canvas=notion-ios#02-search-ask-ai`: it opens selected, zoomed to fill the window. Give the board link when pointing at one screen. - **Read a board as a web page.** The "Export to Figma" button in the top bar opens every board of the page in one scrolling document, each at its own size, at `sheet.html?canvas=<slug>`, and one board is at `/board/<slug>/<file>.html`. Both are ordinary addresses serving the board's own HTML, so they can be linked, reloaded, and read by the browser extensions — a Figma importer, say — that refuse to work on a generated page. This is where to read type at the size it ships at, rather than at whatever the canvas is zoomed to. The sheet opens on what to do with it: the html.to.design browser extension, which captures a localhost page that the Figma plugin's own servers cannot reach, and the paste or plugin route from there into a file. Keep it on loopback. This is a local design tool, not a service to expose. A project with no boards yet opens on a notice naming the directory the canvas resolved, rather than an empty grid: an empty boards folder and a canvas pointed at the wrong one look identical otherwise. **A folder created after boot appears on its own.** The server watches the boards directory and rebuilds its index when a board folder or file is added or removed. Rewriting a board swaps the new version in without reloading the page, so a generator can be re-run with the canvas open while the person keeps drawing on it. If a `?canvas=<slug>` link still matches no page, the folder has no `.html` file in it yet — an empty folder is not a board. The styles panel is hidden by default; toggle it from the toolbar. Always-snap is on by default. The setting is per browser, so turning it off in tldraw's preferences menu sticks. ## Boards One folder under the boards directory = one tldraw page; one `.html` file = one shape. Switch with the page menu at the top-left; do not build a separate switcher. `references/layout.md` has the `layout.json` schema, the caption rules, and the 478 × 980 / sandbox constraints every artboard lives under. **Write nothing at the project's root** but its documents (`*.md`) and `project.json`: work goes in a canvas folder, and what a run makes in that folder's `scratch/`. A project is what gets shared, and `sp pack` packages the root's `canvases/` and documents and nothing else there. Keep `project.json`'s `id` and `format` as they are. The layout is `references/layout.md`, under "The project folder". Share one with the community only when asked, as it says under "Sharing a project". **After editing `layout.json`, right-click the canvas and choose Force refresh.** Shape creation is idempotent. It fills in what is missing but never moves a shape that already exists, so inserting or reordering a row entry leaves the old shape at its old position, overlapping the new one. Force refresh deletes every `canvas-file` / `canvas-row-heading` / `canvas-file-label` shape on all pages and rebuilds them from the current files. Content-only edits to a placed file do **not** need it: the canvas swaps the rewritten board in by itself. ## Place things on the canvas `sp canvas` reads what is on a canvas and places shapes, images, video and boards anywhere on it. The project's canvas open in the app runs each command with tldraw's own editor and answers once the result is saved to `canvas.json`, so a command that returned is on disk. ```bash sp canvas <op> --canvas <slug> '<json>' # - instead of the JSON reads it from stdin ``` The chat panel's agent already has the project and the port. From anywhere else, add `--project <name>`, the name in the canvas's address (`/p/<name>/`). A command needs the project open in the app. With none open it waits ten seconds for one, then fails and says so: ask the person to open the project (`sp open`, then the project) and run it again. Never write `canvas.json` yourself instead. | op | JSON | does | |---|---|---| | `get` | none | Every shape: `id`, `type`, `owner` (`layout`, `agent` or `person`), page bounds `x` `y` `w` `h`, and `parent`, `name`, `text` where it has them. `arrows` lists each arrow's `from` and `to`. | | `create` | `{"shapes": [...]}` | Adds shapes, below. Answers `{"created": [ids]}`. | | `update` | `{"shapes": [{"id", "type", ...}]}` | Changes your own shapes. `x`/`y` are the page bounds' corner, as `get` gives them. | | `delete` | `{"ids": [...]}` | Deletes your own shapes. | | `align` | `{"ids", "operation"}` | `left`, `right`, `top`, `bottom`, `center-horizontal`, `center-vertical`, `center`. | | `distribute` | `{"ids", "operation"}` | `horizontal` or `vertical`, three ids or more. | | `stack` | `{"ids", "operation", "gap"?}` | `horizontal` or `vertical`. | | `pack` | `{"ids", "gap"?}` | Packs them into a block. | | `frame` | `{"ids", "name"?, "padding"?}` | Wraps them in a new frame, 32 px of padding by default. Answers `{"frame": id}`. | | `select`, `zoom` | `{"ids"?}` | Selects them, or moves the view onto them. No ids: `select` clears the selection, `zoom` fits the whole canvas. The canvas in front only. | | `shot` | `{"ids"?}` and `-o <file>.png` | A PNG of them, or of the whole canvas. The canvas in front only. Look at it to check the result. | What `create` takes: - **Image or video**: `{"type": "image", "x", "y", "file": "<path on this machine>"}`, or `"video"`. `sp` copies the file into the canvas folder's `files/` first. Give `w` or `h` to scale it evenly, both to set its size, neither to keep its own. - **Board**: `{"type": "canvas-file", "board": "<file>.html", "x", "y"}`, a board of this canvas folder, at its own size unless `w` and `h` say otherwise. Place only a board that no `layout.json` row lists, or it shows twice. One you placed stays out of the grid of unlisted boards. - **Text, note, geo**: `{"type": "text", "x", "y", "text": "..."}`. Other props go under `props`, never `w`/`h` at the top level. - **Arrow**: `{"type": "arrow", "x", "y", "from": "<id>", "to": "<id>"}` binds both ends, so it follows them. It cannot bind to a layout shape. - An `id` of your own (`"shape:keyframes-title"`) lets a later command name the shape. Every command is checked whole first: one bad shape and nothing is written. Work out coordinates from `get`, never guess them. To put something beside a shape, start at its right edge plus 40 and check the new bounds against every other shape's, moving down past any it would cover. The person's own content is never to be covered. What you create is yours to move and to change, and the person's to move and resize too: images and video keep their proportions, a board stretches freely (Shift keeps them). Content `layout.json` places stays locked. **When the person has moved a shape of yours, theirs is the last word**: an update or a layout op that would move it again fails with `moved_by_person`, listing each shape's bounds `now`. Run `get` again and plan around where they put it. A shape that only went along with the frame the person dragged has not moved. `"force": true` overrides it; use it only when the person asked for the shape to be put back. Refusals come back as JSON on stderr with a non-zero exit, `{"error", "message", ...}`: `not_agents` (not yours), `moved_by_person`, `holds_persons_shapes` (the person dragged a shape of theirs into your frame), `locked` (the person locked it), `not_current_page` (the person is looking at another canvas), `exists`, `not_writable`, `bad_command`, and `failed` (the page could not save it; say so rather than retrying blindly). Your changes stay out of the person's undo. Six keyframes of the person's screen recording, from `$WORK/frames`, put in a frame beside it with an arrow from the recording: ```bash sp canvas get --canvas video-notes # {"id": "shape:rec", "type": "video", "owner": "person", "x": 0, "y": 0, "w": 720, "h": 405, ...} sp canvas create --canvas video-notes - <<EOF {"shapes": [ {"id": "shape:k1", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/1.png"}, {"id": "shape:k2", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/2.png"}, {"id": "shape:k3", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/3.png"}, {"id": "shape:k4", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/4.png"}, {"id": "shape:k5", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/5.png"}, {"id": "shape:k6", "type": "image", "x": 760, "y": 0, "w": 240, "file": "$WORK/frames/6.png"}]} EOF K='"shape:k1", "shape:k2", "shape:k3", "shape:k4", "shape:k5", "shape:k6"' sp canvas pack --canvas video-notes "{\"ids\": [$K], \"gap\": 16}" sp canvas frame --canvas video-notes "{\"ids\": [$K], \"name\": \"Keyframes\"}" # {"frame": "shape:…"} sp canvas create --canvas video-notes '{"shapes": [{"type": "arrow", "x": 0, "y": 0, "from": "shape:rec", "to": "shape:…"}]}' sp canvas shot --canvas video-notes -o scratch/canvas.png ``` Then `get` again and check the frame's bounds against every other shape's before telling the person it is done. ## What the person put on the canvas Anything that is not a board or a `layout.json` picture or card is the person's, what they pasted or dropped, or yours, what you placed with `sp canvas` (`owner` in `get` says which). It is saved in the canvas folder: - `canvas.json`: tldraw records (shapes, bindings, assets), sorted by id. A shape at the page's root has no `parentId`. - `files/`: the files those records point at, by asset id. An asset's `src` is relative to the canvas folder (`./files/<name>`). Read them to see what the person put there. A chip or a link named `<slug>/canvas.json#<shape-id>` points at one record, and one named `<slug>/files/<file>` points at one file. **Never edit either.** To answer a sketch or a note, place a board or a shape beside it with `sp canvas`. What it creates is stamped `meta.by: "agent"`, and every write refuses a shape without that stamp. Never let `sp canvas` commands inject arbitrary JavaScript, never load untrusted HTML into a board, and never add `allow-same-origin` to the artboard iframe. ## Annotated screenshots
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る