Skip to content

Retag every guide with a generous canonical taxonomy and reshape categories#505

Merged
jamesfredley merged 2 commits intomasterfrom
retag-guides-canonical-taxonomy
May 4, 2026
Merged

Retag every guide with a generous canonical taxonomy and reshape categories#505
jamesfredley merged 2 commits intomasterfrom
retag-guides-canonical-taxonomy

Conversation

@jamesfredley
Copy link
Copy Markdown
Contributor

@jamesfredley jamesfredley commented May 4, 2026

Summary

Retags every guide in conf/guides.yml with 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/:

  • Click a tag in the cloud -> land on a curated page listing every guide carrying that tag
  • The tag pages are individually search-engine indexed
  • Categories are a secondary, smaller curated set of "tracks" for skimming by axis

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)

 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, 92 in 2, 60 in 3-5, 20 in 6-10, 24 in 11+.

Synonym normalisation, typo fixes, and drops

from to
rest rest-api
unit-test / integration-test / functional-test *-tests (plural)
test, data-test testing
mock mocking
json views (space), json-view json-views
compose docker-compose
health actuator
fields fields-plugin
log, logs logging
locale i18n
tvmljs tvml
aws-elasticbeanstlak (typo) elasticbeanstalk
schwatz (typo) schwartz
rabbitMQ (case) rabbitmq
jpq-ql (typo) jpa-ql
spring boot (space) spring-boot

Dropped entirely:

  • grails3..grails8 - version is implicit in the URL
  • firefox, chrome - covered by geb
  • backend, frontend, framework, language, web, task, execution, github, git, google, java, ide, automation, asynchronous - vague
  • phantomjs, htmlunit, web-profile - dead/redundant

Intentionally kept distinct

A few tags that look like synonyms are kept as separate, navigable axes because they describe genuinely distinct concepts:

Tag Why distinct
restful-controller (kept, NOT merged into rest-api) Refers to the specific Grails RestfulController class. Guides that demonstrate RestfulController (rest-library, rest-hibernate) are different from the broader REST-API surface.
paketo (kept alongside buildpacks) Paketo is the specific buildpack provider used in grails-docker-bootbuildimage; buildpacks is the general spec. Both are useful navigation entry points.
tvml, tvos, apple-tv Markup language vs OS vs device family - three different navigation axes.
spock-spring (kept alongside spock) Specific Spock-with-Spring extension used in grails-email.

Category grid reshape (28 reassignments)

New category Guides Source
Security (NEW) 7 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")
Frontend SPA (renamed/merged) 14 All Grails + React, Grails + Vue.js, Grails + Angular, Grails + AngularJS, plus mobile-client guides (Android, iOS Objective-C, iOS Swift, tvOS)
Grails + Cloud (renamed/broadened) 3 Was Grails + Google Cloud. Now also grails-elasticbeanstalk
Grails + DevOps (gained 3) 10 grails-as-docker-container, adding-commit-info, grails-docker-external-services moved out of Advanced Grails
Web Layer (gained 1) 6 vaadin-grails moved here - server-rendered rich UI, not SPA

Final 10 sections rendered, in display order:

| Grails Apprentice | Advanced Grails  |
| Web Layer         | Grails + DevOps  |
| GORM              | Grails Testing   |
| Security          | Frontend SPA     |    <- both new positions
| Grails Async      | Grails + Cloud   |

GuidesPage.groovy changes

  • categories map: dropped android, angular, angularjs, ios, ria, react, vue, googlecloud keys. Added security, spa, cloud. Renamed for the new layout.
  • mainContent regenerated as the 5-row grid above.
  • tagCloud removed the TAG_CLOUD_LIMIT cap entirely. After retagging, every survivor is worth showing - 289 tags total, sorted alphabetically for display, with occurrence still driving the tagN CSS 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, validateGuides reports 93 guide(s) parsed, 0 errors
  • Rendered build/dist/guides/index.html has all 10 expected <h2> sections, every Latest Guides entry 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.

…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
Copilot AI review requested due to automatic review settings May 4, 2026 10:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.categories and 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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread conf/guides.yml
Comment on lines +4036 to +4039
- 'domain-classes'
- 'restful-controller'
- 'spring-boot'
- 'controllers'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread conf/guides.yml
Comment on lines +1897 to +1902
- 'containerization'
- 'boot-build-image'
- 'paketo'
- 'buildpacks'
- 'postgres'
- 'actuator'
- 'oci'
- 'grails8'
- 'postgresql'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread conf/guides.yml
Comment on lines +5288 to +5292
- 'json'
- 'crud'
- 'controllers'
- 'restful-controller'
- 'serialization'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread conf/guides.yml
Comment on lines +5329 to +5333
- 'json'
- 'crud'
- 'controllers'
- 'restful-controller'
- 'serialization'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@jamesfredley jamesfredley changed the base branch from fix-guides-index-page to master May 4, 2026 10:33
@jamesfredley jamesfredley merged commit 18daf93 into master May 4, 2026
@jamesfredley jamesfredley deleted the retag-guides-canonical-taxonomy branch May 4, 2026 11:02
jamesfredley added a commit to grails-guides/grails-guides-template that referenced this pull request May 4, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants