Repository navigation
A skill for filming a prototype (sp-create-video), and sp- in front of every skill - #177
Conversation
sp-scene-video turns a board into a live-action clip of someone using the product, with the interface kept pixel-exact rather than redrawn. It is the notion-purchase-motion session of 10-11 September, kept: thirteen paid takes that found out a model shown a screenshot composes its own UI, and a model given a video of the UI keeps it. - scripts/frames.mjs renders an animated board to PNG frames through CDP, scrubbing the CSS timeline rather than recording it. - scripts/ark.py submits one take to Seedance 2.5 on 火山方舟, polls it through a dropped connection, and writes the prompt, seed and usage beside the mp4. - scripts/composite.py is the rescue route: corner-pin the real frames onto a green plate when the model will not hold the interface. - references/ carry the prompt patterns that stop the redraw, the hosting the reference video needs, and the compositing. docs/2026-09-25-scene-video.md says why Ark and not fal, why the green plate ships as a fallback rather than the route, and what the evening on a 530 from cloudflared was about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying super-prototyping with
|
| Latest commit: |
1d67a5b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a5281895.super-prototyping.pages.dev |
| Branch Preview URL: | https://sp-scene-video.super-prototyping.pages.dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f70c51afb5
ℹ️ 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.key_file.read_text().strip() if a.key_file.is_file() else | ||
| sys.exit("no key: set ARK_API_KEY or put it in %s (chmod 600)" % a.key_file)) | ||
| a.out.mkdir(parents=True, exist_ok=True) | ||
| n = 1 + len(list(a.out.glob(a.tag + "-*.mp4"))) |
There was a problem hiding this comment.
Derive the next take number from existing suffixes
When matching files are non-contiguous, counting them does not produce an unused suffix: for example, walk-1.mp4 and walk-3.mp4 yield n == 3. After the paid task succeeds, this overwrites both walk-3.json and walk-3.mp4, losing the previous take and its provenance; parse the maximum existing suffix or otherwise reserve a free filename instead.
Useful? React with 👍 / 👎.
| "prompt": prompt, | ||
| "images": [str(p) for p in a.image], "video": a.video, | ||
| "result": st, "seconds": round(time.time() - t0)} | ||
| (a.out / ("%s-%d.json" % (a.tag, n))).write_text(json.dumps(record, ensure_ascii=False, indent=1)) |
There was a problem hiding this comment.
Write the take record explicitly as UTF-8
On Windows installations whose locale encoding is not UTF-8, the required Chinese prompt makes this write_text call raise UnicodeEncodeError after the paid generation has succeeded but before the MP4 is downloaded. Specify encoding="utf-8", as is already done when reading the prompt, so Windows users retain both outputs.
AGENTS.md reference: AGENTS.md:L69-L75
Useful? React with 👍 / 👎.
| return a[1] + (b[1] - a[1]) * (t - a[0]) / (b[0] - a[0]) | ||
| } | ||
|
|
||
| const CHROME = process.env.CHROME || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' |
There was a problem hiding this comment.
Resolve Chrome on every supported platform
On Windows—and on Linux—the documented node frames.mjs ... command always tries the macOS application path and fails unless the user discovers and manually sets the undocumented CHROME environment variable. Since this skill is shipped into Windows agent homes, resolve the platform-specific Chrome executable (or document and require the override) rather than making the primary rendering step macOS-only.
AGENTS.md reference: AGENTS.md:L69-L75
Useful? React with 👍 / 👎.
| screen, ref_area = biggest(chroma(ref)) | ||
| quad = quad_from_mask(screen) |
There was a problem hiding this comment.
Skip frames with no chroma component during reference search
If the frame at --ref has no detectable green component—for example, the phone is initially offscreen or fully occluded—biggest() returns None, which is passed to quad_from_mask() and causes OpenCV's findContours to fail before the loop can inspect a later usable frame. Guard the empty mask and continue scanning, matching the loop's stated purpose of finding the first frame with four visible edges.
Useful? React with 👍 / 👎.
A skill directory is one flat namespace an agent shares between every source it installs from, and `clone-prototype`, `new-ui-mock`, `prototype-canvas`, `define-product` and `brand-kit` were the plainest words for what they do -- the names a user's own skill is likeliest to take. A collision is silent: linkInstall keeps the user's folder rather than replacing it, so the app's skill simply never installs. They are all `sp-` now, as sp-scene-video already was, and `/sp-brand-kit` says whose it is where the user types it. The app takes the old links out on its next launch. linkInstall only ever made links, so a renamed skill left `~/.claude/skills/clone-prototype` pointing into a `current/skills/` entry that is gone, which the agent lists as a broken skill. It now removes a link into `current` that resolves to nothing, by the same rule `sp uninstall` uses, and leaves the user's own alone either way. canvases/00-welcome/00-welcome.html is edited by hand, against the folder rule, because its gen.py cannot run: it reads ../../../assets/banner.webp, one level above the repo since boards moved into canvases/, and the banner at the real path is the landing page's hero now rather than the board's strip. The generator's copy is edited to match. docs/2026-09-25-sp-prefixed-skills.md has the rest, including what was deliberately left unrenamed. Co-Authored-By: Claude Opus 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: e53d4dd9bc
ℹ️ 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".
| --fps 24 --seconds 10 -o "$V/out/ui" --scale 3 # bun runs it too | ||
| ffmpeg -y -framerate 24 -i "$V/out/ui/f%04d.png" -c:v libx264 -crf 16 \ | ||
| -pix_fmt yuv420p "$V/out/mockup.mp4" |
There was a problem hiding this comment.
Pad odd-width phone frames before H.264 encoding
For the repository's standard 393px-wide .phone (skills/sp-prototype-canvas/references/layout.md:155), the documented --scale 3 produces 1179px-wide PNGs. libx264 rejects that odd width when encoding yuv420p, so this default workflow stops before producing mockup.mp4; pad/crop to even dimensions or use an even scale.
Useful? React with 👍 / 👎.
| ap.add_argument("--seed", type=int, help="repeat a take you liked, or vary one you did not") | ||
| ap.add_argument("--no-audio", action="store_true", help="skip the generated ambience") | ||
| ap.add_argument("--model", default=MODEL) | ||
| ap.add_argument("--key-file", type=Path, default=Path(".ark_key")) |
There was a problem hiding this comment.
Read the key from the documented workflow location
The skill tells users to put the key in $V/.ark_key, but its example invokes this script from the project directory without --key-file, so the default resolves to ./.ark_key instead and exits with no key. Either pass $V/.ark_key in the documented command or derive the default from the workflow's video directory.
Useful? React with 👍 / 👎.
| document.getElementById('sp-scrub').textContent = | ||
| '${selector} *{animation-play-state:paused!important;animation-delay:-${t}s!important}' |
There was a problem hiding this comment.
Pause animations on the selected element too
When the motion is applied to .phone itself, this descendant-only rule never pauses or seeks it; animations on its pseudo-elements are missed as well. Those parts continue according to wall-clock time while the script captures frames, contradicting the deterministic scrubbing contract and producing timing-dependent output, so include the selected root and its pseudo-elements in the scrub rule.
Useful? React with 👍 / 👎.
Seven comments on #177, six of them real. The frame renderer's scrub was the one worth the most: a `*{animation-delay:-Ts}` rule only moves an animation that is still running, and these are paused, so the delay was measured from wherever the pause happened to land -- wall-clock. The same board rendered twice came back different, by up to 170 levels a pixel. It now pauses every animation `document.getAnimations()` returns and sets `currentTime`, which covers pseudo-elements and the clipped element itself as well, and two runs of the same board are now identical to the byte. The documented ffmpeg line could not have worked: the standard 393 pt phone is 1179 px wide at --scale 3, and libx264 refuses an odd dimension in yuv420p. It crops the column rather than padding a black line on. Chrome is resolved the way refkit resolves it, Windows folders and Edge included, and the profile goes in the OS temp directory, so the primary step is not macOS-only in a skill that installs into Windows agent homes. ark.py numbers a take past the highest already there rather than counting the mp4s: a gap in the series would have taken a number back and overwritten the record of a clip that cost money. Its JSON is written as UTF-8 explicitly, or a Chinese prompt raises UnicodeEncodeError on a Windows console encoding after the take is already paid for. The documented call passes --key-file, which the skill's own `$V/.ark_key` needs. composite.py returns None for a frame with no chroma in it instead of handing None to findContours, and says so when no frame shows four edges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven comments on #177, all of them real. The frame renderer's scrub was the one worth the most: a `*{animation-delay:-Ts}` rule only moves an animation that is still running, and these are paused, so the delay was measured from wherever the pause happened to land -- wall-clock. The same board rendered twice came back different, by up to 170 levels a pixel. It now pauses every animation `document.getAnimations()` returns and sets `currentTime`, which covers pseudo-elements and the clipped element itself as well, and two runs of the same board are now identical to the byte. The documented ffmpeg line could not have worked: the standard 393 pt phone is 1179 px wide at --scale 3, and libx264 refuses an odd dimension in yuv420p. It crops the column rather than padding a black line on. Chrome is resolved the way refkit resolves it, Windows folders and Edge included, and the profile goes in the OS temp directory, so the primary step is not macOS-only in a skill that installs into Windows agent homes. ark.py numbers a take past the highest already there rather than counting the mp4s: a gap in the series would have taken a number back and overwritten the record of a clip that cost money. Its JSON is written as UTF-8 explicitly, or a Chinese prompt raises UnicodeEncodeError on a Windows console encoding after the take is already paid for. The documented call passes --key-file, which the skill's own `$V/.ark_key` needs. composite.py returns None for a frame with no chroma in it instead of handing None to findContours, and says so when no frame shows four edges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bd74820 to
6c2826f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c2826fcf1
ℹ️ 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 skill a release renames or drops leaves its old link behind, pointing into a | ||
| // `current/skills/<name>` that is no longer there, and the agent shows it as a broken | ||
| // skill. Ours to remove, by the same rule `sp uninstall` uses: a link into `current`, | ||
| // now resolving to nothing. Anything the user put there is left as it is. | ||
| const dir = path.join(home, agentHome, "skills"); |
There was a problem hiding this comment.
Remove renamed copies from the canvas workspace cache
When upgrading an installation that has already used canvas chat, the old skills are real copied directories under <projects>/.workspaces/.claude/skills and .agents/skills, not the home-level links cleaned up here. installSkills() only iterates current source names and never removes managed copies whose names disappeared, so clone-prototype, define-product, and the other old names remain visible and invokable beside the six sp- skills, with stale instructions that reference paths removed by this commit. Extend the rename migration to remove obsolete managed copies from the persistent workspace cache as well.
AGENTS.md reference: AGENTS.md:L10-L13
Useful? React with 👍 / 👎.
# Conflicts: # README.md # RELEASE-NOTES.md # skills/README.md # skills/sp-prototype-canvas/SKILL.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42661d3d58
ℹ️ 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".
| <h2>Start with this skill</h2> | ||
| <div class="skill"> | ||
| <b>/clone-prototype</b> | ||
| <b>/sp-clone-prototype</b> |
There was a problem hiding this comment.
Regenerate the welcome board from gen.py
This edits the generated board directly, and the accompanying decision note confirms it was changed by hand because its generator cannot currently run. That leaves the committed output without a reproducible generation path; repair the generator’s asset input and rerun it rather than committing a manual HTML edit.
AGENTS.md reference: AGENTS.md:L92-L93
Useful? React with 👍 / 👎.
| except urllib.error.HTTPError as e: | ||
| sys.exit("HTTP %s: %s" % (e.code, e.read().decode()[:800])) |
There was a problem hiding this comment.
Retry transient HTTP poll failures
When a paid task’s GET poll receives a transient HTTP response such as 429 or 5xx, call() converts it to SystemExit here, so the retry handler around the poll never sees it and the process abandons the task before downloading its expiring result. Retry retryable HTTP statuses—respecting Retry-After where present—while preserving terminal client errors.
Useful? React with 👍 / 👎.
| pts = np.vstack([np.array([p[0] for p in pairs]), SRC[:2]]); dst = np.vstack([np.array([p[1] for p in pairs]), q[:2]]) | ||
| Hn, _ = cv2.findHomography(pts, dst, 0) | ||
| if Hn is not None: Hc = Hn; refined += 1 | ||
| u = cv2.imread(str(ui[(i + a.offset) % len(ui)]), cv2.IMREAD_UNCHANGED) |
There was a problem hiding this comment.
Clamp shifted UI frames instead of wrapping
Whenever the documented --offset is nonzero, modulo indexing wraps one end of the animation to the other: for example, --offset 1 makes the final plate frame show f0000.png. This introduces an abrupt reset at the end of an otherwise aligned take; shifted frames outside the UI range should hold the first or last frame rather than loop.
Useful? React with 👍 / 👎.
| const clip = await evaluate(`(() => { | ||
| const el = document.querySelector(${JSON.stringify(selector)}) | ||
| if (!el) return null | ||
| const r = el.getBoundingClientRect() | ||
| return { x: r.x, y: r.y, width: r.width, height: r.height, scale: 1 } |
There was a problem hiding this comment.
Scrub before calculating the capture clip
When the selected .phone itself animates position, scale, or another geometry-affecting property, this rectangle is sampled after the arbitrary 1.5-second load delay and before animations are first scrubbed, then reused for every frame. The fresh code now pauses the root animation, but these pre-scrub bounds can still crop it or add varying empty space throughout the rendered sequence; establish deterministic bounds after seeking to a defined time or account for the animation’s full extent.
Useful? React with 👍 / 👎.
The prefix was the point of the rename; the word "prototype" in the middle was not carrying any of it. Everything on the canvas is a prototype, and the skill is mostly the `sp canvas` command, which is what the app already calls it. The directory moves, `name:` in its frontmatter follows, and every mention across the prose, the canvas's agent prompt and `sp`'s help text goes with them. `docs/` and the tagged sections of RELEASE-NOTES are left as they were, as the rest of this branch leaves them: they are dated records. The Unreleased bullet and the decision note now say this one was shortened as well as prefixed, rather than implying the prefix was the whole change. Co-Authored-By: Claude Opus 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: d1000141c4
ℹ️ 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.out / ("%s-%d.json" % (a.tag, n))).write_text( | ||
| json.dumps(record, ensure_ascii=False, indent=1), encoding="utf-8") | ||
| mp4 = a.out / ("%s-%d.mp4" % (a.tag, n)) | ||
| urllib.request.urlretrieve(st["content"]["video_url"], mp4) |
There was a problem hiding this comment.
Retry the paid result download
If the result URL returns a transient error or the connection drops, urlretrieve raises after the paid task has succeeded and can leave a truncated final .mp4. Because the JSON record already occupies this take number, rerunning the documented command submits a new paid task under the next number rather than completing the existing one. Retry this download and write to a temporary file before atomically renaming it.
Useful? React with 👍 / 👎.
|
|
||
| ## Spending rule | ||
|
|
||
| Every take is the user's money: roughly ¥11 at 720p/10s, ¥74 at 1080p/15s, |
There was a problem hiding this comment.
Price the default reference-video take at ¥22
The skill's default route and example submission include --video, but the reference cost table says a 720p/10s take with a reference video costs about ¥22; ¥11 is the image-only price. Since this paragraph governs the required pre-submission cost disclosure, agents following it will underquote the default paid take by roughly half; make the estimate route-sensitive or use the ¥22 default.
Useful? React with 👍 / 👎.
| ```bash | ||
| uv run --with opencv-python-headless --with numpy \ | ||
| "$KIT/skills/sp-scene-video/scripts/composite.py" \ | ||
| out/plate-1.mp4 out/ui out/scene.mp4 --lock --stabilize |
There was a problem hiding this comment.
Point the composite command at the video workspace
In the documented workflow the current directory remains the project root, while the earlier commands write the plate and UI frames beneath $V/out; this command instead reads out/plate-1.mp4 and out/ui, so copying it verbatim exits with no f*.png frames in out/ui. Use $V/out/... consistently or explicitly change into $V before this command.
Useful? React with 👍 / 👎.
Five, all real. The welcome board can be generated again. It has its own `assets/` now, as every canvas folder is supposed to: the art it was built from, restored from before the landing page took the name at the repo root (e47e45d), and the icon beside it. Two things that had drifted into the committed output are back in the generator -- `layout.json`'s `ground`, added by hand in 2c22caf, and the `/sp-clone-prototype` line. `gen.py` now reproduces both files byte for byte, so the hand edit this branch documented as an exception is not one any more. The chat panel's agent loses the old names too. Its skills are real copies under `<projects>/.workspaces`, not the links `linkInstall` cleans up, and `installSkills` only ever wrote the names the tree has, so `clone-prototype` would have stayed there, listed and invokable, pointing at paths that are gone. It now removes a copy whose name the tree no longer ships, by the `managed-by` marker that makes a copy ours; a folder without one is the user's and stays. A busy Ark no longer abandons a paid take. `call` exited the process on any HTTP error, including the 429 and 5xx a poll is most likely to meet, so the task went on running with nobody holding its id. Those raise now, into the poll loop's own retry; every other code still fails where it did, and a submission that never started says so. frames.mjs measures the capture rectangle after the first scrub rather than 1.5 s into a running board: on a board that animates the phone itself the clip used to land wherever the load left it, x = 42.25 px one run and 42.50 the next on a test board, instead of the 20 px the phone actually starts at. composite.py clamps `--offset` instead of wrapping it, so a shift or a plate longer than the UI holds the first or last frame rather than cutting back to the start of the animation mid-shot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…iew fixes The skill is sp-create-video. ark.py takes --region intl|cn: BytePlus ModelArk by default, Volcengine Ark for a China key, the same request at a different host and model id. Prompts and prose are in English; the Chinese prompts the takes were shot with are translated, and prompting.md says the translation is unshot. From review: frames.mjs kills Chrome and removes its profile on any exit and says it needs Node 22; CI compiles skills/ and checks frames.mjs; an older app no longer removes a newer app's skill it has not heard of; ark.py's docstring says when the record is written; composite.py notes its memory ceiling; the prose flagged by the unslop pass is rewritten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Two changes: a sixth skill, and
sp-in front of every skill's name.1.
sp-create-videoTurn a board into a live-action clip of a real person using the product — an office, a lift, a walk down a corridor — with the interface kept pixel-exact rather than redrawn.
This is the
notion-purchase-motionsession of 10–11 September, kept. Thirteen paid takes on Seedance 2.5 found the rule the skill is built on: a model shown a screenshot composes its own UI (right layout, right blue, invented words), and a model given a video of the UI keeps it and paints a room around it. So the first phase is building an animated board, not writing a prompt.What ships
SKILL.mdscripts/frames.mjscurrentTimerather than recording the page — a recorder drops frames under load and never says so, and this way the same board twice is the same bytes twice.scripts/ark.py--region cn), polls it through a dropped connection, downloads the mp4 before its 24-hour URL expires, and writes the prompt, seed and usage beside it. Stdlib only.scripts/composite.pyuv run --with opencv-python-headless, so the toolkit keeps its two dependencies.references/Decisions
docs/2026-09-25-scene-video.mdcovers them: why Ark and not fal (fal was paid for and never returned a clip; Replicate wanted $0.97 per second; a 720p 10s take on Ark is about ¥11) and whyark.pydefaults to BytePlus ModelArk, Ark's name outside China, with--region cnfor a Volcengine key. Why the green plate ships as the fallback and not the route. And the evening spent on a530fromcloudflared: it has to run outside the agent's sandbox, and a fake-IP VPN breaks its edge lookup unless the edges are pinned.Review
All seven Codex comments are fixed in
bd74820/the follow-up commit: the scrub is nowgetAnimations()+currentTime(the delay rule was measured from wherever the pause landed, so two renders of one board differed by up to 170 levels a pixel); the documentedffmpegline crops to even dimensions, without which libx264 refused the 1179 px width outright; Chrome is resolved the wayrefkitresolves it, Windows and Edge included, with the profile in the OS temp directory;ark.pynumbers past the highest take rather than counting them, and writes its record as UTF-8; the documented call passes--key-file;composite.pyhandles a frame with no chroma in it.Verified
frames.mjsagainst the real board 19: 10 frames, 8 distinct, holds where the timeline holds, transparent outside.phone.composite.py(ported to derive the UI size from the frames rather than hard-coding 1179×2556) against the real plate and frames: 241 frames, the sheet upright on the glass, the thumb over it.ark.py --dryemits the same request body as the takes that succeeded.No mp4 is committed. Takes, plates and frames are derived and belong in a project's
scratch/video/.2. Every skill is named
sp-clone-prototype→sp-clone-prototype, and the same forsp-new-ui-mock,sp-define-productandsp-brand-kit.prototype-canvasbecomessp-canvas: shortened as well as prefixed, since everything here is a prototype and the skill is mostly thesp canvascommand.A skill directory is one flat namespace an agent shares between every source it installs from, and those were the plainest words for what they do — the names a user's own skill is likeliest to take. The collision is silent:
linkInstallkeeps the user's folder rather than replacing it (isOurCopy), so the app's skill simply never installs and the user runs something else under the name they read in the README.The rename carries three things with it:
linkInstallonly ever made links, so a renamed skill left~/.claude/skills/clone-prototypepointing into acurrent/skills/entry that is gone, which the agent lists as a broken skill. It now removes a link intocurrentthat resolves to nothing, by the same rulesp uninstalluses, and leaves anything the user put there alone. Covered by a new test indesktop/launch.test.ts./sp-clone-prototype.AGENTS.md, both READMEs, the canvas's strings and every skill's references.docs/and the tagged sections ofRELEASE-NOTES.mdare left as they are — dated records of what was true when written.docs/2026-09-25-sp-prefixed-skills.mdhas the reasoning and the two alternatives not taken.The welcome board generates again
canvases/00-welcome/gen.pyhad not been runnable since boards moved intocanvases/(51220fa):ASSETSread../../../assets/banner.webp, one level above the repo, and the banner at the real path is the landing page's hero now — download buttons and all — not the strip the board was built around. The board has its ownassets/in this PR, which is what the folder rules ask of every canvas folder: the art restored from before the landing page took the name (e47e45d), and the icon beside it. Two things that had drifted into the committed HTML are back in the generator,layout.json'sground(added by hand in 2c22caf) and the/sp-clone-prototypeline, andgen.pynow reproduces both committed files byte for byte.Checks
bun testindesktop/(10 pass),bun run testincanvas/(227 pass, 1 skipped),pytestintools/(40 pass).Since review
sp-scene-video→sp-create-video.ark.py --region intl|cn: BytePlus ModelArk (dreamina-seedance-2-5-260628) by default, Volcengine Ark (doubao-seedance-2-5-260628) for a China key. The ModelArk endpoint answers (401 on a bad key); no ModelArk take has been shot yet.prompting.mdsays the English wording is unshot.frames.mjscleans up Chrome and its profile on any exit and requires Node 22+; CI compilesskills/andnode --checksframes.mjs;installSkillsno longer removes a newer app's skill it has not heard of (new test); unslop pass on the new prose.🤖 Generated with Claude Code