Add Site Studio canvas extension #2117
Open
ayangupt wants to merge 1 commit into
Open
Conversation
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>
Contributor
There was a problem hiding this comment.
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 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 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
approved these changes
Jun 25, 2026
Contributor
|
Couple of CCR comments that are worth reviewing |
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.
Pull Request Checklist
extensions/site-studio/).npm startand verified thatREADME.mdis up to date.stagedbranch 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.
[Sample: ...]starter placeholders and an AI-draft action that builds on context you have already providedFolder
extensions/site-studio/:extension.mjs,canvas.json,package.json,assets/preview.png.Type of Contribution
Additional Notes
Built and tested with GitHub Copilot. AI-assisted (
🤖🤖🤖in title per CONTRIBUTING.md). Note: canvas extensions are not part of README generation, sonpm startproduces no README changes.