diff --git a/content/ai-agent-resources.md b/content/ai-agent-resources.md index 74765c041f..3e30b58138 100644 --- a/content/ai-agent-resources.md +++ b/content/ai-agent-resources.md @@ -43,7 +43,7 @@ Each document contains: | Field | Type | Description | |-------|------|-------------| -| `id` | string | URL slug identifier | +| `id` | string | Unique identifier, the page's path without a file extension (for example `develop/clients/redis-py`) | | `title` | string | Page title | | `url` | string | Canonical URL | | `summary` | string | Short description | diff --git a/layouts/_default/section.json b/layouts/_default/section.json index 6ee91cc66d..f88dafde72 100644 --- a/layouts/_default/section.json +++ b/layouts/_default/section.json @@ -5,19 +5,7 @@ {{- $content := partial "process-markdown-content.html" (dict "RawContent" .RawContent "Site" .Site "Page" .) -}} {{- /* Build the JSON object for the section itself */ -}} -{{- /* Handle pages where .File may be nil (e.g., taxonomy pages) */ -}} -{{- /* Also handle _index.md files where ContentBaseName is "_index" (not useful as id) */ -}} -{{- $id := "" -}} -{{- with .File -}} - {{- $baseName := .ContentBaseName -}} - {{- if or (not $baseName) (eq $baseName "_index") -}} - {{- $id = $.Title | urlize -}} - {{- else -}} - {{- $id = $baseName | urlize -}} - {{- end -}} -{{- else -}} - {{- $id = .Title | urlize -}} -{{- end -}} +{{- $id := partial "page-id.html" . -}} {{- $summary := (.Params.description | default .Description) | plainify | replaceRE "\\s+" " " | strings.TrimSpace -}} {{- $tags := .Params.categories | default (slice) -}} {{- $lastUpdated := .Lastmod.Format "2006-01-02T15:04:05Z07:00" -}} @@ -25,19 +13,10 @@ {{- /* Build list of child pages */ -}} {{- $children := slice -}} {{- range .Pages -}} - {{- $childTitle := .Title -}} - {{- $childId := "" -}} - {{- with .File -}} - {{- $baseName := .ContentBaseName -}} - {{- if or (not $baseName) (eq $baseName "_index") -}} - {{- $childId = $childTitle | urlize -}} - {{- else -}} - {{- $childId = $baseName | urlize -}} - {{- end -}} - {{- else -}} - {{- $childId = $childTitle | urlize -}} - {{- end -}} - {{- $childSummary := (.Params.description | default .Description) | plainify | replaceRE "\\s+" " " | strings.TrimSpace -}} + {{- /* Same derivation as the page's own id, so children[].id resolves to the record + that page publishes */ -}} + {{- $childId := partial "page-id.html" . -}} + {{- $childSummary :=(.Params.description | default .Description) | plainify | replaceRE "\\s+" " " | strings.TrimSpace -}} {{- $child := dict "id" $childId "title" .Title "url" .Permalink "summary" $childSummary -}} {{- $children = $children | append $child -}} {{- end -}} diff --git a/layouts/_default/single.json b/layouts/_default/single.json index 9d815fa63f..a9325cbf43 100644 --- a/layouts/_default/single.json +++ b/layouts/_default/single.json @@ -5,19 +5,7 @@ {{- $content := partial "process-markdown-content.html" (dict "RawContent" .RawContent "Site" .Site "Page" .) -}} {{- /* Build the JSON object */ -}} -{{- /* Handle pages where .File may be nil (e.g., taxonomy pages) */ -}} -{{- /* Also handle _index.md files where ContentBaseName is "_index" (not useful as id) */ -}} -{{- $id := "" -}} -{{- with .File -}} - {{- $baseName := .ContentBaseName -}} - {{- if or (not $baseName) (eq $baseName "_index") -}} - {{- $id = $.Title | urlize -}} - {{- else -}} - {{- $id = $baseName | urlize -}} - {{- end -}} -{{- else -}} - {{- $id = .Title | urlize -}} -{{- end -}} +{{- $id := partial "page-id.html" . -}} {{- $summary := (.Params.description | default .Description) | plainify | replaceRE "\\s+" " " | strings.TrimSpace -}} {{- $tags := .Params.categories | default (slice) -}} {{- $lastUpdated := .Lastmod.Format "2006-01-02T15:04:05Z07:00" -}} diff --git a/layouts/partials/page-id.html b/layouts/partials/page-id.html new file mode 100644 index 0000000000..77a91dfd06 --- /dev/null +++ b/layouts/partials/page-id.html @@ -0,0 +1,41 @@ +{{- /* + Stable, unique id for a page record in the JSON and Markdown outputs. + + Takes a Page. Returns its content path with the ".md" extension and any trailing + "/_index" removed, so content/develop/clients/redis-py.md becomes + "develop/clients/redis-py" and content/develop/clients/_index.md becomes + "develop/clients". + + This replaces a bare filename. ContentBaseName is not unique across the corpus -- + "message_history" named 58 different pages, "install" 42 -- so any consumer treating + id as a primary key silently collapsed them, with no error to notice. The content + path is unique by construction, because two files cannot share one path, and it is + stable across builds because nothing about it depends on build order. A dedup + suffix would not be: the page that got the plain id and the page that got "-1" could + swap between builds, quietly repointing anyone who had stored either. + + Slashes are kept rather than flattened to hyphens. Flattening reintroduces a + collision risk ("a/b-c" and "a-b/c" both becoming "a-b-c") for no real gain, and the + path form matches how the URL is already structured. + + Pages with no backing file -- generated pages, taxonomy terms -- fall back to the + urlized title, which is what the previous implementation did for them. + + Shared by single.json, section.json and section.json's children[] entries so all + three agree. children[].id must equal the child page's own id or the navigation + graph does not resolve. +*/ -}} + +{{- $id := "" -}} +{{- with .File -}} + {{- /* Not ".Path | replace ..." -- piping makes .Path the LAST argument, so the + backslash becomes the input and every id comes out as a single backslash. */ -}} + {{- $path := replace .Path "\\" "/" -}} + {{- $path = strings.TrimSuffix ".md" $path -}} + {{- $path = strings.TrimSuffix "/_index" $path -}} + {{- $id = $path -}} +{{- end -}} +{{- if not $id -}} + {{- $id = .Title | urlize -}} +{{- end -}} +{{- return $id -}}