From 58d9ccd36afd8f378a5f79092e81934f8d941e7c Mon Sep 17 00:00:00 2001 From: Myasnikov Daniil Date: Wed, 21 Oct 2026 18:26:32 +0500 Subject: [PATCH] feat(docs): group older doc versions under "Older versions" The version switcher, the /docs/ version table and the /docs/ sidebar tree listed all eight versions. Keep the newest params.versions_visible (2) released versions in view and put the rest behind an "Older versions" disclosure. Recent vs older is derived from the `order` field that hack/register_version.sh already assigns, so a new release shifts the oldest visible version into the group without touching hugo.yaml. `hidden` is left alone: Makefile routing treats a hidden entry as unreleased. The sidebar tree never prunes the version the page belongs to, decided by content path rather than URL, so builds under a subpath baseURL or with defaultContentLanguageInSubdir keep their navigation. versions_visible is clamped to at least 1 and documented in CLAUDE.md and CONTRIBUTING.md, which also stop naming v1.2 as the current release. Assisted-By: LLM Signed-off-by: Myasnikov Daniil --- CLAUDE.md | 3 +- CONTRIBUTING.md | 11 ++-- assets/scss/blocks/_nav.scss | 30 +++++++++++ hugo.yaml | 3 ++ i18n/de.toml | 3 ++ i18n/en.toml | 3 ++ i18n/es.toml | 3 ++ i18n/hi.toml | 3 ++ i18n/pt-br.toml | 3 ++ i18n/ru.toml | 3 ++ i18n/zh-cn.toml | 3 ++ layouts/docs/docs-landing.html | 57 ++++++++++++--------- layouts/partials/docs-versions.html | 19 +++++++ layouts/partials/sidebar-tree.html | 17 ++++-- layouts/partials/version-switcher-item.html | 29 +++++++++++ layouts/partials/version-switcher.html | 45 ++++++---------- 16 files changed, 175 insertions(+), 60 deletions(-) create mode 100644 layouts/partials/docs-versions.html create mode 100644 layouts/partials/version-switcher-item.html diff --git a/CLAUDE.md b/CLAUDE.md index 389c05465..1dd12fa2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,11 +72,12 @@ Docs are versioned per Cozystack minor release. Each version is an independent c **Version registration (`hugo.yaml`)** - `params.latest_version_id` — id of the current/default version - `params.versions[]` — ordered list consumed by the version switcher (`layouts/partials/version-switcher.html`). Newest goes first, `order` highest. **When adding a new version, update both fields.** +- `params.versions_visible` — how many of the newest non-hidden versions (ranked by `order`, minimum 1) stay in the switcher, the `/docs/` table and the `/docs/` sidebar tree; the rest are grouped under "Older versions". `hack/register_version.sh` needs no change: a new release pushes the oldest visible version into the group on its own. The split lives in `layouts/partials/docs-versions.html`. - `hidden: true` on a version entry hides it from the dropdown selector. Combined with the content mount exclusion pattern (`- '! docs//**'`), the whole version is also excluded from production builds. Used for the `next` trunk; the dev server and Netlify deploy previews still render it at `/docs/next/`. **Where to add changes** - Docs for an upcoming (unreleased) feature → edit `content/en/docs/next/`. These materialize as `v/` when `make release-next RELEASE_TAG=vX.Y.Z` runs -- Bug fix or addition to the current release → edit only `content/en/docs//` (currently `v1.2`) +- Bug fix or addition to the current release → edit only `content/en/docs//` (the `params.latest_version_id` value in `hugo.yaml`) - Content that also applies to older supported versions → copy the change into each relevant `v*/` directory (no automatic backport) - Cross-version/landing content → `content/en/docs/_index.md` - Blog posts are version-independent → `content/en/blog/` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdb10371a..d6240485b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ Use this decision table before you start editing: | Your change is… | Edit here | |---------------------------------------------------------------------|-------------------------------------------------| | A doc for a **feature that ships in a future release** | `content/en/docs/next/` | -| A **bug fix** or addition **for the current release** | `content/en/docs//` (`v1.2`) | +| A **bug fix** or addition **for the current release** | `content/en/docs//` | | A fix that also applies to an **older supported version** | Copy the change into each relevant `vX.Y/` | | Cross-version content (the landing page, overview) | `content/en/docs/_index.md` | | A **blog post** | `content/en/blog/` | @@ -237,6 +237,7 @@ If you need to understand or change the versioning machinery itself: - `hugo.yaml` — site-wide config; `params.versions[]` is the list of registered versions (including `next` with `hidden: true`), and `params.latest_version_id` is the current stable. +- `params.versions_visible` (in `hugo.yaml`) — how many of the newest released versions, ranked by `order` (minimum 1), stay in the version switcher, the `/docs/` table and the `/docs/` sidebar tree; the rest are grouped under "Older versions". - `config/production/hugo.yaml` — production-only overrides; excludes `docs/next/**` from GitHub Pages builds. Netlify deploy previews set `HUGO_ENV=preview` so they skip this override. @@ -250,10 +251,10 @@ If you need to understand or change the versioning machinery itself: cozystack/cozystack. - `hack/download_openapi.sh` — fetches `openapi.json` per version at build time (v1.1+; v0 and v1.0 ship their `api.json` in-tree). -- `layouts/docs/docs-landing.html` — the `/docs/` landing template; builds the - version table from `params.versions[]`, filtering out hidden entries. -- `layouts/partials/version-switcher.html` — top-navbar version dropdown; - filters out hidden entries. +- `layouts/partials/docs-versions.html` — splits `params.versions[]` into the newest `params.versions_visible` released versions and the older rest, skipping hidden entries; shared by the three consumers below. +- `layouts/docs/docs-landing.html` — the `/docs/` landing template; builds the version table from that split, with the older versions in a collapsed block. +- `layouts/partials/version-switcher.html` — sidebar version dropdown; lists the recent versions and puts the older ones under an "Older versions" disclosure that is open when the current page belongs to one. +- `layouts/partials/sidebar-tree.html` — on `/docs/` it leaves out the roots of older versions. - `layouts/404.html` — client-side fallback redirect for `/docs/v1/…` short paths. diff --git a/assets/scss/blocks/_nav.scss b/assets/scss/blocks/_nav.scss index db5c3736a..a57ca8f6f 100644 --- a/assets/scss/blocks/_nav.scss +++ b/assets/scss/blocks/_nav.scss @@ -290,6 +290,36 @@ nav.navbar { } } } + + // "Older versions" group: native
, no JS. + &__older { + summary { + cursor: pointer; + list-style: none; + + &::-webkit-details-marker { + display: none; + } + + // Same caret as .dropdown-toggle, flipped when the group is open. + &::after { + content: ""; + float: right; + margin-top: 0.5em; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-left: 0.3em solid transparent; + } + } + + &[open] > summary::after { + transform: rotate(180deg); + } + + ul .dropdown-item { + padding-left: 1.75rem; + } + } } // Fixed-header offset. diff --git a/hugo.yaml b/hugo.yaml index f773a9049..683588a3d 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -247,6 +247,9 @@ params: version_menu: "Versions" version_menu_pagelinks: true latest_version_id: "v1.6" + # How many of the newest released versions stay in the version switcher and the + # /docs/ table; the rest go under "Older versions". Ranked by `order`; minimum 1. + versions_visible: 2 versions: - version: "v1.6" url: "/docs/v1.6/" diff --git a/i18n/de.toml b/i18n/de.toml index d7da2e0a9..1063f16e4 100644 --- a/i18n/de.toml +++ b/i18n/de.toml @@ -92,3 +92,6 @@ other = 'Einen Fehler entdeckt? Eröffnen Sie ein Issu [translation_see_original] other = 'Siehe das englische Original.' + +[docs_older_versions] +other = "Ältere Versionen" diff --git a/i18n/en.toml b/i18n/en.toml index 05d368007..5cc5d59d1 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -92,3 +92,6 @@ other = 'Spotted a mistake? Open an issue.' [translation_see_original] other = 'See the English original.' + +[docs_older_versions] +other = "Older versions" diff --git a/i18n/es.toml b/i18n/es.toml index 7ff68ad2e..9518c9b55 100644 --- a/i18n/es.toml +++ b/i18n/es.toml @@ -93,3 +93,6 @@ other = "Caso de uso anterior" [home_use_cases_next] other = "Caso de uso siguiente" + +[docs_older_versions] +other = "Versiones anteriores" diff --git a/i18n/hi.toml b/i18n/hi.toml index 865297119..03bffa705 100644 --- a/i18n/hi.toml +++ b/i18n/hi.toml @@ -92,3 +92,6 @@ other = 'कोई त्रुटि मिली? Is [translation_see_original] other = 'अंग्रेज़ी मूल देखें।' + +[docs_older_versions] +other = "पुराने संस्करण" diff --git a/i18n/pt-br.toml b/i18n/pt-br.toml index 5150b8fee..dc02a15d6 100644 --- a/i18n/pt-br.toml +++ b/i18n/pt-br.toml @@ -93,3 +93,6 @@ other = "Caso de uso anterior" [home_use_cases_next] other = "Próximo caso de uso" + +[docs_older_versions] +other = "Versões anteriores" diff --git a/i18n/ru.toml b/i18n/ru.toml index 0eaa8b6fb..1027017d9 100644 --- a/i18n/ru.toml +++ b/i18n/ru.toml @@ -92,3 +92,6 @@ other = 'Нашли ошибку? Создайте iss [translation_see_original] other = 'Смотрите английский оригинал.' + +[docs_older_versions] +other = "Старые версии" diff --git a/i18n/zh-cn.toml b/i18n/zh-cn.toml index 4252f0c8c..29df2332e 100644 --- a/i18n/zh-cn.toml +++ b/i18n/zh-cn.toml @@ -92,3 +92,6 @@ other = '发现错误?提交 issue。' [translation_see_original] other = '请参阅英文原文。' + +[docs_older_versions] +other = "更早的版本" diff --git a/layouts/docs/docs-landing.html b/layouts/docs/docs-landing.html index b035f7641..aeffff7d3 100644 --- a/layouts/docs/docs-landing.html +++ b/layouts/docs/docs-landing.html @@ -20,29 +20,40 @@

{{ .Title }}

{{ .Content }} + {{- $groups := partial "docs-versions.html" . -}}

{{ T "docs_landing_available_versions" }}

- - - - - - - - - {{- range .Site.Params.versions -}} - {{- if not .hidden -}} - {{- $isLatest := eq .id $latestID -}} - {{- $status := T "docs_landing_status_previous" -}} - {{- if $isLatest -}} - {{- $status = T "docs_landing_status_latest" -}} - {{- end -}} - - - - - {{- end -}} - {{- end -}} - -
{{ T "docs_landing_col_version" }}{{ T "docs_landing_col_status" }}
{{ .version }}{{ $status }}
+ {{ template "versions-table" (dict "versions" $groups.recent "latestID" $latestID) }} + {{- with $groups.older }} +
+ {{ T "docs_older_versions" }} ({{ len . }}) + {{ template "versions-table" (dict "versions" . "latestID" $latestID) }} +
+ + {{- end }} {{ end }} + +{{ define "versions-table" }} + + + + + + + + + {{- $latestID := .latestID -}} + {{- range .versions }} + + + + + {{- end }} + +
{{ T "docs_landing_col_version" }}{{ T "docs_landing_col_status" }}
{{ .version }}{{ if eq .id $latestID }}{{ T "docs_landing_status_latest" }}{{ else }}{{ T "docs_landing_status_previous" }}{{ end }}
+{{ end }} diff --git a/layouts/partials/docs-versions.html b/layouts/partials/docs-versions.html new file mode 100644 index 000000000..71f607f6c --- /dev/null +++ b/layouts/partials/docs-versions.html @@ -0,0 +1,19 @@ +{{- /* Splits params.versions into the newest `versions_visible` released entries + and the rest. `hidden` entries (the `next` trunk) are in neither group. + Ranking uses `order`, which hack/register_version.sh assigns on release, + so a new release pushes the oldest visible version into "older" on its own. + Returns dict "recent" "older" (both slices of version entries, newest first). */ -}} +{{- $released := slice -}} +{{- range site.Params.versions -}} + {{- if not .hidden -}} + {{- $released = $released | append . -}} + {{- end -}} +{{- end -}} +{{- $sorted := sort $released "order" "desc" -}} +{{- /* `default 2` would turn an explicit 0 into 2; isset keeps it and max clamps it to 1. */ -}} +{{- $n := 2 -}} +{{- if isset site.Params "versions_visible" -}} + {{- $n = int site.Params.versions_visible -}} +{{- end -}} +{{- $n = int (math.Max 1 $n) -}} +{{- return dict "recent" (first $n $sorted) "older" (after $n $sorted) -}} diff --git a/layouts/partials/sidebar-tree.html b/layouts/partials/sidebar-tree.html index 97477b01d..483ee9f89 100644 --- a/layouts/partials/sidebar-tree.html +++ b/layouts/partials/sidebar-tree.html @@ -35,11 +35,21 @@ {{- end -}} {{- end -}} {{- end -}} + {{- $hideRoots := slice -}} + {{- /* Never prune the version the page belongs to. .Path is the content path, so + this holds even when the prefix match above misses (subpath baseURL, + defaultContentLanguageInSubdir) and the tree is rooted at /docs/. */ -}} + {{- range (partial "docs-versions.html" .).older -}} + {{- $root := strings.TrimRight "/" .url -}} + {{- if not (or (eq $.Page.Path $root) (hasPrefix $.Page.Path (printf "%s/" $root))) -}} + {{- $hideRoots = $hideRoots | append $root -}} + {{- end -}} + {{- end -}} {{ $ulNr := 0 -}} {{ $ulShow := cond (isset .Site.Params.ui "ul_show") .Site.Params.ui.ul_show 1 -}} {{ $sidebarMenuTruncate := cond (isset .Site.Params.ui "sidebar_menu_truncate") .Site.Params.ui.sidebar_menu_truncate 50 -}}
    - {{ template "section-tree-nav-section" (dict "page" . "section" $navRoot "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" (add $ulShow 1)) }} + {{ template "section-tree-nav-section" (dict "page" . "section" $navRoot "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" (add $ulShow 1) "hideRoots" $hideRoots) }}
@@ -51,6 +61,7 @@ {{ $treeRoot := cond (eq .ulNr 0) true false -}} {{ $ulNr := .ulNr -}} {{ $ulShow := .ulShow -}} +{{ $hideRoots := .hideRoots -}} {{ $active := and (not $shouldDelayActive) (eq $s $p) -}} {{ $activePath := and (not $shouldDelayActive) (or (eq $p $s) ($p.IsDescendant $s)) -}} {{ $show := cond (or (lt $ulNr $ulShow) $activePath (and (not $shouldDelayActive) (eq $s.Parent $p.Parent)) (and (not $shouldDelayActive) (eq $s.Parent $p)) (not $p.Site.Params.ui.sidebar_menu_compact) (and (not $shouldDelayActive) ($p.IsDescendant $s.Parent))) true false -}} @@ -71,8 +82,8 @@ {{- $ulNr := add $ulNr 1 }}
    {{ range $pages -}} - {{ if (not (and (eq $s $p.Site.Home) (eq .Params.toc_root true))) -}} - {{ template "section-tree-nav-section" (dict "page" $p "section" . "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" $ulShow) }} + {{ if and (not (and (eq $s $p.Site.Home) (eq .Params.toc_root true))) (not (in $hideRoots .Path)) -}} + {{ template "section-tree-nav-section" (dict "page" $p "section" . "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" $ulShow "hideRoots" $hideRoots) }} {{- end }} {{- end }}
diff --git a/layouts/partials/version-switcher-item.html b/layouts/partials/version-switcher-item.html new file mode 100644 index 000000000..80da82115 --- /dev/null +++ b/layouts/partials/version-switcher-item.html @@ -0,0 +1,29 @@ +{{- /* One dropdown entry. Context: dict "v" "logicalPath" + (current page path without the language base) "currentID" "currentUrl". + Each target is the most specific page that is actually built, so no link + 404s. The pipeline translates only the latest version, so most localized + pages and version roots never exist. Staying on the same topic matters more + than staying in the language, so the order is: English version root (always + built), localized version root, the same page in English, the same page + localized. site.GetPage resolves in the current-language site; + hugo.Sites.Default is the English site. */ -}} +{{- $v := .v -}} +{{- $targetPath := $v.url -}} +{{- with site.GetPage (strings.TrimRight "/" $v.url) -}} + {{- $targetPath = .RelPermalink -}} +{{- end -}} +{{- if .currentUrl -}} + {{- $subPath := strings.TrimPrefix .currentUrl .logicalPath -}} + {{- $trimmed := strings.TrimRight "/" (printf "%s%s" $v.url $subPath) -}} + {{- with hugo.Sites.Default.GetPage $trimmed -}} + {{- $targetPath = .RelPermalink -}} + {{- end -}} + {{- with site.GetPage $trimmed -}} + {{- $targetPath = .RelPermalink -}} + {{- end -}} +{{- end -}} +
  • + + {{- $v.version -}} + +
  • diff --git a/layouts/partials/version-switcher.html b/layouts/partials/version-switcher.html index 02e16d8e3..be87c6104 100644 --- a/layouts/partials/version-switcher.html +++ b/layouts/partials/version-switcher.html @@ -25,7 +25,7 @@ {{- else if .Site.Params.versions -}}