From ba9567cd22ca7959c5f4f67adef89dedd3cfdee0 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 27 Feb 2026 11:45:32 +1100 Subject: [PATCH 1/6] feat(schema): add external recipe fields to cookbook schema Add optional external, url, and author fields to the recipe schema in cookbook.schema.json. When external is true, url is required via conditional validation. Author supports name (required) and url (optional) for attribution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .schemas/cookbook.schema.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.schemas/cookbook.schema.json b/.schemas/cookbook.schema.json index 857bd84e7..f0465d88c 100644 --- a/.schemas/cookbook.schema.json +++ b/.schemas/cookbook.schema.json @@ -88,7 +88,40 @@ "items": { "type": "string" } + }, + "external": { + "type": "boolean", + "description": "Whether this recipe links to an external repository", + "default": false + }, + "url": { + "type": "string", + "description": "URL to the external repository or project (required when external is true)", + "format": "uri" + }, + "author": { + "type": "object", + "description": "Author information for external recipes", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "Author display name or GitHub username" + }, + "url": { + "type": "string", + "description": "Author profile URL", + "format": "uri" + } + } } + }, + "if": { + "properties": { "external": { "const": true } }, + "required": ["external"] + }, + "then": { + "required": ["url"] } } } From de75aecae6e78330f4ee34e47850a58b92e6ac38 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 27 Feb 2026 11:45:40 +1100 Subject: [PATCH 2/6] feat(data): support external recipes in data generator - External recipes (external: true) skip local file validation - Validate URL format for external recipes - Pass through external, url, and author fields to output JSON - Add per-recipe languages array: derived from resolved variant keys for local recipes, and from tags matching known language IDs for external recipes - Collect language IDs in a first pass before processing recipes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/generate-website-data.mjs | 40 +++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index 9b145bd5b..79b28a1f8 100644 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -742,9 +742,12 @@ function generateSamplesData() { const allTags = new Set(); let totalRecipes = 0; - const cookbooks = cookbookManifest.cookbooks.map((cookbook) => { - // Collect languages + // First pass: collect all known language IDs across cookbooks + cookbookManifest.cookbooks.forEach((cookbook) => { cookbook.languages.forEach((lang) => allLanguages.add(lang.id)); + }); + + const cookbooks = cookbookManifest.cookbooks.map((cookbook) => { // Process recipes and add file paths const recipes = cookbook.recipes.map((recipe) => { @@ -753,6 +756,36 @@ function generateSamplesData() { recipe.tags.forEach((tag) => allTags.add(tag)); } + totalRecipes++; + + // External recipes link to an external URL — skip local file resolution + if (recipe.external) { + if (recipe.url) { + try { + new URL(recipe.url); + } catch { + console.warn(`Warning: Invalid URL for external recipe "${recipe.id}": ${recipe.url}`); + } + } else { + console.warn(`Warning: External recipe "${recipe.id}" is missing a url`); + } + + // Derive languages from tags that match known language IDs + const recipeLanguages = (recipe.tags || []).filter((tag) => allLanguages.has(tag)); + + return { + id: recipe.id, + name: recipe.name, + description: recipe.description, + tags: recipe.tags || [], + languages: recipeLanguages, + external: true, + url: recipe.url || null, + author: recipe.author || null, + variants: {}, + }; + } + // Build variants with file paths for each language const variants = {}; cookbook.languages.forEach((lang) => { @@ -771,13 +804,12 @@ function generateSamplesData() { } }); - totalRecipes++; - return { id: recipe.id, name: recipe.name, description: recipe.description, tags: recipe.tags || [], + languages: Object.keys(variants), variants, }; }); From fd24ef86ce4bded98471019c7d32c878b75c8b3a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 27 Feb 2026 11:45:50 +1100 Subject: [PATCH 3/6] feat(website): render external recipe cards on cookbook page - Extend Recipe interface with external, url, author, and languages - Render external recipes with Community badge, author attribution, and View on GitHub link instead of View Recipe/View Example buttons - Language filter uses per-recipe languages array uniformly - Remove Nerd Font icons from select dropdown options (native selects cannot render custom web fonts) - Add CSS for external recipe cards (dashed border, badge, author) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pages/learning-hub/cookbook/index.astro | 33 ++++++++++ website/src/scripts/pages/samples.ts | 60 ++++++++++++++++--- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/website/src/pages/learning-hub/cookbook/index.astro b/website/src/pages/learning-hub/cookbook/index.astro index 34e1ba3c9..9187ea443 100644 --- a/website/src/pages/learning-hub/cookbook/index.astro +++ b/website/src/pages/learning-hub/cookbook/index.astro @@ -197,6 +197,39 @@ const base = import.meta.env.BASE_URL; font-style: italic; } + /* External recipe card */ + .recipe-card.external { + border-style: dashed; + } + + .recipe-badge.external-badge { + display: inline-flex; + align-items: center; + gap: 4px; + background: var(--color-bg-secondary); + color: var(--color-text-muted); + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; + border: 1px solid var(--color-border); + white-space: nowrap; + } + + .recipe-author-line { + margin-bottom: 12px; + font-size: 13px; + color: var(--color-text-muted); + } + + .recipe-author-line a { + color: var(--color-link); + text-decoration: none; + } + + .recipe-author-line a:hover { + text-decoration: underline; + } + /* Empty state */ .empty-state { text-align: center; diff --git a/website/src/scripts/pages/samples.ts b/website/src/scripts/pages/samples.ts index cdc2e7d3a..2007c3dc1 100644 --- a/website/src/scripts/pages/samples.ts +++ b/website/src/scripts/pages/samples.ts @@ -25,7 +25,11 @@ interface Recipe { name: string; description: string; tags: string[]; + languages: string[]; variants: Record; + external?: boolean; + url?: string | null; + author?: { name: string; url?: string } | null; } interface Cookbook { @@ -138,7 +142,7 @@ function setupFilters(): void { languages.forEach((lang, id) => { const option = document.createElement("option"); option.value = id; - option.textContent = `${lang.icon} ${lang.name}`; + option.textContent = lang.name; languageSelect.appendChild(option); }); @@ -257,10 +261,10 @@ function getFilteredRecipes(): { ); } - // Apply language filter + // Apply language filter using per-recipe languages array if (selectedLanguage) { - results = results.filter( - ({ recipe }) => recipe.variants[selectedLanguage!] + results = results.filter(({ recipe }) => + recipe.languages.includes(selectedLanguage!) ); } @@ -370,14 +374,54 @@ function renderRecipeCard( const recipeKey = `${cookbook.id}-${recipe.id}`; const isExpanded = expandedRecipes.has(recipeKey); - // Determine which language to show - const displayLang = selectedLanguage || cookbook.languages[0]?.id || "nodejs"; - const variant = recipe.variants[displayLang]; - const tags = recipe.tags .map((tag) => `${escapeHtml(tag)}`) .join(""); + // External recipe — link to external URL + if (recipe.external && recipe.url) { + const authorHtml = recipe.author + ? `by ${ + recipe.author.url + ? `${escapeHtml(recipe.author.name)}` + : escapeHtml(recipe.author.name) + }` + : ""; + + return ` +
+
+

${highlightedName || escapeHtml(recipe.name)}

+ + + Community + +
+

${escapeHtml(recipe.description)}

+ ${authorHtml ? `
${authorHtml}
` : ""} +
${tags}
+ +
+ `; + } + + // Local recipe — existing behavior + // Determine which language to show + const displayLang = selectedLanguage || cookbook.languages[0]?.id || "nodejs"; + const variant = recipe.variants[displayLang]; + const langIndicators = cookbook.languages .filter((lang) => recipe.variants[lang.id]) .map( From 04083bddf750f2d53ad4ccf9bf92ff42bbfc02e4 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 27 Feb 2026 11:45:59 +1100 Subject: [PATCH 4/6] feat(cookbook): add community samples section with first external recipe Add a Community Samples cookbook section to cookbook.yml with the Node.js Agentic Issue Resolver as the first external recipe entry, linking to https://github.com/Impesud/nodejs-copilot-issue-resolver. Resolves the use case from PR #613 for supporting external samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cookbook/cookbook.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cookbook/cookbook.yml b/cookbook/cookbook.yml index 43f66f3fc..cedf016c1 100644 --- a/cookbook/cookbook.yml +++ b/cookbook/cookbook.yml @@ -69,3 +69,24 @@ cookbooks: - playwright - mcp - wcag + + - id: community-samples + name: Community Samples + description: Community-contributed projects and examples for GitHub Copilot + path: cookbook/community-samples + featured: false + languages: [] + recipes: + - id: nodejs-agentic-issue-resolver + name: Node.js Agentic Issue Resolver + description: A resilient agentic workflow for autonomous codebase exploration and fixing, optimized for the Copilot SDK Technical Preview + external: true + url: https://github.com/Impesud/nodejs-copilot-issue-resolver + author: + name: Impesud + url: https://github.com/Impesud + tags: + - nodejs + - copilot-sdk + - agents + - community From 0273497665d31329ff0adefbca785de6edba925b Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 27 Feb 2026 11:50:39 +1100 Subject: [PATCH 5/6] feat(cookbook): add Copilot SDK Web App to community samples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add aaronpowell/copilot-sdk-web-app — a full-stack chat app built with the GitHub Copilot SDK, .NET Aspire, and React. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cookbook/cookbook.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cookbook/cookbook.yml b/cookbook/cookbook.yml index cedf016c1..0134ebb7d 100644 --- a/cookbook/cookbook.yml +++ b/cookbook/cookbook.yml @@ -90,3 +90,16 @@ cookbooks: - copilot-sdk - agents - community + - id: copilot-sdk-web-app + name: Copilot SDK Web App + description: A full-stack chat application built with the GitHub Copilot SDK, .NET Aspire, and React with GitHub OAuth, session history, and model selection + external: true + url: https://github.com/aaronpowell/copilot-sdk-web-app + author: + name: aaronpowell + url: https://github.com/aaronpowell + tags: + - dotnet + - copilot-sdk + - web-app + - community From 716838031330d959b870833decbf474118f35982 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 2 Mar 2026 10:22:27 +1100 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- website/src/scripts/pages/samples.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/scripts/pages/samples.ts b/website/src/scripts/pages/samples.ts index 2007c3dc1..ac14bc1db 100644 --- a/website/src/scripts/pages/samples.ts +++ b/website/src/scripts/pages/samples.ts @@ -391,7 +391,7 @@ function renderRecipeCard( return `
+ }" data-recipe="${escapeHtml(recipeKey)}">

${highlightedName || escapeHtml(recipe.name)}

@@ -419,10 +419,10 @@ function renderRecipeCard( // Local recipe — existing behavior // Determine which language to show - const displayLang = selectedLanguage || cookbook.languages[0]?.id || "nodejs"; + const displayLang = selectedLanguage || cookbook.languages?.[0]?.id || "nodejs"; const variant = recipe.variants[displayLang]; - const langIndicators = cookbook.languages + const langIndicators = (cookbook.languages ?? []) .filter((lang) => recipe.variants[lang.id]) .map( (lang) =>