Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/changelog.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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):*
Expand Down
66 changes: 62 additions & 4 deletions scripts/prerender-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(/<title>[\s\S]*?<\/title>/, `<title>${escapeHtml(title)}</title>`)

html = html.replace(
Expand Down Expand Up @@ -246,8 +257,45 @@ function applyHead(html, { title, description, canonicalUrl, enUrl, deUrl, lang
.join('\n')
html = html.replace(/(<link\s+rel="canonical"[^>]*>)/, `$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(
/<meta\s+property="og:url"\s+content="[^"]*"\s*\/?>/,
`<meta property="og:url" content="${canonicalUrl}" />`
)
html = html.replace(
/<meta\s+property="og:title"\s+content="[^"]*"\s*\/?>/,
`<meta property="og:title" content="${escapeHtml(title)}" />`
)
html = html.replace(
/<meta\s+property="og:description"\s+content="[^"]*"\s*\/?>/,
`<meta property="og:description" content="${escapeHtml(description)}" />`
)
html = html.replace(
/<meta\s+name="twitter:url"\s+content="[^"]*"\s*\/?>/,
`<meta name="twitter:url" content="${canonicalUrl}" />`
)
html = html.replace(
/<meta\s+name="twitter:title"\s+content="[^"]*"\s*\/?>/,
`<meta name="twitter:title" content="${escapeHtml(title)}" />`
)
html = html.replace(
/<meta\s+name="twitter:description"\s+content="[^"]*"\s*\/?>/,
`<meta name="twitter:description" content="${escapeHtml(description)}" />`
)

if (lang === 'de') {
html = html.replace('<html lang="en">', '<html lang="de">')
html = html
.replace(
/<meta\s+property="og:locale"\s+content="[^"]*"\s*\/?>/,
'<meta property="og:locale" content="de_DE" />'
)
.replace(
/<meta\s+property="og:locale:alternate"\s+content="[^"]*"\s*\/?>/,
'<meta property="og:locale:alternate" content="en_US" />'
)
}

return html
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -530,15 +583,20 @@ 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'
? 'Semantic Anchors - Gemeinsames Vokabular für die Kommunikation mit LLMs'
: '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/`,
Expand Down
8 changes: 4 additions & 4 deletions website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<!-- Primary Meta Tags -->
<title>Semantic Anchors - Shared Vocabulary for LLM Communication</title>
<meta name="title" content="Semantic Anchors - Shared Vocabulary for LLM Communication" />
<meta name="description" content="110+ semantic anchors and semantic contracts for precise LLM communication. Curated methodologies, frameworks, and composable project conventions. Evaluated across 10 models." />
<meta name="description" content="A curated catalog of semantic anchors and semantic contracts — shared vocabulary for precise communication with Large Language Models." />
<meta name="keywords" content="semantic anchors, semantic contracts, LLM, large language models, AI communication, prompt engineering, software development, TDD, architecture patterns, design principles, AGENTS.md" />
<meta name="author" content="LLM Coding Community" />
<meta name="language" content="English, German" />
Expand All @@ -28,7 +28,7 @@
<meta property="og:type" content="website" />
<meta property="og:url" content="https://llm-coding.github.io/Semantic-Anchors/" />
<meta property="og:title" content="Semantic Anchors - Shared Vocabulary for LLM Communication" />
<meta property="og:description" content="110+ semantic anchors and semantic contracts for precise LLM communication. Evaluated across 10 models." />
<meta property="og:description" content="A curated catalog of semantic anchors and semantic contracts — shared vocabulary for precise communication with Large Language Models." />
<meta property="og:image" content="https://llm-coding.github.io/Semantic-Anchors/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
Expand All @@ -40,7 +40,7 @@
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://llm-coding.github.io/Semantic-Anchors/" />
<meta name="twitter:title" content="Semantic Anchors - Shared Vocabulary for LLM Communication" />
<meta name="twitter:description" content="110+ semantic anchors and semantic contracts for precise LLM communication. Evaluated across 10 models." />
<meta name="twitter:description" content="A curated catalog of semantic anchors and semantic contracts — shared vocabulary for precise communication with Large Language Models." />
<meta name="twitter:image" content="https://llm-coding.github.io/Semantic-Anchors/twitter-card.png" />

<!-- Structured Data (Schema.org) -->
Expand All @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion website/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down
Loading