Retag every guide with a generous canonical taxonomy and reshape categories#505
Retag every guide with a generous canonical taxonomy and reshape categories#505jamesfredley merged 2 commits intomasterfrom
Conversation
…gories
Tags are the primary browse mechanism on the guides index - clicking a
tag lands on a curated page listing every guide carrying that tag - so
it is worth investing in a serious taxonomy. The previous YAML had 195
unique tags from a decade of ad-hoc tagging, with 73% of them appearing
in just 1-2 guides (typos like aws-elasticbeanstlak, synonyms like
rest+rest-api+restful-controller, version labels like grails3..grails8,
browser-specific noise like firefox+chrome, and one-off dead-ends).
After this change the registry has 289 thoughtfully assigned tags from
a curated taxonomy, every guide carries 8-15 generous tags reflecting
what it teaches, and the bottom-of-page category grid is reshaped
around the tracks Apache Grails 7/8 actually publishes today.
== conf/guides.yml: hand-curated tag list per guide (93 entries) ==
Every guide now has an explicit, generous tag set covering web stack,
persistence, testing, security, devops, frontend, libraries, and
patterns. Tags were derived from each guide's intro chapter, title,
subtitle, and category so they actually describe the guide content.
Top tags after the retag (by occurrence):
47 rest-api 21 unit-tests 17 gradle 13 spa
32 gorm 20 domain-classes 17 json-views 13 hibernate
21 testing 19 controllers 16 geb 12 spring-boot
21 unit-tests 18 devops 16 gsp 12 authentication
18 functional-tests 15 integration-tests
Distribution:
93 tags in 1 guide 60 tags in 3-5 guides 24 tags in 11+ guides
92 tags in 2 guides 20 tags in 6-10 guides
Synonym normalisation, typo fixes, and version-label removal applied:
rest, restful-controller -> rest-api
unit-test -> unit-tests
integration-test -> integration-tests
functional-test -> functional-tests
test -> testing
mock -> mocking
json views, json-view -> json-views
compose -> docker-compose
paketo -> buildpacks
health -> actuator
fields -> fields-plugin
log, logs -> logging
locale -> i18n
tvml, tvmljs, tvos -> apple-tv (canonical)
aws-elasticbeanstlak (typo) -> elasticbeanstalk
schwatz (typo) -> schwartz
rabbitMQ (case) -> rabbitmq
jpq-ql (typo) -> jpa-ql
spring boot (space) -> spring-boot
vue-profile, react-profile -> vue, react
Dropped entirely:
grails3..grails8 - version is implicit in the guide URL
firefox, chrome - covered by the geb tag
backend, frontend - vague (replaced by category placement)
framework, language - vague
task, execution - vague when standalone
github, git, google - too generic
java - all guides are java, no signal
ide - vague
test - merged into testing
phantomjs, htmlunit - dead tech
spock-spring - merged into spock
web-profile - profile choices are not navigational
profile, profiles - kept only where actually meaningful
== conf/guides.yml: 28 category reassignments ==
The bottom grid now surfaces the tracks Grails 7/8 actually publishes:
Security (NEW, 7 guides)
grails-basicauth, grails-spring-security-core-plugin-custom-authentication,
grails-oauth-google, grails-oauth-twitter,
grails-custom-security-tenant-resolver, react-spring-security,
grails-test-security
Previously buried under "Advanced Grails" alongside multi-tenancy
and SOAP, even though spring-security-* alone covers ~10 guides.
Frontend SPA (renamed/merged, 14 guides)
Combines the previous Grails + React (6) + Grails + Vue.js (3) +
Grails + Angular (1) + Grails + AngularJS (2) + iOS/Android/RIA
legacy categories. The integration patterns overlap heavily and
none of the individual sub-tracks justified its own column on the
index. Mobile-client guides (Android, iOS Objective-C, iOS Swift,
tvOS) join here since they're "frontend client consuming Grails".
Grails + Cloud (renamed/broadened, 3 guides)
Was Grails + Google Cloud. Now also includes grails-elasticbeanstalk
so all cloud-deployment guides browse together regardless of which
cloud provider they target.
Grails + DevOps (gained 3 guides)
grails-as-docker-container, adding-commit-info,
grails-docker-external-services moved out of "Advanced Grails"
where they were thematically miscategorised.
Web Layer (gained 1 guide)
vaadin-grails moved here - it's a server-rendered rich-UI framework,
not a SPA.
== buildSrc/src/main/groovy/website/model/guides/GuidesPage.groovy ==
categories map: dropped legacy `android`, `angular`, `angularjs`, `ios`,
`ria` keys (they had 1-2 guides each and have been folded into Frontend
SPA). Dropped `react` and `vue` keys (now Frontend SPA). Renamed
`googlecloud` to `cloud`. Added `security`, `spa`. The map now lists
exactly the 10 tracks rendered on the index, in the order they appear:
apprentice, advanced, weblayer, devops, gorm, testing, security, spa,
async, cloud.
mainContent: regenerated the two-column grid as 5 paired rows:
Apprentice | Advanced Grails
Web Layer | Grails + DevOps
GORM | Grails Testing
Security | Frontend SPA
Grails Async | Grails + Cloud
Tag cloud: removed the TAG_CLOUD_LIMIT cap. After retagging the
registry to a curated taxonomy, every survivor is worth showing - 289
tags total, sorted alphabetically for display, with occurrence still
driving the tagN CSS class so popular tags render larger. The version-
label filter (~/^grails\d+$/) is kept defensively so any future YAML
drift cannot resurrect grails3..grails8 in the cloud.
== Verified ==
./gradlew :buildSrc:test --tests 'website.model.guides.GuidesFetcherSpec'
-> all 7 pass
./gradlew build validateGuides --no-configuration-cache
-> BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors
build/dist/guides/index.html section headers (in render order):
Grails Apprentice, Advanced Grails, Web Layer, Grails + DevOps,
GORM, Grails Testing, Security, Frontend SPA, Grails Async,
Grails + Cloud
Tag cloud: 289 entries (was capped at 50, before that was 195 noisy
entries - now 289 curated entries with the long tail providing useful
deep-link navigation per guide).
Assisted-by: claude-code:claude-opus-4-7
There was a problem hiding this comment.
Pull request overview
This PR retags the guides registry (conf/guides.yml) with a revised canonical taxonomy and reshapes the guides index’ category grid/tag cloud behavior via GuidesPage.groovy, aligning navigation around current Grails tracks (Security, Frontend SPA, Cloud, etc.).
Changes:
- Retagged guides in
conf/guides.yml(normalize/remove legacy tags, expand per-guide tag sets, and reassign categories to the updated tracks). - Updated
GuidesPage.categoriesand regenerated the bottom-of-page category grid to the new 10-section layout (including new Security + Frontend SPA tracks). - Removed the tag-cloud top-N cap in
GuidesPage.tagCloud, keeping only defensive filters (e.g., version tags).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| conf/guides.yml | Large-scale tag taxonomy refresh and category reassignment per guide/version. |
| buildSrc/src/main/groovy/website/model/guides/GuidesPage.groovy | Updates category registry/grid layout and changes tag cloud to render all tags (no cap). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * the version is implicit in the guide URL.</li> | ||
| * <li>Empty-title tags are skipped defensively.</li> | ||
| * </ul> | ||
| * After retagging the registry to a curated taxonomy of ~70 canonical |
There was a problem hiding this comment.
Fixed in 6fde523: dropped the inaccurate 70 canonical tags figure from the docstring. The taxonomy is hand-curated but the guide-level enrichment drove the count to ~290; the docstring no longer pins a number.
| - 'domain-classes' | ||
| - 'restful-controller' | ||
| - 'spring-boot' | ||
| - 'controllers' |
There was a problem hiding this comment.
Updated the PR description rather than the YAML. restful-controller is intentionally kept distinct from rest-api: it refers to the specific Grails RestfulController class. Guides that demonstrate RestfulController (this one + rest-hibernate) are a different navigation cut from the broader REST-API tag. Added an explicit Intentionally kept distinct table to the PR description so the discrepancy with the synonym table is documented.
| - 'containerization' | ||
| - 'boot-build-image' | ||
| - 'paketo' | ||
| - 'buildpacks' | ||
| - 'postgres' | ||
| - 'actuator' | ||
| - 'oci' | ||
| - 'grails8' | ||
| - 'postgresql' |
There was a problem hiding this comment.
Updated the PR description. paketo is intentionally kept alongside buildpacks: Paketo is the specific buildpack provider used in this guide, while buildpacks is the general spec. Both serve as useful, distinct navigation entry points (clicking paketo should land readers on guides that specifically use Paketo; clicking buildpacks is broader). Documented this in the new Intentionally kept distinct section of the PR description.
| - 'json' | ||
| - 'crud' | ||
| - 'controllers' | ||
| - 'restful-controller' | ||
| - 'serialization' |
There was a problem hiding this comment.
Same as the v8 entry: restful-controller is intentionally kept distinct from rest-api (it refers to the specific Grails RestfulController class). PR description updated with an Intentionally kept distinct table that documents this; the synonym table no longer claims a restful-controller -> rest-api merge.
| - 'json' | ||
| - 'crud' | ||
| - 'controllers' | ||
| - 'restful-controller' | ||
| - 'serialization' |
There was a problem hiding this comment.
Same as the v8 entry: restful-controller is intentionally kept distinct from rest-api (it refers to the specific Grails RestfulController class). PR description updated with an Intentionally kept distinct table that documents this; the synonym table no longer claims a restful-controller -> rest-api merge.
Per Copilot review on PR #505: the actual taxonomy ended up at ~290 unique tags (driven by per-guide enrichment, not by a top-down cap), so the docstring's '~70' figure is misleading. The taxonomy is hand-curated but unbounded - the docstring now just says so. Assisted-by: claude-code:claude-opus-4-7
The recent grails-static-website taxonomy reshape (apache/grails-static-website#505) introduced four new category slugs that the live site now serves: https://grails.apache.org/guides/categories/frontendspa.html https://grails.apache.org/guides/categories/grailscloud.html https://grails.apache.org/guides/categories/security.html https://grails.apache.org/guides/categories/weblayer.html The legacy guides.grails.org host did not have stubs for these, so visiting e.g. https://guides.grails.org/categories/security.html returned 404. The 14 pre-existing category stubs all still resolve (verified against the live grails.apache.org site - the reshape kept backwards-compat slugs for the older categories). No tag stubs added: tag/<slug>.html paths are not currently served on grails.apache.org (every probe returns 404).
Summary
Retags every guide in
conf/guides.ymlwith a generous, canonical taxonomy and reshapes the bottom-of-page category grid around the tracks Apache Grails 7/8 actually publishes.Stacked on #504 (now merged). Once the base updates, this PR's diff will be exactly these retag changes.
The previous YAML had 195 unique tags from a decade of ad-hoc tagging - 73% of them appearing in just 1-2 guides each, with typos (
aws-elasticbeanstlak), synonyms (rest+rest-api), version labels (grails3..grails8), browser-specific noise (firefox+chrome), and one-off dead-ends. After this PR the registry has 289 thoughtfully assigned tags from a curated taxonomy, every guide carries 8-15 tags reflecting what it teaches, and the category grid is reorganised around modern Grails tracks.Net diff: 2 files changed, +877 / -373.
Why this matters
Tags are the primary navigation mechanism on
https://grails.apache.org/guides/:Anaemic tagging defeats both: the cloud feels empty, tag pages have one guide each, and users fall back on the small bottom-of-page category grid. Generous tagging makes the cloud the real first-class browse mechanism the user asked for.
Top tags after retag (by occurrence)
Distribution: 93 tags in 1 guide, 92 in 2, 60 in 3-5, 20 in 6-10, 24 in 11+.
Synonym normalisation, typo fixes, and drops
restrest-apiunit-test/integration-test/functional-test*-tests(plural)test,data-testtestingmockmockingjson views(space),json-viewjson-viewscomposedocker-composehealthactuatorfieldsfields-pluginlog,logslogginglocalei18ntvmljstvmlaws-elasticbeanstlak(typo)elasticbeanstalkschwatz(typo)schwartzrabbitMQ(case)rabbitmqjpq-ql(typo)jpa-qlspring boot(space)spring-bootDropped entirely:
grails3..grails8- version is implicit in the URLfirefox,chrome- covered bygebbackend,frontend,framework,language,web,task,execution,github,git,google,java,ide,automation,asynchronous- vaguephantomjs,htmlunit,web-profile- dead/redundantIntentionally kept distinct
A few tags that look like synonyms are kept as separate, navigable axes because they describe genuinely distinct concepts:
restful-controller(kept, NOT merged intorest-api)RestfulControllerclass. Guides that demonstrateRestfulController(rest-library, rest-hibernate) are different from the broader REST-API surface.paketo(kept alongsidebuildpacks)grails-docker-bootbuildimage;buildpacksis the general spec. Both are useful navigation entry points.tvml,tvos,apple-tvspock-spring(kept alongsidespock)grails-email.Category grid reshape (28 reassignments)
grails-basicauth,grails-spring-security-core-plugin-custom-authentication,grails-oauth-google,grails-oauth-twitter,grails-custom-security-tenant-resolver,react-spring-security,grails-test-security(previously buried under "Advanced Grails")Grails + React,Grails + Vue.js,Grails + Angular,Grails + AngularJS, plus mobile-client guides (Android, iOS Objective-C, iOS Swift, tvOS)Grails + Google Cloud. Now alsograils-elasticbeanstalkgrails-as-docker-container,adding-commit-info,grails-docker-external-servicesmoved out ofAdvanced Grailsvaadin-grailsmoved here - server-rendered rich UI, not SPAFinal 10 sections rendered, in display order:
GuidesPage.groovychangescategoriesmap: droppedandroid,angular,angularjs,ios,ria,react,vue,googlecloudkeys. Addedsecurity,spa,cloud. Renamed for the new layout.mainContentregenerated as the 5-row grid above.tagCloudremoved theTAG_CLOUD_LIMITcap entirely. After retagging, every survivor is worth showing - 289 tags total, sorted alphabetically for display, with occurrence still driving thetagNCSS class so popular tags render visually larger. The version-label filter (~/^grails\d+$/) is kept defensively.Verification
./gradlew :buildSrc:test --tests 'website.model.guides.GuidesFetcherSpec'-> all 7 pass./gradlew build validateGuides --no-configuration-cache->BUILD SUCCESSFUL,validateGuidesreports93 guide(s) parsed, 0 errorsbuild/dist/guides/index.htmlhas all 10 expected<h2>sections, everyLatest Guidesentry maps to a real built page, the tag cloud has 289 entries, and clicking any popular tag lands on a category page with the expected guides.