Add APNG Studio canvas extension#2272
Open
AndreaGriffiths11 wants to merge 8 commits into
Open
Conversation
APNG Studio is an interactive GitHub Copilot canvas extension for building Animated PNG (APNG) files from frames: draw or upload frames, tune per-frame timing and compositing, preview live, send the result to a phone by QR, and export an animated .png. Adds extensions/apng-studio/ with the required .github/plugin/plugin.json and assets/preview.png, and regenerates .github/plugin/marketplace.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
🔴 Contributor Reputation Check: HIGH risk
Maintainers: please review this contributor before merging. |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds APNG Studio, a canvas extension for creating, previewing, sharing, and exporting animated PNGs.
Changes:
- Adds APNG and QR encoders with persistent project storage.
- Adds the interactive canvas UI and agent actions.
- Registers the extension in the marketplace.
Show a summary per file
| File | Description |
|---|---|
.github/plugin/marketplace.json |
Registers APNG Studio. |
extensions/apng-studio/.github/plugin/plugin.json |
Defines extension metadata. |
extensions/apng-studio/LICENSE |
Adds the MIT license. |
extensions/apng-studio/README.md |
Documents features and installation. |
extensions/apng-studio/apng.mjs |
Implements PNG/APNG encoding. |
extensions/apng-studio/assets/demo.png |
Provides the README demonstration. |
extensions/apng-studio/assets/preview.png |
Provides the marketplace preview. |
extensions/apng-studio/copilot-extension.json |
Defines runtime metadata. |
extensions/apng-studio/extension.mjs |
Implements storage, actions, servers, and sharing. |
extensions/apng-studio/qr.mjs |
Implements QR encoding. |
extensions/apng-studio/web/app.js |
Implements client-side behavior. |
extensions/apng-studio/web/index.html |
Defines the canvas interface. |
extensions/apng-studio/web/styles.css |
Styles the canvas interface. |
Review details
- Files reviewed: 11/13 changed files
- Comments generated: 9
- Review effort level: Medium
Security and robustness (extension.mjs):
- Reject "." and ".." project ids so canvas/action input cannot resolve
outside artifacts/.
- Bound request bodies (1 MiB JSON, 40 MiB frame upload); return 413 when
exceeded.
- Reject malformed JSON with 400 instead of coercing to {}, so a truncated
body cannot trigger destructive routes such as /frames/clear.
- Scope shares per project so one canvas cannot rotate or stop another's
share; the LAN server is shared and torn down once no shares remain.
- get_state treats hiddenFirst as active only with >=2 frames, matching the
encoder, so a single-frame project reports the correct duration.
Accessibility (web/):
- Expose Pen/Eraser state with aria-pressed and keep it in sync in setTool.
- Mark the toast as an aria-live status region.
Packaging:
- Add extensions/apng-studio/.gitignore with artifacts/ so the documented
runtime-data exclusion holds for this extension.
- Update the README install section to reference the committed awesome-copilot
extension path.
Regenerated .github/plugin/marketplace.json.
Update the plugin description (and regenerated marketplace entry) to refer to the host as the GitHub Copilot app.
Security: - Require a per-server access token on every loopback data/mutation request (minted per canvas server, carried in the iframe URL, attached to every renderer request). Static assets stay public. Blocks a local process or cross-origin page from reading state or driving mutations. Concurrency and durability: - Serialize the load-mutate-save cycle per project; dedupe concurrent first loads; write project.json via temp file + rename. Lifecycle: - Track SSE clients per instance and end them before server.close(). - Stop a project's LAN share only when no other panel references it. Correctness: - Report exact numerator/denominator total duration; handle pointercancel. QR and accessibility: - Place QR version bits least-significant-bit first (versions 7-10). - Associate delay/fps/dispose/blend labels with their inputs via for=.
- Run assemble() under the project lock; add_color_frame renders and appends within one lock. - Validate uploaded frames (PNG chunk scan, size + dimension checks) before writing; first frame stores real dimensions; CanvasError maps to 400. - Validate move delta; clear frames in memory before deleting files. - Route the open-handler rename through applySettings. - Deduplicate LAN share startup; clean up if the canvas closes mid-start. - End SSE streams before server.close(); skip dead streams in broadcast. - Exact fractional duration; drawing surface stays >= 1px; refresh state before re-seeding "start from last frame".
- Validate uploaded frames are 8-bit RGBA non-interlaced PNGs (standard compression/filter); reject formats the codec can't encode. - Cap projects at 600 frames (add + duplicate). - Bind the LAN share server to the private address, not 0.0.0.0; require a private address and rebind when it changes while idle; build the URL from the bound address. - Align width/height inputs and clampSize to the 2048 maximum. - Size the drawing surface by width + aspect ratio for narrow panels. - set_frame with an unknown frameId returns a validation error.
- 256 MiB aggregate encoded-byte budget (add + duplicate); per-frame size tracked and backfilled on load, so a frame count alone no longer bounds assembly memory. - Frame timing is one exclusive mode (delayMs | fps | delayNum/delayDen); combinations are rejected instead of producing hybrid delays. - Collision-resistant project storage keys: safe ids are used verbatim (existing projects still load) and only unsafe ids get a hash suffix, so "foo/bar" and "foo?bar" can't share a directory. - Load only treats a missing file as a new project; other read/parse errors surface instead of silently overwriting real data. - Share server binds to a real LAN interface (skips virtual/VPN/container, prefers physical NICs and common ranges). - Generated export names get millisecond + random suffix to avoid same-second overwrite. - PNG validator accepts the encoder's Uint8Array so agent solid-color frames aren't rejected.
…rame - Write the PNG before mutating project metadata on add/duplicate, and delete after persisting, so an interrupted disk op can't dangle a reference or advance unsaved state. - Evict a project from the in-memory cache if its save fails, so a transient write error can't desync the live session from disk. - Reject on the loopback server's listen error and drop a half-initialized instance so startup failures clean up and can retry. - Normalize APNG dispose_op PREVIOUS to BACKGROUND on the first animated frame (spec requirement). - Release decoded ImageBitmaps after use so a large upload batch doesn't retain native image memory. - Surface otherwise-silent async UI failures via a toast; check the state response is ok before parsing it.
| const EXPORTS_DIR = join(ARTIFACTS_DIR, "exports"); | ||
| const DEFAULT_PROJECT = "default"; | ||
| const MAX_FRAMES = 600; // count cap so a project can't accumulate unbounded frames | ||
| const MAX_TOTAL_BYTES = 256 << 20; // 256 MiB of encoded frames — bounds assembly memory |
Comment on lines
+366
to
+367
| const res = await fetch(withKey(`/preview.png?n=${nonce()}`)); | ||
| const blob = await res.blob(); |
Comment on lines
+1197
to
+1201
| } else { | ||
| // Re-open: repoint the existing server at the requested | ||
| // project in place so the loopback URL stays stable. | ||
| entry.projectId = projectId; | ||
| } |
| <span class="dot">·</span> | ||
| <span id="meta-loops">loops ∞</span> | ||
| </div> | ||
| <div class="row gap"> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
APNG Studio is an interactive GitHub Copilot canvas extension for building Animated PNG (APNG) files from frames. It renders in a side panel and is also driveable through agent actions.
Features:
dispose_opandblend_opfrom the APNG spec..png(byte-compatible with PNG, so every viewer can open it).The APNG codec and a dependency-free QR encoder are vendored (
apng.mjs,qr.mjs); no third-party runtime dependencies.Contribution type
Canvas extension, added at
extensions/apng-studio/with.github/plugin/plugin.jsonandassets/preview.png.Validation
npm run plugin:validate: extension apng-studio is valid.npm start: regenerated.github/plugin/marketplace.json; README requires no changes.