diff --git a/website/public/images/social-image.png b/website/public/images/social-image.png
new file mode 100644
index 000000000..76abe3fe9
Binary files /dev/null and b/website/public/images/social-image.png differ
diff --git a/website/src/layouts/BaseLayout.astro b/website/src/layouts/BaseLayout.astro
index 5efa9caef..35fc684d7 100644
--- a/website/src/layouts/BaseLayout.astro
+++ b/website/src/layouts/BaseLayout.astro
@@ -13,6 +13,8 @@ const {
activeNav = "",
} = Astro.props;
const base = import.meta.env.BASE_URL;
+const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
+const socialImageUrl = new URL(`${base}images/social-image.png`, Astro.site);
// Get git commit SHA and build date at build time
let commitSha = "unknown";
@@ -35,8 +37,24 @@ try {
- {title} - Awesome GitHub Copilot
+ {title} | Awesome GitHub Copilot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= {};
+
+const RESOURCE_TYPE_TO_JSON: Record = {
+ agent: "agents.json",
+ instruction: "instructions.json",
+ skill: "skills.json",
+ hook: "hooks.json",
+ workflow: "workflows.json",
+ plugin: "plugins.json",
+};
+
+/**
+ * Look up the display title for a resource from its JSON data file
+ */
+async function resolveResourceTitle(
+ filePath: string,
+ type: string
+): Promise {
+ const fallback = filePath.split("/").pop() || filePath;
+ const jsonFile = RESOURCE_TYPE_TO_JSON[type];
+ if (!jsonFile) return fallback;
+
+ if (!(jsonFile in resourceDataCache)) {
+ resourceDataCache[jsonFile] = await fetchData(jsonFile);
+ }
+
+ const data = resourceDataCache[jsonFile];
+ if (!data) return fallback;
+
+ // Try exact path match first
+ const item = data.items.find((i) => i.path === filePath);
+ if (item) return item.title;
+
+ // For skills/hooks, the modal receives the file path (e.g. skills/foo/SKILL.md)
+ // but JSON stores the folder path (e.g. skills/foo)
+ const parentPath = filePath.substring(0, filePath.lastIndexOf("/"));
+ if (parentPath) {
+ const parentItem = data.items.find((i) => i.path === parentPath);
+ if (parentItem) return parentItem.title;
+ }
+
+ return fallback;
+}
// Plugin data cache
interface PluginItem {
@@ -329,9 +384,24 @@ export async function openFileModal(
}
// Show modal with loading state
- title.textContent = filePath.split("/").pop() || filePath;
+ const fallbackName = filePath.split("/").pop() || filePath;
+ title.textContent = fallbackName;
modal.classList.remove("hidden");
+ // Update document title to reflect the open file
+ if (!originalDocumentTitle) {
+ originalDocumentTitle = document.title;
+ }
+ document.title = `${fallbackName} | Awesome GitHub Copilot`;
+
+ // Resolve the proper title from JSON data asynchronously
+ resolveResourceTitle(filePath, type).then((resolvedTitle) => {
+ if (currentFilePath === filePath) {
+ title.textContent = resolvedTitle;
+ document.title = `${resolvedTitle} | Awesome GitHub Copilot`;
+ }
+ });
+
// Set focus to close button for accessibility
setTimeout(() => {
closeBtn?.focus();
@@ -447,6 +517,7 @@ async function openPluginModal(
// Update title
title.textContent = plugin.name;
+ document.title = `${plugin.name} | Awesome GitHub Copilot`;
// Render plugin view
modalContent.innerHTML = `
@@ -531,6 +602,12 @@ export function closeModal(updateUrl = true): void {
updateHash(null);
}
+ // Restore original document title
+ if (originalDocumentTitle) {
+ document.title = originalDocumentTitle;
+ originalDocumentTitle = null;
+ }
+
// Return focus to trigger element
if (
triggerElement &&