Skip to content

Move the example canvases out of the app and into a repo of their own, fetched per canvas #147

Description

@Jing-yilin

Problem

The example canvases ship inside the app. mockups/canvases is 23 folders (22 canvases and templates), 2,773 files and 422 MB, and desktop/package.json copies it whole into the package. Two plans do not fit that shape:

  • Examples should grow without an app release. Today a new example reaches a user only in the next dmg or exe.
  • A community library is planned, where other people contribute canvases. Their folders cannot land in the app's installer, and should not land in the app's repository either.

The costs already measured:

Today
macOS dmg, 1.5.3 538 MB arm64, 545 MB x64. About 130 MB of that is the app.
Windows exe, 1.5.3 515 MB
macOS differential update 51 MB in about 1,550 range requests, because the examples are thousands of small files inside the zip (docs/2026-09-21-auto-update.md)
A plugin install About 430 MB on every product. The sparsePaths escape in the README works on Claude Code only; on Codex a sparse marketplace install fails.
Git pack 464 MiB, and every contributed canvas adds tens of MB to it for good

What changed since #122

#122 proposed fetching the examples on first run and was closed as not planned: it saved size and update traffic only, against a downloader, a checksum, an unpack step, an offline failure path and a progress state. That reasoning was right for that goal. #122 also put "Updating examples between app releases" out of scope. That is now the goal, and so is the community library, so this issue reopens the question with a different answer to "what does it buy". The costs #122 named still apply and are priced below.

Proposal

1. A second repository holds the examples. For instance ReScienceLab/super-prototyping-examples. It is an ordinary project that uses this plugin: mockups/canvases/<slug>/ with gen.py as the source of truth, the same folder rules, the same README-with-evidence rule. refkit, sp and the skills already work against any project's boards, so nothing new is needed to regenerate a board there. Community canvases arrive as pull requests to that repository.

2. One archive per canvas, and one index.json, published as GitHub release assets of that repository. A workflow there packs each canvas that changed and publishes a release whose index.json lists every canvas:

[
  { "slug": "spotify-ios", "bytes": 31457280, "sha256": "", "url": "https://github.com/…/releases/download/<tag>/spotify-ios.tgz" }
]

A canvas that did not change keeps the URL of the release it was last packed in, so it is neither uploaded nor downloaded again. The client reads https://github.com/<org>/<repo>/releases/latest/download/index.json, which GitHub redirects to the newest release (checked against this repo: a 302 to the tagged asset). No API call, so no 60-per-hour limit, and no tag to overwrite. That URL only works in a repository whose latest release is the examples, which is one more reason for the second repository.

One archive for everything does not hold up: mockups/canvases changed in 177 commits on 21 of the last 31 days, so a single 325 MB pack would be downloaded again almost daily.

3. One fetch, in canvas/server. As #122 designed it, and as docs/2026-09-20-desktop-onboarding-and-skills.md decided for the skills copy: dist/server.mjs is the one piece of code the app and sp start both run. At start the server reads the index, downloads each canvas whose sha256 is not in ~/.cache/super-prototyping/examples/ yet, checks it, unpacks it with the system tar (bsdtar ships with macOS and with Windows 10 1803 and later; the Windows half still needs a check on a real machine), and renames it into place. That is the cache directory sp start already uses for canvas-dist.tgz, so there is still no configuration file. No new dependency. The app and sp start then show the same examples, which closes the disagreement #122 described.

4. The first screen does not wait. Start here opens at once and a canvas appears when its archive lands, with one line saying that examples are downloading. Offline, the bundled folders are all there is, and the next start tries again. No card for a canvas that is not downloaded yet, no picker.

5. What stays in this repo and in the app: templates, 00-welcome, and the four folders a skill reads by literal path (luma-ios, duolingo-ios, apple-settings, claude-ios). Together 59 MB and 497 files. The other 17 folders (362 MB, 325 MB gzipped) move. canvas/server reads one examples directory today, so showing the bundled folders beside the fetched ones is a design point for the change: a second root, or the bundled ones listed in the index like any other.

Why a second repository now, and not later

  • Blast radius. GitHub's DMCA policy can disable an entire repository. This repository also carries the app's releases, the updater feed and the URL the cask downloads from. Content that clones third-party interfaces should not be able to take those down with it.
  • The URL is baked into every shipped app. Moving the examples later strands the versions already installed. Where they live has to be settled before the first fetch ships.
  • Install and clone size. A plugin install drops from about 430 MB to about 65 MB of worktree once the 17 folders leave. History keeps the old blobs; a rewrite is not proposed here.
  • It is what the comparable projects do. Obsidian keeps a pointer-only index in obsidian-releases and the content in each author's release. Excalidraw keeps excalidraw-libraries apart from the app, with libraries.json and pull requests. Zed, Raycast and shadcn fetch from a registry and never clone the gallery. Homebrew, which this project already uses, is a pointer plus a sha256.
  • It costs no code. A repository is not machinery.

Before the first community canvas is shown to anyone

  • Board HTML must not be able to script the app's origin. On the canvas a board renders in <iframe srcDoc sandbox="">, which is already strict. The two places that load a board by URL (the boards sheet, and "Open this board as a page") should get the same content-security-policy: sandbox header that canvas/server/sp.ts already sends for chat attachments. Small, independent of the rest, and worth doing first.
  • A check in the examples repository's CI that rejects <script, on*= handlers and javascript: URLs in a board. None of today's 352 boards contains a script tag.
  • gen.py is arbitrary Python. The app never runs it, but a person or an agent may. It is reviewed in the pull request like any other code.
  • A notice and a removal contact. There is none in the repository today. One file in the examples repository.

Expected numbers

Today After
macOS dmg 538 / 545 MB about 190 MB (130 MB plus the 59 MB kept; an estimate, not a built dmg)
Windows exe 515 MB the same proportion
macOS differential update 51 MB not measured; 82% of the small files leave the zip
First run nothing to fetch 325 MB once, in the background; later only the canvases that changed
Plugin install about 430 MB about 65 MB of worktree

What it costs

The list from #122, unchanged: a downloader, a checksum, an unpack step, an offline failure path, and one line of progress in canvas/src. Plus one workflow in the new repository. An honest guess is 100 to 150 lines in canvas/server.

Order of work

  1. The content-security-policy: sandbox header on board responses. Independent.
  2. Create the examples repository, move the 17 folders, add the notice, the pack-and-publish workflow and the board check. dun-web: the marketing page's feature section, cloned from one capture #76, Two video-sourced canvases: flashcard-onboarding and skillpath-ios #38 and Add motion/: one folder per animation asset, discovered like the boards #13 touch mockups/canvases and need settling first.
  3. The fetch in canvas/server, against the published index.
  4. Narrow extraResources to the kept folders. Steps 3 and 4 ship in the same release: narrowing alone would take 17 examples away from users.
  5. Remove the moved folders here, point CONTRIBUTING.md at the new repository for canvases, and update the install-size note in the README and the examples row in docs/2026-09-20-desktop-onboarding-and-skills.md.

Out of scope, and what would bring each in

  • A submission site, accounts, ratings or search. When "open a pull request" demonstrably turns contributors away.
  • Author, licence or tag fields in the index. When the first outside canvas merges.
  • A click-to-download card per canvas, or a picker. When the background download proves too heavy.
  • A mirror or CDN. When GitHub release downloads prove too slow for real users.
  • Rewriting this repository's history to shrink the pack.
  • A CLA. A Signed-off-by trailer is the cheaper answer if one is ever needed.

Decisions to make in the change

  1. The second repository now, or only when the first outside contributor shows up. Proposed: now, for the reasons above.
  2. Keep the six folders in the app (59 MB), or only 00-welcome and templates (1.3 MB) as Download the example canvases on first run instead of shipping them in the dmg #122 had it, which needs the skills to stop reading examples by path.
  3. Download in the background at first start, or only on a click. Proposed: in the background, which matches today's "everything is there".

Context

Supersedes #122. Related: #125 (comments on a read-only example canvas), #111. The research behind this was a read-only sweep of this repository, the comparable products, GitHub's hosting limits and policies, and the canvas's rendering and server code, with each angle fact-checked against primary sources.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions