Fix guides index: surface every YAML entry, drop legacy categories, curate tag cloud#504
Merged
jamesfredley merged 1 commit intomasterfrom May 4, 2026
Merged
Conversation
…urate tag cloud Five recently-added Grails 8 guides (multi-module, rest-library, github-actions-cicd) plus three older entries (grails-as-docker-container, grails-mock-basics v4, creating-your-first-grails-app v6) were silently absent from https://grails.apache.org/guides/ even though their per-guide HTML was built and uploaded correctly. Three independent bugs combined to make this happen, and the bottom-of-page category grid was simultaneously overweight on dead/legacy stacks while missing the modern Web Layer section that every flagship Grails 8 guide lives in. == conf/guides.yml: 5 sampleRef.repo collisions == The fetcher used to group guide-versions by sampleRef.repo, so any two YAML entries that happened to point at the same external repo merged into a single rendered guide and clobbered each other. Five YAML entries were pointing at the wrong repo (the new Grails 8 sample repo instead of their own legacy repo): - grails-as-docker-container v3, v4 -> grails-as-docker-container (was grails-docker-bootbuildimage; collided with grails-docker-bootbuildimage v8) - grails-multi-project-build v4 -> grails-multi-project-build (was grails-multi-module; collided with grails-multi-module v8) - rest-hibernate v3, v4 -> rest-hibernate (was grails-rest-library; collided with grails-rest-library v8) - grails-on-github-actions v4 -> grails-on-github-actions (was grails-github-actions-cicd; collided with grails-github-actions-cicd v8) These look like the residue of repo renames that were applied in the sample registry but never propagated to the new entries that needed distinct slugs. == GuidesFetcher: identify guides by YAML name, not sampleRef.repo == Even with the YAML cleaned up, the fetcher was structurally fragile. It also had two latent bugs: 1. BRANCH_TO_MAJOR map hardcoded master->4 and was missing grails7, grails8. creating-your-first-grails-app/v6 (whose sampleRef.branch is master) was silently mapped to major version 4, overwriting the real v4 entry in grailsMayorVersionTags. The v6 build was reachable only via direct URL. 2. grails-mock-basics had v3 and v4 both with branch=master under one guide. The slug+branch grouping reduced to {master}, so size==1 triggered toSingleGuide and silently returned the first match (v3). Refactor parseGuides to walk one Guide per top-level YAML entry: - 1 versions: child -> SingleGuide - 2+ versions: child -> GrailsVersionedGuide, with one rendered link per version. The YAML version key is used DIRECTLY as the integer major version, which eliminates BRANCH_TO_MAJOR entirely. sampleRef.repo is now treated only as the "Get the Code" target on the rendered chrome - it is never used to identify or group guides. Two YAML entries that happen to point at the same external repo are still rendered as two separate guides. Adds GuidesFetcherSpec with 7 unit tests pinning the regression cases: - SingleGuide for 1-version entries - GrailsVersionedGuide for 2+-version entries with the YAML version key preserved as the int major version (covers the master->4 overwrite + missing grails7/grails8) - Two YAML entries sharing sampleRef.repo render as two guides (covers all 4 collision pairs above) - Two YAML versions sharing the same branch are both kept when they belong to the same guide (covers grails-mock-basics) - Future-dated guides are filtered when skipFuture is true - Empty versions: blocks are silently skipped - Sort order is publication-date-descending == GuidesPage: trim dead categories, add Web Layer, curate tag cloud == categories map: dropped Grails + Android, Grails + Angular, Grails + AngularJS, Grails + iOS, Grails + RIA. Each had 1-2 stale guides and was eating equal real estate to Advanced Grails (22 guides). Their guides remain built and reachable via tags, search, and direct URL; only the index-page section is gone. Added Web Layer (5 guides today: htmx, tailwindcss, vite-spa, fields-custom-widgets-and-wrappers, rest-library). Without this section, rest-library was the only one of those guides that didn't even fit in the top-8 Latest Guides sidebar, so it was completely invisible from the index. Reshuffled the two-column grid so the modern stacks (Apprentice, Advanced, Web Layer, DevOps) sit above the older ones (GORM, Testing, Vue, React, Google Cloud). DevOps in particular was previously buried under the legacy iOS / Android / RIA cluster despite having more guides than any of them. tagCloud: filters out version-label tags (grails3..grails8) - they artificially dominated the cloud by occurrence count without adding navigational value (the version is implicit in the guide URL anyway). Caps the visible set to TAG_CLOUD_LIMIT=50 most-used tags before re-sorting alphabetically for display, so the long tail of one-shot legacy tags (apple-tv, tvml, aws-elasticbeanstlak, ...) no longer clutters the sidebar. == Verified == ./gradlew :buildSrc:test --tests 'website.model.guides.GuidesFetcherSpec' -> all 7 pass ./gradlew build validateGuides -> BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors Local build/dist/guides/index.html before-vs-after: - multi-module 0 -> 2 hits - rest-library 0 -> 2 hits - github-actions-cicd 0 -> 2 hits - grails-as-docker-container 0 -> 2 hits - grails-mock-basics 1 (v3 only) -> 2 (v3 + v4) - creating-your-first-grails-app 2 (v3,v4) -> 3 (v3,v4,v6) - rest-hibernate / grails-on-github-actions / grails-multi-project-build: each one stops being mis-rendered as the wrong v8 guide Latest Guides sidebar now contains all 8 new Grails 8 guides (was 5 of 8: docker-bootbuildimage, htmx, spock-test-tour, tailwindcss, vite-spa). Tag cloud is now 50 entries (was 200+). Assisted-by: claude-code:claude-opus-4-7
There was a problem hiding this comment.
Pull request overview
This PR fixes guide-index rendering so every YAML-defined guide entry appears correctly on /guides, while also reshaping the index taxonomy and tag cloud for the current Grails guide catalog. It does this in the guide metadata source, the YAML fetcher/model layer, and the page renderer used by the guides site build.
Changes:
- Corrects several
sampleRef.repovalues inconf/guides.ymlso legacy guides no longer collide with newer guides during index generation. - Refactors
GuidesFetcherto build guides from top-level YAMLnameentries instead of grouping by repository slug, and adds regression tests for the reported edge cases. - Curates the guides index presentation by removing legacy categories, adding a
Web Layercategory, and filtering/capping the tag cloud.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
conf/guides.yml |
Fixes misassigned guide sample repositories for several affected guide entries. |
buildSrc/src/test/groovy/website/model/guides/GuidesFetcherSpec.groovy |
Adds regression coverage for single-version, multi-version, collision, duplicate-branch, future-date, empty-version, and sort-order cases. |
buildSrc/src/main/groovy/website/model/guides/GuidesPage.groovy |
Updates index category layout and adds tag-cloud curation/filtering. |
buildSrc/src/main/groovy/website/model/guides/GuidesFetcher.groovy |
Reworks YAML parsing/grouping to construct guides per top-level entry using YAML version keys directly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
60
to
+70
| static Map<String, Category> categories = [ | ||
| advanced: new Category(name: 'Advanced Grails', image: 'advancedgrails.svg'), | ||
| android: new Category(name: 'Grails + Android', image: 'grails_android.svg'), | ||
| angular: new Category(name: 'Grails + Angular', image: 'grailsangular.svg'), | ||
| angularjs: new Category(name: 'Grails + AngularJS', image: 'grailsangular.svg'), | ||
| apprentice: new Category(name: 'Grails Apprentice', image: 'grailaprrentice.svg'), | ||
| async: new Category(name: 'Grails Async', image: 'async.svg'), | ||
| devops: new Category(name: 'Grails + DevOps', image: 'grailsdevops.svg'), | ||
| googlecloud: new Category(name: 'Grails + Google Cloud', image: 'googlecloud.svg'), | ||
| gorm: new Category(name: 'GORM', image: 'gorm.svg'), | ||
| ios: new Category(name: 'Grails + iOS', image: 'ios.svg'), | ||
| react: new Category(name: 'Grails + React', image: 'react.svg'), | ||
| ria: new Category(name: 'Grails + RIA (Rich Internet Application)', image: 'ria.svg'), | ||
| testing: new Category(name: 'Grails Testing', image: 'testing.svg'), | ||
| vue: new Category(name: 'Grails + Vue.js', image: 'vue.svg'), | ||
| weblayer: new Category(name: 'Web Layer', image: 'views.svg'), |
Comment on lines
+142
to
+145
| * {@code githubBranch} / {@code githubSlug} on the guide is the version | ||
| * with the most recent publication date (or the last-iterated version if | ||
| * dates are missing/equal). This drives the "Read More" link in the | ||
| * {@code Latest Guides} sidebar.</p> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five recently-added Grails 8 guides plus three older entries were silently absent from https://grails.apache.org/guides/ even though their per-guide HTML was being built and uploaded correctly. Three independent bugs combined to cause this, and the bottom-of-page category grid was simultaneously overweight on dead/legacy stacks while missing the modern Web Layer section that every flagship Grails 8 guide lives in.
This PR fixes the index page, refactors the fetcher to be structurally robust against future YAML drift, and reshapes the category/tag presentation around what Apache Grails actually publishes today. All four fixes in one PR per request.
Net diff: 4 files changed, +451 / -224.
Before vs after (local
build/dist/guides/index.html)grails-multi-module/v8grails-rest-library/v8grails-github-actions-cicd/v8grails-as-docker-container/v3,v4grails-mock-basics/v4creating-your-first-grails-app/v6rest-hibernate/grails-on-github-actions/grails-multi-project-buildThe Latest Guides sidebar now contains all 8 new Grails 8 guides (was 5 of 8). The tag cloud is now 50 entries (was 200+).
What this PR does
1.
conf/guides.yml- 5sampleRef.repocollisionsFive YAML entries had
sampleRef.repopointing at a NEW guide's repo instead of their own. The fetcher used to group bysampleRef.repo, so they merged into the wrong guide:These look like the residue of repo renames that were applied in the sample registry but never propagated to the new entries that needed distinct slugs. Each of the GitHub URLs above 301-redirects today, so "Get the Code" buttons remain functional regardless.
2.
GuidesFetcher- identify guides by YAMLname, notsampleRef.repoEven with the YAML cleaned up, the fetcher was structurally fragile and had two latent bugs:
BRANCH_TO_MAJORmap hardcodedmaster -> 4and was missinggrails7,grails8.creating-your-first-grails-app/v6(whosesampleRef.branchismaster) was silently mapped to major version 4, overwriting the real v4 entry ingrailsMayorVersionTags. v6 was reachable only via direct URL.grails-mock-basicshad v3 and v4 both withbranch: masterunder one guide. The slug+branch grouping reduced to{master}, sosize == 1triggeredtoSingleGuideand silently returned the first match (v3).Refactor
parseGuidesto walk oneGuideper top-level YAML entry:versions:child →SingleGuideversions:children →GrailsVersionedGuidewith one rendered link per version. The YAML version key is now used directly as the integer major version, which eliminatesBRANCH_TO_MAJORentirely.sampleRef.repois now treated only as the "Get the Code" target on the rendered chrome - it is never used to identify or group guides. Two YAML entries that happen to point at the same external repo are still rendered as two separate guides.New
GuidesFetcherSpec(7 tests) pins all the regression cases:SingleGuidefor 1-version entriesGrailsVersionedGuidefor 2+-version entries with the YAML version key preserved as the int major version (master->4 + grails7/8 regression)sampleRef.reporender as two guides (4-collision regression)skipFutureis trueversions:blocks are silently skipped3.
GuidesPage.categories- drop dead categories, add Web LayerDropped:
Grails + Android,Grails + Angular,Grails + AngularJS,Grails + iOS,Grails + RIA (Rich Internet Application). Each had 1-2 stale guides and was eating equal real estate toAdvanced Grails(22 guides).Added:
Web Layer(5 guides today: htmx, tailwindcss, vite-spa, fields-custom-widgets-and-wrappers, rest-library). Without this section,rest-librarywas the only one of those guides that didn't fit in the top-8 Latest Guides sidebar, so it was completely invisible.Reshuffled the two-column grid so the modern stacks sit above the older ones:
Guides categorized in the dropped categories remain built and reachable via tags, search, and direct URL. Only the index-page section is gone. Their
categories/<slug>.htmlpages also stop being generated.4.
tagCloud- filter version labels, cap to top NThe
Guides by Tagcloud rendered every tag in the YAML, alphabetically, with no curation. Two problems:grails3,grails4, ...,grails8) artificially dominated the cloud by occurrence count without adding navigational value (the version is implicit in the guide URL).apple-tv,tvml,tvmljs,aws-elasticbeanstlak, ...) cluttered the sidebar.Now: filters out version labels via
~/^grails\d+$/, sorts by occurrence descending, takes topTAG_CLOUD_LIMIT = 50, then re-sorts alphabetically for display.Verification
What's intentionally still NOT linked from the index
After the dead-category drop, 7 guide-version pairs no longer have a category section to live in:
angular2-combined/v4,building-an-android-client-...,building-an-ios-objectc-client-...,building-an-ios-swift-client-...,grails-quickcasts-angularjs-scaffolding-with-grails-3/v4,grails-restapi-angularjs/v4,vaadin-grails/v4. Their per-guide HTML still builds. They remain reachable via tag pages, search, and direct URL. This is the user's chosen "drop dead categories" approach.