Skip to content

Add APNG Studio canvas extension#2272

Open
AndreaGriffiths11 wants to merge 6 commits into
github:mainfrom
AndreaGriffiths11:add-apng-studio-canvas
Open

Add APNG Studio canvas extension#2272
AndreaGriffiths11 wants to merge 6 commits into
github:mainfrom
AndreaGriffiths11:add-apng-studio-canvas

Conversation

@AndreaGriffiths11

Copy link
Copy Markdown
Contributor

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:

  • Draw frames on a built-in canvas (pen/eraser, fill, onion-skin) or upload images.
  • Per-frame timing as an exact numerator/denominator fraction, plus per-frame dispose_op and blend_op from the APNG spec.
  • Loop count and an optional hidden first frame (static fallback for non-APNG viewers).
  • Live preview, and a "Send to phone" QR that serves the animation over the LAN from a short-lived, token-gated endpoint.
  • Export a valid animated .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.json and assets/preview.png.

Validation

  • npm run plugin:validate: extension apng-studio is valid.
  • npm start: regenerated .github/plugin/marketplace.json; README requires no changes.

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>
Copilot AI review requested due to automatic review settings July 11, 2026 11:20
@github-actions github-actions Bot added canvas-extension PR touches canvas extensions new-submission PR adds at least one new contribution labels Jul 11, 2026
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🔴 Contributor Reputation Check: HIGH risk

Check Risk
Profile HIGH
Credential audit NONE

Maintainers: please review this contributor before merging.
See the workflow run for full details.
Automated check powered by AGT.

@github-actions github-actions Bot added the needs-review:HIGH Contributor reputation check flagged HIGH risk label Jul 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/README.md Outdated
Comment thread extensions/apng-studio/README.md
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
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.
Copilot AI review requested due to automatic review settings July 11, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 12/14 changed files
  • Comments generated: 10
  • Review effort level: Medium

Comment thread extensions/apng-studio/qr.mjs
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/web/app.js Outdated
Comment thread extensions/apng-studio/web/app.js
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Update the plugin description (and regenerated marketplace entry) to refer to
the host as the GitHub Copilot app.
Copilot AI review requested due to automatic review settings July 11, 2026 12:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 12/14 changed files
  • Comments generated: 8
  • Review effort level: Medium

Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
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=.
Copilot AI review requested due to automatic review settings July 11, 2026 12:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 12/14 changed files
  • Comments generated: 6
  • Review effort level: Medium

Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
Comment thread extensions/apng-studio/web/app.js Outdated
Comment thread extensions/apng-studio/web/app.js Outdated
Comment thread extensions/apng-studio/extension.mjs Outdated
- 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".
Copilot AI review requested due to automatic review settings July 11, 2026 13:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 12/14 changed files
  • Comments generated: 6
  • Review effort level: Medium

Comment thread extensions/apng-studio/extension.mjs
Comment thread extensions/apng-studio/web/index.html Outdated
Comment thread extensions/apng-studio/web/app.js Outdated
Comment thread extensions/apng-studio/extension.mjs
Comment thread extensions/apng-studio/extension.mjs
Comment thread extensions/apng-studio/extension.mjs
- 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.
Copilot AI review requested due to automatic review settings July 11, 2026 13:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 12/14 changed files
  • Comments generated: 7
  • Review effort level: Medium

const ARTIFACTS_DIR = join(EXT_DIR, "artifacts");
const EXPORTS_DIR = join(ARTIFACTS_DIR, "exports");
const DEFAULT_PROJECT = "default";
const MAX_FRAMES = 600; // aggregate cap so a project can't exhaust the host on assemble
Comment on lines +1018 to +1021
delayMs: { type: "integer", minimum: 0, maximum: 65535 },
fps: { type: "integer", minimum: 1, maximum: 1000, description: "Exact frame rate; sets delay to 1/fps s." },
delayNum: { type: "integer", minimum: 0, maximum: 65535 },
delayDen: { type: "integer", minimum: 1, maximum: 65535 },
Comment on lines +699 to +706
for (const addrs of Object.values(networkInterfaces())) {
for (const a of addrs || []) {
if (a.family === "IPv4" && !a.internal) candidates.push(a.address);
}
}
const isPrivate = (ip) =>
/^10\./.test(ip) || /^192\.168\./.test(ip) || /^172\.(1[6-9]|2\d|3[01])\./.test(ip);
return candidates.find(isPrivate) || null;
Comment on lines +59 to +63
const sanitizeId = (s) => {
const cleaned = String(s || DEFAULT_PROJECT).replace(/[^\w.-]+/g, "_").slice(0, 64);
// Reject "." and ".." (and empty) so a project id can never escape ARTIFACTS_DIR.
if (!cleaned || cleaned === "." || cleaned === "..") return DEFAULT_PROJECT;
return cleaned;
Comment on lines +102 to +106
try {
meta = JSON.parse(await fs.readFile(join(projectDir(pid), "project.json"), "utf8"));
} catch {
meta = null;
}
Comment on lines +239 to +240
delayNum: opts.delayNum != null ? opts.delayNum : opts.delayMs != null ? opts.delayMs : 120,
delayDen: opts.delayDen,
const bytes = await assembleFromMeta(meta);
if (!bytes) throw new CanvasError("no_frames", "Nothing to export — add at least one frame first.");
await ensureDir(EXPORTS_DIR);
const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

canvas-extension PR touches canvas extensions needs-review:HIGH Contributor reputation check flagged HIGH risk new-submission PR adds at least one new contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants