diff --git a/docs/changelog.adoc b/docs/changelog.adoc index 13fb6b2..b8d952a 100644 --- a/docs/changelog.adoc +++ b/docs/changelog.adoc @@ -2,6 +2,14 @@ A chronological record of all semantic anchors added to the catalog. Community contributors are credited with thanks. +== 2026-06-11 + +*Share metadata & descriptions (#601):* + +* *Per-page Open Graph / Twitter cards* — every pre-rendered page now carries its own `og:url`, `og:title`, `og:description` and `twitter:*` tags (previously all subpages shipped the home-page values, so sharing an anchor link rendered the generic home preview). German pages get `og:locale=de_DE`. +* *Accurate home description* — the hard-coded "110+ semantic anchors" claim (real count: 161) is replaced by counts computed from the data files at build time; the static shell texts are now count-free so they cannot go stale. +* *Normalized snippet lengths* — meta descriptions are capped at ~160 characters on a word boundary; very short anchor extracts get the anchor title prefixed. + == 2026-06-10 *Real anchor pages (#597):* diff --git a/scripts/prerender-routes.js b/scripts/prerender-routes.js index a0f0828..1876053 100644 --- a/scripts/prerender-routes.js +++ b/scripts/prerender-routes.js @@ -218,7 +218,18 @@ const SITE = 'https://llm-coding.github.io/Semantic-Anchors' * enUrl: string, deUrl: (string|null), lang: string}} meta * @returns {string} Updated HTML. */ +/** + * Cap a meta description at ~160 characters on a word boundary — Google + * truncates around 160, so longer texts would be cut mid-sentence in SERPs. + */ +function capDescription(text) { + if (text.length <= 160) return text + const cut = text.slice(0, 159) + return cut.slice(0, Math.max(cut.lastIndexOf(' '), 100)) + '…' +} + function applyHead(html, { title, description, canonicalUrl, enUrl, deUrl, lang }) { + description = capDescription(description) html = html.replace(/[\s\S]*?<\/title>/, `<title>${escapeHtml(title)}`) html = html.replace( @@ -246,8 +257,45 @@ function applyHead(html, { title, description, canonicalUrl, enUrl, deUrl, lang .join('\n') html = html.replace(/(]*>)/, `$1\n${alternates}`) + // Per-page social metadata (#601): without this every pre-rendered page + // keeps the shell's home-pointing og:/twitter: values, so shared subpage + // links render the generic home preview. + html = html.replace( + //, + `` + ) + html = html.replace( + //, + `` + ) + html = html.replace( + //, + `` + ) + html = html.replace( + //, + `` + ) + html = html.replace( + //, + `` + ) + html = html.replace( + //, + `` + ) + if (lang === 'de') { html = html.replace('', '') + html = html + .replace( + //, + '' + ) + .replace( + //, + '' + ) } return html @@ -462,8 +510,12 @@ function prerenderAnchorPages(shell) { const hasDe = fs.existsSync(path.join(DIST, fragmentDe)) const enUrl = `${SITE}/anchor/${anchor.id}` const deUrl = hasDe ? `${SITE}/de/anchor/${anchor.id}` : null + // Short Core-Concepts extracts ("Dependencies only point inward") get the + // anchor title prefixed so the SERP snippet stands on its own (#601). + const withTitle = (extracted) => + extracted && extracted.length < 50 ? `${anchor.title} — ${extracted}` : extracted const description = - extractDescription(`docs/anchors/${anchor.id}.adoc`) || + withTitle(extractDescription(`docs/anchors/${anchor.id}.adoc`)) || `${anchor.title} — a semantic anchor: an established term that activates a rich, well-defined concept in any modern LLM.` // The anchor body sits in a [%collapsible] block; expand it on the @@ -494,7 +546,8 @@ function prerenderAnchorPages(shell) { fragmentDe, { title: `${anchor.title} — Semantic Anchors`, - description: extractDescription(`docs/anchors/${anchor.id}.de.adoc`) || description, + description: + withTitle(extractDescription(`docs/anchors/${anchor.id}.de.adoc`)) || description, canonicalUrl: deUrl, enUrl, deUrl, @@ -530,6 +583,11 @@ function writeHomeVariant(shell, lang) { ${buildCatalogMarkup(lang, tr)} ` + // Real counts from the data files, so the description can never go stale + // again the way the hard-coded "110+" did (#601). + const anchorCount = loadWebsiteJson('public/data/anchors.json').length + const contractCount = loadWebsiteJson('public/data/contracts.json').length + let html = applyHead(shell, { title: lang === 'de' @@ -537,8 +595,8 @@ function writeHomeVariant(shell, lang) { : 'Semantic Anchors - Shared Vocabulary for LLM Communication', description: lang === 'de' - ? '110+ semantische Anker und semantische Contracts für präzise LLM-Kommunikation. Kuratierte Methoden, Frameworks und komponierbare Projektkonventionen. Über 10 Modelle evaluiert.' - : '110+ semantic anchors and semantic contracts for precise LLM communication. Curated methodologies, frameworks, and composable project conventions. Evaluated across 10 models.', + ? `${anchorCount} semantische Anker und ${contractCount} semantische Contracts für präzise LLM-Kommunikation — kuratierte Methoden, Frameworks und Projektkonventionen.` + : `${anchorCount} semantic anchors and ${contractCount} semantic contracts for precise LLM communication — curated methodologies, frameworks, and composable project conventions.`, canonicalUrl: lang === 'de' ? `${SITE}/de/` : `${SITE}/`, enUrl: `${SITE}/`, deUrl: `${SITE}/de/`, diff --git a/website/index.html b/website/index.html index d6b5301..113a636 100644 --- a/website/index.html +++ b/website/index.html @@ -8,7 +8,7 @@ Semantic Anchors - Shared Vocabulary for LLM Communication - + @@ -28,7 +28,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -51,7 +51,7 @@ "name": "Semantic Anchors", "alternateName": "Semantic Anchors for LLMs", "url": "https://llm-coding.github.io/Semantic-Anchors/", - "description": "110+ semantic anchors and semantic contracts for precise communication with Large Language Models. Evaluated across 10 models.", + "description": "A curated catalog of semantic anchors and semantic contracts — shared vocabulary for precise communication with Large Language Models.", "inLanguage": ["en", "de"], "publisher": { "@id": "https://llm-coding.github.io/Semantic-Anchors/#organization" }, "potentialAction": { diff --git a/website/src/main.js b/website/src/main.js index 4550257..dc23444 100644 --- a/website/src/main.js +++ b/website/src/main.js @@ -27,7 +27,7 @@ import { } from './components/onboarding-modal.js' import { renderContractsPage, initContractsPage } from './components/contracts-page.js' -const APP_VERSION = '0.6.0' +const APP_VERSION = '0.6.1' window.copyAnchorLink = async function copyAnchorLink(anchorId) { const url = `${window.location.origin}${import.meta.env.BASE_URL}anchor/${anchorId}`