Skip to content

Add Site Studio canvas extension #2117

Open
ayangupt wants to merge 2 commits into
github:mainfrom
ayangupt:add-site-studio-canvas
Open

Add Site Studio canvas extension #2117
ayangupt wants to merge 2 commits into
github:mainfrom
ayangupt:add-site-studio-canvas

Conversation

@ayangupt

Copy link
Copy Markdown
Contributor

Pull Request Checklist

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have read and followed the Guidance for submissions involving paid services.
  • My contribution adds a new canvas extension in the correct directory (extensions/site-studio/).
  • The file follows the required naming convention.
  • The content is clearly structured and follows the example format.
  • I have tested my canvas extension with GitHub Copilot.
  • I have run npm start and verified that README.md is up to date.
  • I am targeting the staged branch for this pull request.

Description

Site Studio is a canvas extension for planning, drafting, and tracking a personal website — section by section, collaboratively with your agent.

  • Plan — a status board for every section (Not Started -> In Progress -> Built -> Review -> Approved)
  • Draft — an autosaving, schema-validated content editor with [Sample: ...] starter placeholders and an AI-draft action that builds on context you have already provided
  • Review — a live feed of every content change, status change, and milestone

Folder extensions/site-studio/: extension.mjs, canvas.json, package.json, assets/preview.png.


Type of Contribution

  • New canvas extension.

Additional Notes

Built and tested with GitHub Copilot. AI-assisted (🤖🤖🤖 in title per CONTRIBUTING.md). Note: canvas extensions are not part of README generation, so npm start produces no README changes.

Site Studio is a canvas extension for planning, drafting, and tracking a
personal website section by section. It gives you and your agent a shared
dashboard with a status board, an autosaving content editor (with AI-draft
and [Sample: ...] placeholders), and a live feed of every change and milestone.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 24, 2026 19:37
@ayangupt ayangupt requested a review from aaronpowell as a code owner June 24, 2026 19:37
@github-actions github-actions Bot added canvas-extension PR touches canvas extensions new-submission PR adds at least one new contribution labels Jun 24, 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 a new Site Studio Copilot canvas extension under extensions/site-studio/, providing an interactive UI + local HTTP API for planning, drafting, and reviewing personal website sections with agent collaboration.

Changes:

  • Introduces the Site Studio extension runtime (extension.mjs) with state persistence, SSE updates, template packs, and canvas actions.
  • Adds extension metadata (canvas.json) including keywords and screenshot paths.
  • Adds extension package metadata (package.json) for module/type entrypoint and SDK dependency.
Show a summary per file
File Description
extensions/site-studio/package.json Declares the extension package metadata and Copilot SDK dependency.
extensions/site-studio/extension.mjs Implements the Site Studio canvas (server, UI rendering, persistence, actions).
extensions/site-studio/canvas.json Registers canvas metadata, keywords, version, and screenshot references.

Copilot's findings

  • Files reviewed: 2/4 changed files
  • Comments generated: 8

Comment on lines +24 to +26
const VALID_STATUS = new Set(["Not Started", "In Progress", "Built", "Review", "Approved", "Needs Changes"]);
const VALID_CHANGE_TYPES = new Set(["file_modified", "file_added", "file_deleted", "status_change", "commit", "milestone"]);
const STATUS_ORDER = ["Not Started", "In Progress", "Built", "Review", "Approved", "Needs Changes"];
Comment on lines +409 to +413
for (const cwd of candidates) {
try {
const branch = execSync("git --no-pager rev-parse --abbrev-ref HEAD", { cwd, stdio: ["ignore", "pipe", "ignore"] })
.toString()
.trim();
Comment thread extensions/site-studio/extension.mjs Outdated
Comment on lines +561 to +567
async function persistState() {
if (!stateCache) {
return;
}
await mkdir(ARTIFACTS_DIR, { recursive: true });
await writeFile(STATE_FILE, JSON.stringify(stateCache, null, 2), "utf8");
}
Comment on lines +828 to +845
async function readBodyJson(req) {
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
if (!chunks.length) {
return {};
}
const body = Buffer.concat(chunks).toString("utf8");
if (!body.trim()) {
return {};
}
try {
return JSON.parse(body);
} catch {
throw new CanvasError("invalid_json", "Request body must be valid JSON.");
}
}
Comment on lines +1535 to +1546
if (req.method === "GET" && pathname === "/events") {
const entry = servers.get(instanceId);
res.statusCode = 200;
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.write("event: state_update\n");
res.write(`data: ${JSON.stringify(stateCache)}\n\n`);
entry.clients.add(res);
req.on("close", () => entry.clients.delete(res));
return;
}
Comment on lines +700 to +710
function upsertSectionContent(sectionId, field, value, mark = "draft", author = "agent") {
const section = findSectionOrThrow(sectionId);
const normalizedField = safeString(field).trim();
if (!normalizedField) {
throw new CanvasError("invalid_field", "Field name is required.");
}
const normalizedValue = safeString(value, "");
validateFieldValue(section.id, normalizedField, normalizedValue, mark);
const current = safeString(section.content[normalizedField], "");
section.content[normalizedField] = normalizedValue;
section.lastModified = nowIso();
Comment thread extensions/site-studio/extension.mjs Outdated
Comment on lines +734 to +736
if (!section.content || !(normalizedField in section.content)) {
return { section, field: normalizedField, removed: false };
}
Comment on lines +1236 to +1241
const aiAssist = '<div class="ai-assist">' +
'<span class="ai-tip-wrap">' +
'<span class="ai-label">✨ Generate with AI</span>' +
'<span class="ai-info" aria-hidden="true">ⓘ</span>' +
'<span class="ai-tip-bubble" role="tooltip">' + esc(aiTip) + '</span>' +
'</span>' +
aaronpowell
aaronpowell previously approved these changes Jun 25, 2026
@aaronpowell

Copy link
Copy Markdown
Contributor

Couple of CCR comments that are worth reviewing

@aaronpowell aaronpowell changed the base branch from staged to main June 25, 2026 04:41
@github-actions github-actions Bot added the targets-main PR targets main instead of staged label Jun 25, 2026
Robustness, prototype-pollution safety, and accessibility fixes from the
Copilot code review on github#2117:

- Add "ai_request" to VALID_CHANGE_TYPES so log_change accepts the change
  type the server itself emits (e.g. /api/request-generation).
- Bound the git branch lookup with timeout + maxBuffer so a hung git can't
  block the extension process and canvas UI.
- Serialize state persistence via a promise queue and snapshot state
  synchronously, so concurrent mutations can't clobber newer state on disk.
- Enforce a maximum request body size in readBodyJson to avoid unbounded
  memory use on the loopback server.
- Guard the /events handler against a missing/closed instance instead of
  throwing when servers.get(instanceId) is undefined.
- Reject unsafe field names (__proto__, prototype, constructor) in
  upsertSectionContent and use an own-property check in deleteSectionContent
  to prevent prototype pollution.
- Make the "Generate with AI" info tooltip reachable by keyboard and screen
  readers (focusable, labelled) instead of mouse-hover only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the targets-main PR targets main instead of staged label Jun 25, 2026
@ayangupt ayangupt requested a review from aaronpowell June 25, 2026 16:56
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 new-submission PR adds at least one new contribution targets-main PR targets main instead of staged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants