From f895487da98cbbbb3ff4746d7e50c8f9e951eebe Mon Sep 17 00:00:00 2001 From: Ashley Wolf Date: Tue, 23 Jun 2026 19:40:15 -0700 Subject: [PATCH] Add contributor attribution to canvas extension cards Each canvas extension card now shows who contributed it, matching the by-author line already used on plugin and sample cards. Adds a standard npm author field to each extension (and the external entry), reads it in the website data generator, and renders it on the card and details modal. Contributors were sourced from the original PR/commit author for each extension on the staged branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/generate-website-data.mjs | 21 +++++++++++++++++++ extensions/accessibility-kanban/package.json | 1 + extensions/arcade-canvas/package.json | 2 +- extensions/backlog-swipe-triage/package.json | 1 + .../chromium-control-canvas/package.json | 2 +- extensions/color-orb/package.json | 1 + extensions/diagram-viewer/package.json | 1 + extensions/external.json | 1 + extensions/feedback-themes/package.json | 1 + extensions/gesture-review/package.json | 1 + .../release-notes-showcase/package.json | 1 + extensions/where-was-i/package.json | 1 + .../src/scripts/pages/extensions-render.ts | 16 +++++++++++++- website/src/scripts/pages/extensions.ts | 14 +++++++++++++ 14 files changed, 61 insertions(+), 3 deletions(-) diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index f20a8bbea..49b6aecf7 100755 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -101,6 +101,25 @@ function normalizeText(value, fallback = "") { return typeof value === "string" ? value.trim() : fallback; } +/** + * Normalize an author value (npm string form or { name, url } object) to + * { name, url? } | null. Returns null when no usable name is present. + */ +function normalizeAuthor(value) { + if (!value) return null; + if (typeof value === "string") { + const name = value.trim(); + return name ? { name } : null; + } + if (typeof value === "object") { + const name = normalizeText(value.name); + if (!name) return null; + const url = normalizeText(value.url); + return url ? { name, url } : { name }; + } + return null; +} + /** * Find the latest git-modified date for any file under a directory. */ @@ -1044,6 +1063,7 @@ function generateCanvasManifest(gitDates, commitSha) { installUrl, sourceUrl: null, external: false, + author: normalizeAuthor(packageJson.author), keywords, }); } @@ -1116,6 +1136,7 @@ function generateCanvasManifest(gitDates, commitSha) { installUrl, sourceUrl: sourceUrl || null, external: true, + author: normalizeAuthor(ext?.author), keywords, }); } diff --git a/extensions/accessibility-kanban/package.json b/extensions/accessibility-kanban/package.json index 48b33dbd3..8b47bce4a 100644 --- a/extensions/accessibility-kanban/package.json +++ b/extensions/accessibility-kanban/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "dependencies": { "@github/copilot-sdk": "latest" }, diff --git a/extensions/arcade-canvas/package.json b/extensions/arcade-canvas/package.json index 1c7734e04..4086990e7 100644 --- a/extensions/arcade-canvas/package.json +++ b/extensions/arcade-canvas/package.json @@ -2,7 +2,7 @@ "name": "arcade-canvas", "version": "1.0.0", "main": "extension.mjs", - "author": "Dan Wahlin", + "author": { "name": "Dan Wahlin", "url": "https://github.com/DanWahlin" }, "license": "MIT", "type": "module", "dependencies": { diff --git a/extensions/backlog-swipe-triage/package.json b/extensions/backlog-swipe-triage/package.json index 34f712651..fa38ba318 100644 --- a/extensions/backlog-swipe-triage/package.json +++ b/extensions/backlog-swipe-triage/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "James Montemagno", "url": "https://github.com/jamesmontemagno" }, "dependencies": { "@github/copilot-sdk": "1.0.1" }, diff --git a/extensions/chromium-control-canvas/package.json b/extensions/chromium-control-canvas/package.json index 5b6197b2f..180e11f77 100644 --- a/extensions/chromium-control-canvas/package.json +++ b/extensions/chromium-control-canvas/package.json @@ -2,7 +2,7 @@ "name": "chromium-control-canvas", "version": "1.0.0", "main": "extension.mjs", - "author": "Andrea Griffiths", + "author": { "name": "Andrea Griffiths", "url": "https://github.com/AndreaGriffiths11" }, "license": "MIT", "type": "module", "dependencies": { diff --git a/extensions/color-orb/package.json b/extensions/color-orb/package.json index a28cd5c3f..cd0f8e3eb 100644 --- a/extensions/color-orb/package.json +++ b/extensions/color-orb/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "dependencies": { "@github/copilot-sdk": "latest" }, diff --git a/extensions/diagram-viewer/package.json b/extensions/diagram-viewer/package.json index 054ebcf77..e97f026a1 100644 --- a/extensions/diagram-viewer/package.json +++ b/extensions/diagram-viewer/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "dependencies": { "@github/copilot-sdk": "latest" }, diff --git a/extensions/external.json b/extensions/external.json index c6b98bccf..f0e2f561a 100644 --- a/extensions/external.json +++ b/extensions/external.json @@ -3,6 +3,7 @@ "id": "coffilot", "name": "Coffilot", "description": "Java-focused Copilot canvas extension from jdubois.", + "author": { "name": "Julien Dubois", "url": "https://github.com/jdubois" }, "keywords": [ "java", "canvas", diff --git a/extensions/feedback-themes/package.json b/extensions/feedback-themes/package.json index f769acee7..e1aa4543f 100644 --- a/extensions/feedback-themes/package.json +++ b/extensions/feedback-themes/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "dependencies": { "@github/copilot-sdk": "latest" }, diff --git a/extensions/gesture-review/package.json b/extensions/gesture-review/package.json index 1f5a911f9..d0627cab7 100644 --- a/extensions/gesture-review/package.json +++ b/extensions/gesture-review/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "dependencies": { "@github/copilot-sdk": "latest" }, diff --git a/extensions/release-notes-showcase/package.json b/extensions/release-notes-showcase/package.json index cb192cd1c..490f8ace9 100644 --- a/extensions/release-notes-showcase/package.json +++ b/extensions/release-notes-showcase/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "James Montemagno", "url": "https://github.com/jamesmontemagno" }, "dependencies": { "@github/copilot-sdk": "1.0.1" }, diff --git a/extensions/where-was-i/package.json b/extensions/where-was-i/package.json index 5534b4dbb..deb44ac1b 100644 --- a/extensions/where-was-i/package.json +++ b/extensions/where-was-i/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", + "author": { "name": "Aaron Powell", "url": "https://github.com/aaronpowell" }, "description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.", "keywords": [ "interrupt-recovery", diff --git a/website/src/scripts/pages/extensions-render.ts b/website/src/scripts/pages/extensions-render.ts index ac8f2539a..9e81f19c6 100644 --- a/website/src/scripts/pages/extensions-render.ts +++ b/website/src/scripts/pages/extensions-render.ts @@ -1,4 +1,4 @@ -import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils"; +import { escapeHtml, getGitHubUrl, getLastUpdatedHtml, sanitizeUrl } from "../utils"; import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render"; export interface RenderableExtension { @@ -34,6 +34,7 @@ export interface RenderableExtension { installUrl?: string | null; sourceUrl?: string | null; external?: boolean; + author?: { name: string; url?: string } | null; } export type ExtensionSortOption = "title" | "lastUpdated"; @@ -92,8 +93,21 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string { `; + const authorHtml = item.author?.name + ? `by ${ + item.author.url + ? `${escapeHtml( + item.author.name + )}` + : escapeHtml(item.author.name) + }` + : ""; + const metaHtml = ` ${item.external ? 'External' : ""} + ${authorHtml} ${getLastUpdatedHtml(item.lastUpdated)} `; diff --git a/website/src/scripts/pages/extensions.ts b/website/src/scripts/pages/extensions.ts index 3c3c9c946..201b8edcb 100644 --- a/website/src/scripts/pages/extensions.ts +++ b/website/src/scripts/pages/extensions.ts @@ -15,6 +15,7 @@ import { getGitHubUrl, getQueryParam, getQueryParamValues, + sanitizeUrl, showToast, updateQueryParams, } from "../utils"; @@ -178,6 +179,19 @@ function openDetailsModal( if (item.external) { metaParts.push('External'); } + if (item.author?.name) { + metaParts.push( + item.author.url + ? `by ${escapeHtml( + item.author.name + )}` + : `by ${escapeHtml( + item.author.name + )}` + ); + } if (item.lastUpdated) { metaParts.push( `Updated ${escapeHtml(