Repository navigation
Project package, sandboxed boards, sp pack, and the community page - #183
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A board opened at its own address now runs in an origin of its own, on the app's server and on Pages, so a project from someone else cannot drive the canvas's write endpoints. The server answers only to localhost or an IP address, which closes DNS rebinding. A link shape opens only a web address. A new project gets project.json with format 1 and a UUID, and one a newer app made is refused rather than misread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package is the project less what is left out by place: scratch, captures, dotfiles, comments and measurement evidence. Every reference the app makes has to resolve inside it, no symlinks, web links only, 50 MB a file and 200 MB in all. -o also draws the cover as thumbnail.png, and mints the project's id if it has none, the only write to the project. layout.md documents the folder and format 1, and the sp-canvas skill tells agents to keep the root clear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying super-prototyping with
|
| Latest commit: |
bf52aa1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8b14c980.super-prototyping.pages.dev |
| Branch Preview URL: | https://worktree-project-package-pla.super-prototyping.pages.dev |
…e PR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home gets a Community tile left of Join Discord, which opens a closable Community chip on the bar; a card's Open brings that project forward as its tab. The same page is community.html for the site. Each card shows the project's thumbnail as sp pack draws thumbnail.png: the cover whole, on the canvas's ground at 16:10. Until the community repo exists it lists the examples, by ReScienceLab, and sharing points to Discord. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83bbf94776
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <a | ||
| className="cm-btn cm-btn--solid cm-btn--md" | ||
| href={canvasPageUrl(open.slug)} |
There was a problem hiding this comment.
Route standalone community cards through /demo/
When this component is rendered by communitySite.tsx at superproto.dev/community, canvasPageUrl() uses the relative base ./, so this link resolves to https://superproto.dev/?canvas=…—the download page—instead of the hosted canvas under /demo/. The same relative base passed to CoverPicture makes thumbnails request /board/… outside the proxied demo. Consequently, the public Community page cannot reliably display or open its examples; use DEMO for these URLs when openExample is absent.
AGENTS.md reference: AGENTS.md:L35-L41
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not a bug on the site: the landing Worker (ReScienceLab/super-prototyping-landing#2) prepends <base href="/demo/"> to the page's head, so the build's relative ./?canvas=… and ./board/… resolve under /demo/. Checked through the Worker in Playwright: Open goes to /demo/?canvas=apple-app-store and every thumbnail loads.
| fs.writeFileSync( | ||
| path.join(dir, "project.json"), | ||
| `${JSON.stringify({ format: PROJECT_FORMAT, id: crypto.randomUUID() }, null, 2)}\n`, | ||
| ); |
There was a problem hiding this comment.
Preserve the generated ID when naming an untitled project
When the user leaves the project name blank, AppShell.tsx immediately instructs the agent to write {"name": "…"} as project.json. This newly created file already contains format and id, so following that instruction replaces the generated identity; sp pack --check then rejects the project for lacking an ID, while sp pack -o silently assigns a different one. Update the naming instruction or flow to merge name into the existing object.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 038d8ca: the prompt now tells the agent to add name to project.json, keeping the keys already there.
| found = _cover(project, pj) | ||
| if found is None: | ||
| raise SystemExit("error: the project has no board to make a thumbnail of") |
There was a problem hiding this comment.
Allow packages without an HTML cover
For a project whose canvas contains only a video or another non-HTML content type, _pack() accepts all files and --check can succeed, but _cover() finds no folder containing *.html and this branch aborts sp pack -o. This contradicts the new package format's explicit support for video-only projects and prevents them from being shared; generate a fallback thumbnail or derive one from the available content instead of requiring a board.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not a case that exists: a canvas is a folder with *.html in it (boardIndex() in canvas/server/boards.ts), so a folder holding only a video is no canvas and the app never shows it. A video inside a canvas ships, as the test's talk.mp4 does.
| if canvases.is_symlink(): | ||
| problems.append("canvases: is a symlink") | ||
| elif canvases.is_dir(): |
There was a problem hiding this comment.
Reject Windows junctions before walking canvases
On Windows, a directory junction is not reported by Path.is_symlink(), and os.walk() follows it as a directory. A project containing a canvases junction—or a nested junction handled by the identical check below—therefore packages files outside the project, potentially publishing unrelated or sensitive data despite the no-path-escape guarantee. Reject junction/reparse points explicitly or verify every resolved path remains under the resolved project root.
AGENTS.md reference: AGENTS.md:L69-L75
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 038d8ca: every shipped file is also refused when it resolves outside the project, which catches a junction wherever it is. Test added.
… one -o sets a missing author to the login gh is signed in as; --check reports one missing, and both refuse an author or contributors that are not GitHub logins. The community repo holds them to whoever opens the pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reads index.json from ReScienceLab/super-prototyping-community and lists those projects first, each by its thumbnail.png, with its contributors. Sharing and takedown now point at that repo. The plan records Phase 3 as built and moves the licence and rename questions to Decided. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 753cf453e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| file, box, size, ground = found | ||
| scale = 2 | ||
| if size is None: | ||
| im = Image.open(file).convert("RGBA") |
There was a problem hiding this comment.
Rasterize SVG covers before passing them to Pillow
When a project uses a listed SVG brand image as its cover—a supported path that the UI can create—_pack() accepts it, but sp pack -o reaches this branch and passes the SVG to Pillow, which does not decode SVG files. The command therefore fails instead of producing the package; render SVG covers through the browser or another SVG rasterizer before compositing the thumbnail.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 038d8ca: an SVG cover is drawn by Chrome at the size its layout row gives it, as a board is. Checked on a real SVG.
| total = sum(size for _, size in ship) | ||
| if total > PACK_TOTAL_CAP: | ||
| problems.append(f"{total >> 20} MB in all, over the {PACK_TOTAL_CAP >> 20} MB a " | ||
| "package can be") |
There was a problem hiding this comment.
Count the generated thumbnail against the package cap
When the selected project files total just under 200 MiB, this check passes even though -o subsequently adds thumbnail.png and may add or enlarge project.json. The resulting package can exceed the declared total-size limit and be rejected by the community repository's validation despite sp pack reporting success; recheck the total after all generated files are known or reserve their size here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving as is: the cap is on the project's own files, and the community repo's CI runs the same sp pack --check, which leaves the generated thumbnail.png out of the count too, so the two agree. The thumbnail is a 1600 × 1000 PNG, a few MB at most.
…new project's id when naming it From the Codex review of #183. A Windows junction is no symlink to is_symlink(), so a shipped file is now also refused when it resolves outside the project. Pillow reads no SVG, so Chrome draws an SVG cover as it draws a board. The naming prompt told the agent to write {"name": …} as project.json, which dropped the id the app had just written; it now adds the key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 038d8cae40
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| </a> | ||
| <nav> | ||
| <a href="./">Demo</a> | ||
| <a href="https://discord.gg/2DEZFFKx7k" aria-label="Discord"> |
There was a problem hiding this comment.
Point the Demo navigation to /demo/
On the standalone page served at https://superproto.dev/community, href="./" resolves to https://superproto.dev/, which is the download page, so the navigation item labeled “Demo” does not open the hosted canvas at /demo/. Point this link explicitly at /demo/.
AGENTS.md reference: AGENTS.md:L35-L41
Useful? React with 👍 / 👎.
|
|
||
| def json_of(rel): | ||
| try: | ||
| return json.loads((project / rel).read_text(encoding="utf-8")) |
There was a problem hiding this comment.
Reject non-JSON numeric constants during packing
Python's json.loads accepts NaN, Infinity, and -Infinity by default, so an app-defined file containing one of these values passes _pack() and sp pack --check. JavaScript's JSON.parse rejects those files, meaning community CI can admit a package whose project.json, layout.json, or canvas.json the app cannot read; configure the decoder to reject non-standard constants.
Useful? React with 👍 / 👎.
| if chosen.get("path"): | ||
| file = canvases / chosen["path"] | ||
| layout = layout_of(canvases / Path(chosen["path"]).parts[0]) |
There was a problem hiding this comment.
Ignore image covers no longer listed in the layout
When a previously selected brand image remains on disk after its rows[].images entry is removed, the app's projectCover() ignores that stale choice and falls back to a board, but this branch honors any existing cover.path. Packing therefore generates a community thumbnail from a different image than the local project card; require an image cover to remain listed in the layout before using it.
Useful? React with 👍 / 👎.
The window is the viewport's height and clips, so the tab showed only its first two rows and the wheel did nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thumbnail is 2400x1260, an Open Graph image's 1200x630 twice over, set like a book's cover on the canvas's ground: a spine, the app's icon, the name in a serif, the board count, and the cover board with the row after it. sp pack draws a project's; sp thumbnail draws a canvas folder's, and each example now commits one. The community page shows only thumbnails, at their own ratio. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bf52aa12e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| tall, which the thumbnail sets beside it while they fit: layout.json's cover, else its | ||
| first board that is not a 00- one.""" | ||
| layout = _layout_of(folder) | ||
| names = sorted((f.stem for f in folder.glob("*.html")), key=_numeric) |
There was a problem hiding this comment.
Exclude reference boards from generated thumbnails
When layout.json selects a ref-* board as its cover—or the first canvas folder contains only reference boards—_pack() correctly excludes that HTML, but _canvas_cover() scans the original folder and renders it into the package's public thumbnail.png. This can publish a third-party capture that the package rules deliberately leave out; filter these candidates using the same exclusion rules as _pack().
AGENTS.md reference: AGENTS.md:L108-L113
Useful? React with 👍 / 👎.
Implements phases 0–3 of
docs/2026-09-25-project-package.mdand adds the community page./boardresponse is served withCSP: sandbox allow-scripts …, and_headersdoes the same on Pages.{format: 1, id}, plusauthorandcontributorsas GitHub logins. A project with a newer format is refused with 409. A project with no project.json is read as format 1.sp pack <project> --check | -o <dir>:scratch/,ref-*,comments.json,probes.jsonandcrops.json.-o, it fills in a missing id, and a missing author fromgh api user, then drawsthumbnail.png.layout.mdand the sp-canvas SKILL now say what belongs at a project's root, and "Sharing a project" gives an agent the steps to open a PR to the community repo.canvas/src/Community.tsx), built from the reference design:community.html. ReScienceLab/super-prototyping-landing#2 serves it atsuperproto.dev/community.index.json. Each card shows the project'sthumbnail.png, author and contributors, and links to the project on GitHub.sp packdraws one. A card's Open opens the example in its own tab.sp thumbnail <canvas>...draws an example's; all 21 are committed. Cards show them at their own ratio.sp pack --checkon each changed project, checks the thumbnail, and tiesauthorandcontributorsto the PR's GitHub account. It runs onpull_request_targetwith read-only permissions and never runs code from the PR.index.jsonwith each person's numeric GitHub id.tscerror insp.test.ts, left by an earlier commit, that brokebun run build.Tests: vitest (228 tests) and
tools/test_sp_canvas.py(16) pass, andtscand oxlint are clean. The community repo's check passed on its seed PR, ReScienceLab/super-prototyping-community#1. The community page was checked in Playwright:index.json.After merge: delete the community repo's
SP_REFvariable, so its CI installsspfrommain.🤖 Generated with Claude Code