)]}'
{
  "log": [
    {
      "commit": "00c267d3209c41f523e38b89efd30b82c06ec733",
      "tree": "438b6fc0ac981c567dd22563c75cb0a8b7844a88",
      "parents": [
        "90982e1bae4b9ace964df23de4f0bbc4b0d7954e",
        "d1c56668f91274e7d0b27091bcc4902a54513d03"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Fri Jun 26 15:19:09 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jun 26 15:19:09 2026 -0400"
      },
      "message": "Merge pull request #519 from sanjana2505006/grails-spring-security-v8-guide\n\nAdd Grails 8 Spring Security guide (v8 prose, snippets, guides.yml)"
    },
    {
      "commit": "d1c56668f91274e7d0b27091bcc4902a54513d03",
      "tree": "438b6fc0ac981c567dd22563c75cb0a8b7844a88",
      "parents": [
        "0496c3e13ece0016da21d3194c6492b2ac84e88a"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Sat Jun 27 00:46:25 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Sat Jun 27 00:46:25 2026 +0530"
      },
      "message": "Clarify PostgreSQL is required for bootRun in initial and complete\n"
    },
    {
      "commit": "0496c3e13ece0016da21d3194c6492b2ac84e88a",
      "tree": "fc96c712c2116aa1d6a5a85c894ac9b2e70db1ef",
      "parents": [
        "90982e1bae4b9ace964df23de4f0bbc4b0d7954e"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 26 21:30:58 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 26 21:37:04 2026 +0530"
      },
      "message": "Add Grails 8 Spring Security guide (v8 prose, snippets, guides.yml)\n\nGuide under guides/grails-spring-security/v8 with vendored snippets.\nRegisters grails-spring-security in conf/guides.yml.\nSample app: grails-guides/grails-spring-security (grails8).\n"
    },
    {
      "commit": "90982e1bae4b9ace964df23de4f0bbc4b0d7954e",
      "tree": "ab3f626744cec1ab5575fabf9cd65936fe2f62ca",
      "parents": [
        "ad4914533436ca1b2eb449403d7c0a07591f520d"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu Jun 25 14:06:45 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Jun 25 14:06:45 2026 -0400"
      },
      "message": "Update release date from Jun 2027 to Jul 2027\n\nBased on https://endoflife.date/spring-boot"
    },
    {
      "commit": "ad4914533436ca1b2eb449403d7c0a07591f520d",
      "tree": "8e73030a2105446569cd8de877a15d60944615a8",
      "parents": [
        "d5d2d735ad2c0d3674c7c3505aa97df3d2ecbfa8"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Mon Jun 22 15:28:17 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Mon Jun 22 15:28:17 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nAdd 7.1.2 to Grails Versions list\n"
    },
    {
      "commit": "d5d2d735ad2c0d3674c7c3505aa97df3d2ecbfa8",
      "tree": "a7ddd4dabcd607889aab2bf2a0e35fa79335dbd8",
      "parents": [
        "df4d2f0742ad0ace7b13a6cf734e49bf6f315307"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Mon Jun 22 15:26:41 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Mon Jun 22 15:26:41 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nAdd 7.0.12 to Grails Versions list\n"
    },
    {
      "commit": "df4d2f0742ad0ace7b13a6cf734e49bf6f315307",
      "tree": "1b915c81ea464b3872e07f9b05516dbd71de7e23",
      "parents": [
        "e103a5a13b9360f708463696302d2aef8d0068d4",
        "f02e1d26301caf0e7f751bb05132a6eb9816e3e4"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Tue Jun 16 18:19:06 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 16 18:19:06 2026 -0400"
      },
      "message": "Merge pull request #517 from sanjana2505006/grails-data-access-v8-guide\n\nGrails data access v8 guide"
    },
    {
      "commit": "f02e1d26301caf0e7f751bb05132a6eb9816e3e4",
      "tree": "1b915c81ea464b3872e07f9b05516dbd71de7e23",
      "parents": [
        "08a46d307aa44abe5811acf68c405e5bb25d8430"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Tue Jun 16 18:13:46 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Tue Jun 16 18:13:46 2026 -0400"
      },
      "message": "Fix grails-data-access v8 guide so it renders and matches conventions\n\nThe guide failed to render: the conf/guides.yml toc referenced sub-section\nkeys (cloneAndRun, author, book, criteria, hql, whereQueries, unitTests,\nintegrationTests) that have no backing .adoc files, which aborts the TOC\nbuild with \"No file found for ...\".\n\n- conf/guides.yml: drop the file-less toc sub-keys, keeping each chapter as a\n  single page; add the helpWithGrails chapter last, matching the other v8 guides.\n- introduction.adoc: remove the stray document header and attributes\n  (\u003d title, :toc:, :source-highlighter:, :imagesdir:, etc.) and the\n  include::requirements.adoc[] that double-rendered the requirements section.\n- All chapters: drop the duplicate leading \"\u003d\u003d \u003ctitle\u003e\" line (the title is\n  rendered from the toc) and promote in-page subsections from \u003d\u003d\u003d to \u003d\u003d.\n- Add guide/helpWithGrails.adoc -\u003e include::{commondir}/common-helpWithGrails.adoc[].\n\nVerified locally: validateGuides -PvalidationMode\u003dboth reports 0 errors and\nrenderGuide_grails_data_access_8 builds successfully; snippets resolve and the\nrequirements content renders once.\n\nAssisted-by: opencode:claude-opus-4-8\n"
    },
    {
      "commit": "08a46d307aa44abe5811acf68c405e5bb25d8430",
      "tree": "f24ad76790dc810017dced0975b3da678ce01503",
      "parents": [
        "dfe031de84cabb25ce0c36765466b699a90d4dd0",
        "e103a5a13b9360f708463696302d2aef8d0068d4"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Tue Jun 16 16:53:35 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jun 16 16:53:35 2026 -0400"
      },
      "message": "Merge branch \u0027master\u0027 into grails-data-access-v8-guide"
    },
    {
      "commit": "e103a5a13b9360f708463696302d2aef8d0068d4",
      "tree": "b4ff23c0e4559fe59c51b14e7043e19257ee2b4c",
      "parents": [
        "fdc932f12f8152feee34ddb06fab4140bfef847a",
        "6e49a8f7e87e7c1dfcd551957fdabb7a9df319ca"
      ],
      "author": {
        "name": "Søren Berg Glasius",
        "email": "soeren@glasius.dk",
        "time": "Wed Jun 10 14:12:27 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jun 10 14:12:27 2026 +0200"
      },
      "message": "Merge pull request #418 from sanjana2505006/feature/modernization\n\nModernization: Lazy loading images, smooth scrolling, and cleanup"
    },
    {
      "commit": "6e49a8f7e87e7c1dfcd551957fdabb7a9df319ca",
      "tree": "b4ff23c0e4559fe59c51b14e7043e19257ee2b4c",
      "parents": [
        "c9067c0eda6d439036567d6f81356f797fca6e1e"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Tue Jun 09 20:08:08 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Tue Jun 09 20:08:08 2026 +0530"
      },
      "message": "Add Community over Code logo to homepage event\n"
    },
    {
      "commit": "c9067c0eda6d439036567d6f81356f797fca6e1e",
      "tree": "6d5356e2b52d79bbb28f773ceef21d9c7f56b04f",
      "parents": [
        "becdb02c7a6f0f1e90a1c2d50b220f2ca143cf13"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Mon Jun 08 18:45:48 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Mon Jun 08 18:46:47 2026 +0530"
      },
      "message": "Add Community over Code event to homepage\n"
    },
    {
      "commit": "becdb02c7a6f0f1e90a1c2d50b220f2ca143cf13",
      "tree": "1597594f9cf03d169a1d69192f670e56cbe1d793",
      "parents": [
        "952e62be503f90ccc10b03353eb707481811547d",
        "fdc932f12f8152feee34ddb06fab4140bfef847a"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Sat Jun 06 01:23:00 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Jun 06 01:23:00 2026 +0530"
      },
      "message": "Merge branch \u0027master\u0027 into feature/modernization"
    },
    {
      "commit": "dfe031de84cabb25ce0c36765466b699a90d4dd0",
      "tree": "3e78a6d6c68b190c80d7e1cd1b598723bc25c334",
      "parents": [
        "cecd77c0e830c42b27b95109c7a539ebafbecb7f"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 05 23:44:14 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 05 23:44:14 2026 +0530"
      },
      "message": "Remove local-only paths from .gitignore\n"
    },
    {
      "commit": "cecd77c0e830c42b27b95109c7a539ebafbecb7f",
      "tree": "281ee15f8cf896edf1e1799c4de86b26dbf52ac1",
      "parents": [
        "fdc932f12f8152feee34ddb06fab4140bfef847a"
      ],
      "author": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 05 02:50:30 2026 +0530"
      },
      "committer": {
        "name": "Sanjana",
        "email": "sanju250506@gmail.com",
        "time": "Fri Jun 05 02:50:30 2026 +0530"
      },
      "message": "Add Grails 8 data access guide (v8 prose, snippets, guides.yml)\n\nGuide under guides/grails-data-access/v8 with vendored snippets.\nRegisters grails-data-access in conf/guides.yml.\nSample app: grails-guides/grails-data-access (grails8).\n"
    },
    {
      "commit": "fdc932f12f8152feee34ddb06fab4140bfef847a",
      "tree": "5362ae917e68a66048d1a65ffe1160f6f9800d90",
      "parents": [
        "c6ec22dbc6322284a98f3861a64f649e859c5785"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Wed Jun 03 14:40:10 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jun 03 14:40:10 2026 -0400"
      },
      "message": "Update support date from Dec 2026 to Jun 2027"
    },
    {
      "commit": "c6ec22dbc6322284a98f3861a64f649e859c5785",
      "tree": "16ce30e84b8605a080c6b05ee7c5574f7cb8c844",
      "parents": [
        "dfb80baa0bc8b3e00011a96fb86904e69ebe5c7c",
        "3fc10ac28cebfac7d7add750f564547147895138"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 13:27:34 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 13:27:34 2026 -0400"
      },
      "message": "Merge pull request #516 from apache/fix-guides-og-url\n\nFix Open Graph URL for generated guides pages"
    },
    {
      "commit": "3fc10ac28cebfac7d7add750f564547147895138",
      "tree": "16ce30e84b8605a080c6b05ee7c5574f7cb8c844",
      "parents": [
        "dfb80baa0bc8b3e00011a96fb86904e69ebe5c7c"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 13:17:57 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 13:17:57 2026 -0400"
      },
      "message": "Fix Open Graph URL for generated guides pages\n\nrenderPages computed ogurl as \u003csite\u003e + Page.path and merged it last,\noverriding any page-provided value. The generated guides pages (index,\ntags, categories, versions) carry only a bare leaf filename as their path,\nso og:url rendered as e.g. https://grails.apache.org8.html instead of\nhttps://grails.apache.org/guides/versions/8.html. Disk-parsed main-site\npages were unaffected because their path is already root-relative.\n\nLet renderPages keep a page-provided ogurl (computing the default only\nwhen absent), and have GuidesTask set the correct absolute ogurl in the\nfront-matter of each generated page (index, tags, categories, versions).\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "dfb80baa0bc8b3e00011a96fb86904e69ebe5c7c",
      "tree": "af06cc673e6c97702fe8256046381afbda59d9f2",
      "parents": [
        "6c0039feecab29f4a88f987923083847583fcff6",
        "c886b7cc43f8f5456983a927bd9e4b9572826f70"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 13:05:35 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jun 01 13:05:35 2026 -0400"
      },
      "message": "Merge pull request #515 from apache/guides-filter-by-grails-version\n\nAdd Grails version filter to the guides listing page"
    },
    {
      "commit": "c886b7cc43f8f5456983a927bd9e4b9572826f70",
      "tree": "af06cc673e6c97702fe8256046381afbda59d9f2",
      "parents": [
        "2f0316f856519f3b13905a007490e01e913e47db"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:58:52 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:58:52 2026 -0400"
      },
      "message": "Compute available versions only when the version cloud is rendered\n\nAddresses PR review feedback: availableVersions(guides) was evaluated for\nevery generated page even though leftColumn only renders the version/tag\nclouds on the index and version pages. Compute it lazily so the per-tag and\nper-category pages no longer scan and sort the full guide list.\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "2f0316f856519f3b13905a007490e01e913e47db",
      "tree": "9fe47f3df48330a915521e0552e830f662a9ba11",
      "parents": [
        "7cf81ce73e08ecb7ec74c8dce589197b87f5b35b"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:42:27 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:42:27 2026 -0400"
      },
      "message": "Add Grails version filter to the guides listing page\n\nAdd a \"Guides by Grails Version\" cloud to the guides index, above the tag\ncloud, and generate a static /guides/versions/\u003cN\u003e.html page per Grails major\nversion - mirroring the existing tag cloud and /guides/tags/\u003cslug\u003e.html pages.\nEach version page lists every guide published for that version using the\nLatest Guides treatment (title, date, category, Read More) on the left, with\nthe version and tag clouds beside it. The version cloud collapses during\nsearch like the tag cloud.\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "7cf81ce73e08ecb7ec74c8dce589197b87f5b35b",
      "tree": "fe243939f7c31b3cbc33de875aabbae9b2a256f8",
      "parents": [
        "6c0039feecab29f4a88f987923083847583fcff6"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:41:44 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon Jun 01 12:41:44 2026 -0400"
      },
      "message": "Make GuidesTask compatible with the configuration cache\n\nrenderGuides() resolved templates/partials through project.rootProject at\nexecution time, which is unsupported with the configuration cache and made\ngenGuides fail when the cache was enabled. Capture the partials directory as\nan @InputDirectory wired at configuration time, mirroring RenderSiteTask, so\nthe task no longer touches project during execution.\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "6c0039feecab29f4a88f987923083847583fcff6",
      "tree": "37e1e3f35609374a89c22a9a5d1b841f3ae1622e",
      "parents": [
        "8d5ab9c5387491acd9efb2c0776d8eb5ef81928a",
        "9ee0cf2328625ac75be4b0e6b985d50707b019af"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 21:35:20 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 31 21:35:20 2026 -0400"
      },
      "message": "Merge pull request #514 from apache/simplify-transactional-events-listeners-as-services\n\nSimplify grails-transactional-events v8: listeners are plain services"
    },
    {
      "commit": "9ee0cf2328625ac75be4b0e6b985d50707b019af",
      "tree": "37e1e3f35609374a89c22a9a5d1b841f3ae1622e",
      "parents": [
        "9f761688290b824d525840a575452dd0c3f773ef"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 21:32:03 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 21:32:03 2026 -0400"
      },
      "message": "Use Grails\u0027 injected log in NotificationService; complete @ComponentScan example\n\nThe NotificationService snippet declared its own SLF4J logger, but as a\nGrails service artefact the class gets a `log` property injected by\nconvention - the explicit field collides with it (the upstream sample app\u0027s\ncompileGroovy fails with \"Class annotated with Log annotation cannot have\nlog field declared\"). Drop the explicit logger and use the injected `log`.\n\nAlso add the GrailsAutoConfiguration and ComponentScan imports to the\nillustrative @ComponentScan example in the registration chapter so it is\nself-contained (review feedback).\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "9f761688290b824d525840a575452dd0c3f773ef",
      "tree": "9b7146a50e53180c70a15453d02b3a1463668895",
      "parents": [
        "8d5ab9c5387491acd9efb2c0776d8eb5ef81928a"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 21:02:24 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 21:02:24 2026 -0400"
      },
      "message": "Simplify grails-transactional-events v8: listeners are plain services\n\nThe v8 guide registered its four Spring event listeners one-by-one in\nresources.groovy (behind a long explanatory comment) because the classes\nwere named *Listener and so were not Grails artefacts. Renaming them to\n*Service lets Grails auto-register them by convention - the simpler,\nidiomatic approach:\n\n- CustomerLifetimeValueListener -\u003e CustomerLifetimeValueService\n- AuditListener               -\u003e AuditService\n- NotificationListener        -\u003e NotificationService\n- WorkOrderAssignmentListener -\u003e WorkOrderPlanningService\n\nresources.groovy now declares only the TransactionalEventListenerFactory\nbean - the one piece Grails genuinely does not provide, because it drives\ntransactions through GORM\u0027s @Transactional rather than Spring\u0027s\n@EnableTransactionManagement.\n\nThe narrative is updated throughout: the registration chapter also explains\nthe src/main/groovy + @ComponentScan alternative, and the AFTER_COMMIT\nguidance consistently uses withNewTransaction (Grails\u0027 @Transactional AST\ntransform would otherwise hide a listener method from Spring\u0027s scanner).\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "8d5ab9c5387491acd9efb2c0776d8eb5ef81928a",
      "tree": "247a43fa0c6f3fb2dae648e15e09d9ead6965d0e",
      "parents": [
        "e387cef3788fde86fea2dd19976c780da6c2b264",
        "75d0c573e0e21e45b05bd5710877e3e3387b733b"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 12:20:44 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 31 12:20:44 2026 -0400"
      },
      "message": "Merge pull request #509 from apache/add-grails-transactional-events-v8\n\nAdd grails-transactional-events v8 guide"
    },
    {
      "commit": "e387cef3788fde86fea2dd19976c780da6c2b264",
      "tree": "6423f0354bab6e7831fe652cf024a4227c58d14a",
      "parents": [
        "aad6d3e76a5eaeb2f92cef20ad514e959ed88342",
        "59f24111da94853f1c217aa51df2a155bda68070"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 12:20:41 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 31 12:20:41 2026 -0400"
      },
      "message": "Merge pull request #513 from apache/revendor-v8-guide-fixes\n\nRe-vendor v8 guide snippets from CI-green upstream sample apps"
    },
    {
      "commit": "59f24111da94853f1c217aa51df2a155bda68070",
      "tree": "4680c13730ff6ed30c25af4b3e413c05cc44f869",
      "parents": [
        "158f823ef840e67dfc3eebd421db31f40983c5e4"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 11:59:19 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 11:59:19 2026 -0400"
      },
      "message": "Address Oracle review: re-vendor test fixes and correct fields narrative\n\nSync the static-site with the upstream fixes from the Oracle pre-merge review:\n\n- htmx TaskIntegrationSpec: deterministic newest-first sort test (explicit\n  dateCreated, no Thread.sleep) and an accurately named punctuation-search test.\n- spock-test-tour BookControllerSpec: unit-test the index model directly\n  (bookList/bookCount) instead of a no-op `contentAsString !\u003d null` assertion.\n- rest-library BookFunctionalSpec: the author-filter test now seeds a second\n  author and asserts that author\u0027s book is excluded, so it can no longer pass\n  when filtering is ignored.\n- fields appWideTable.adoc: correct the model-variable table to match the\n  actual _table.gsp - domainClass builds the i18n message code, action buttons\n  use g:link resource\u003d\"${instance}\", entries expose name/naturalName, and\n  displayStyle/theme are not forwarded.\n\nAll four guides render clean.\n\nAssisted-by: claude-code:claude-opus-4-8\n"
    },
    {
      "commit": "158f823ef840e67dfc3eebd421db31f40983c5e4",
      "tree": "04b15dc9c04397684e0b9daa82e4895867577c4f",
      "parents": [
        "19bbdbcd9cb4f8d73fa541e0b4341e3f3a15b4c2"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 11:00:12 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 31 11:00:12 2026 -0400"
      },
      "message": "Address Copilot review: re-vendor htmx, multi-module, spock-test-tour fixes\n\nSync three snippet files with the upstream fixes made in response to the\nCopilot PR review on this PR:\n\n- htmx TaskIntegrationSpec: assert grails.validation.ValidationException (not a\n  generic Exception) and describe the blank/null-title cases as validation\n  (not database) rejection.\n- multi-module AdminBookFunctionalSpec: correct the test descriptions to match\n  the /books endpoint the requests actually hit.\n- spock-test-tour BookController: delegate show() to super.show() instead of\n  duplicating RestfulController logic (the method stays explicitly declared so\n  the URL-mapping layer still registers it).\n\nAll three guides render clean; the upstream builds remain green.\n\nAssisted-by: claude-code:claude-opus-4-8\n"
    },
    {
      "commit": "19bbdbcd9cb4f8d73fa541e0b4341e3f3a15b4c2",
      "tree": "0a7f9d751ed976c4e3635357ae219ef553823d9d",
      "parents": [
        "d28303cbc674850b64f479b5cf916017a2b3a730"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 21:08:30 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 21:08:30 2026 -0400"
      },
      "message": "Re-vendor grails-multi-module v8 functional specs from CI-green upstream\n\nSync the webapp-admin and webapp-customer @Integration HttpClient functional\nspecs byte-identical with grails-guides/grails-multi-module#9 (CI green):\nfull Book CRUD + 404/422 over HTTP (admin) and catalog list / show / 404\n(customer). Guide renders clean.\n\nAssisted-by: claude-code:claude-opus-4-8\n"
    },
    {
      "commit": "d28303cbc674850b64f479b5cf916017a2b3a730",
      "tree": "ee8e5e2c4fe75515f076877f1f1b8209f2572876",
      "parents": [
        "7313955e3055f6a0042cfe7ee6576176498f2329"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 20:35:59 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 20:35:59 2026 -0400"
      },
      "message": "Re-vendor 7 v8 guide snippets from CI-green upstream sample apps\n\nSync vendored snippets byte-identical with the upstream grails-guides\nsample apps whose test-coverage PRs are now CI-green. Functional specs\nuse the canonical ContainerGebSpec (Testcontainers); no webdriver-binaries.\n\n- rest-library: UrlMappings root-mapping fix + expanded BookFunctionalSpec\n- spock-test-tour: BookController show fix + BookControllerSpec + BookServiceSpec + BookFunctionalSpec\n- htmx: TaskController + TaskIntegrationSpec + TaskTrackerFunctionalSpec\n- tailwindcss: TailwindFunctionalSpec\n- vite-spa: ApiFunctionalSpec + BookControllerSpec\n- docker-bootbuildimage: build.gradle testcontainers-bom platform line\n- github-actions-cicd: HomePageFunctionalSpec + MessageIntegrationSpec + MessageSpec\n\nAll seven guides render clean (renderGuide_* BUILD SUCCESSFUL).\n\nAssisted-by: claude-code:claude-opus-4-8\n"
    },
    {
      "commit": "7313955e3055f6a0042cfe7ee6576176498f2329",
      "tree": "9576a15f1f25c76a25364d99a94cae227b462301",
      "parents": [
        "f76fda5584aa368b43f1f89053fda3c7af8cd69f"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 14:25:15 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sat May 30 14:25:15 2026 -0400"
      },
      "message": "Re-vendor grails-fields v8: domain-agnostic f:table + full per-action Geb coverage\n\nSync the vendored snippets and narrative with the upstream fix\n(grails-guides/grails-fields-custom-widgets-and-wrappers#1):\n\n- templates/_fields/_table.gsp: iterate with var\u003d\"instance\" and build the\n  delete-confirm from instance.toString() instead of the hard-coded book /\n  book.title, so the app-wide table renders for Author and any title-less\n  domain, not just Book. This is the crash reported in grails-core\n  discussion #13533.\n- LibraryFunctionalSpec.groovy: expand from 4 to 11 Geb specs covering every\n  scaffolded page and action of both Book and Author, including the\n  /author/index regression guard and the save/update/delete actions.\n- appWideTable.adoc / testing.adoc: update the prose to match.\n\nAssisted-by: claude-code:claude-opus-4-8\n"
    },
    {
      "commit": "aad6d3e76a5eaeb2f92cef20ad514e959ed88342",
      "tree": "2debc9866ff68fd1e8a14aa97581b676f54a44c9",
      "parents": [
        "f76fda5584aa368b43f1f89053fda3c7af8cd69f",
        "ffd8a67017bc0ed362e3f7dbb96ed94531fd3f3a"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sat May 30 11:12:46 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 11:12:46 2026 -0400"
      },
      "message": "Merge pull request #512 from apache/ci/setup-gradle-v6-basic-cache\n\nci: audit and update GitHub Actions to ASF-approved versions"
    },
    {
      "commit": "ffd8a67017bc0ed362e3f7dbb96ed94531fd3f3a",
      "tree": "2debc9866ff68fd1e8a14aa97581b676f54a44c9",
      "parents": [
        "f76fda5584aa368b43f1f89053fda3c7af8cd69f"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Fri May 29 13:10:08 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Fri May 29 13:10:08 2026 -0400"
      },
      "message": "ci: audit and update GitHub Actions to ASF-approved versions\n\nPin every external GitHub Action to a full commit SHA from the ASF\napproved actions allow-list, with a trailing comment naming the version\nit resolves to. Mirrors the grails-core audit in apache/grails-core#15690.\n\n- actions/checkout -\u003e de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 (was v4, v6)\n- actions/setup-java -\u003e be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 (was v4, v5)\n- gradle/actions/setup-gradle -\u003e 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 (was 017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2, 0723195856401067f7a2779048b490ace7a47d7c # v5.0.2)\n- scacap/action-surefire-report -\u003e 3dacff26879cd2a7f2160d101254032a3707fe6f # v1.12.0 (was 5609ce4db72c09db044803b344a8968fd1f315da # v1.9.1)\n- actions/cache -\u003e 27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 (was v5)\n\nAdd cache-provider: basic to all 3 setup-gradle steps so caching\nstays on the MIT-licensed provider rather than the proprietary enhanced\nprovider introduced in gradle/actions v6 (Gradle commercial Terms of Use).\n\nFirst-party apache/grails-github-actions/* and local ./.github/actions/*\nreferences are intentionally left unchanged.\n\nAssisted-by: claude-code:claude-4.8-opus\n"
    },
    {
      "commit": "f76fda5584aa368b43f1f89053fda3c7af8cd69f",
      "tree": "35f81c7168bcff9ab7822dda4e3cf01aac3a6e16",
      "parents": [
        "2f04b1006e70ecd6c2c43c7890af2deebf549a08",
        "6dd5cca7370e423faca57866080e84f8f4c7ba2a"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 20:07:43 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu May 28 20:07:43 2026 -0400"
      },
      "message": "Merge pull request #511 from apache/migrate-testcontainers-version-to-properties\n\nReference Testcontainers BOM version from gradle.properties in guide prose"
    },
    {
      "commit": "6dd5cca7370e423faca57866080e84f8f4c7ba2a",
      "tree": "35f81c7168bcff9ab7822dda4e3cf01aac3a6e16",
      "parents": [
        "2f04b1006e70ecd6c2c43c7890af2deebf549a08"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 19:18:11 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 19:18:11 2026 -0400"
      },
      "message": "Reference the Testcontainers BOM version from gradle.properties in guide prose\n\nThe grails-rest-library and grails-vite-spa testing chapters showed the\ntestcontainers-bom version inline. The sample apps now keep that version in\ngradle.properties (as testcontainersVersion), so the chapters reference\n\\ and show the gradle.properties entry, keeping the\nversion in one place.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "2f04b1006e70ecd6c2c43c7890af2deebf549a08",
      "tree": "c7d18aed53b7229549faf6a8fb82753115868e3e",
      "parents": [
        "f346914fa14325ee4396a2b502bae26849732cf2",
        "e6573bf5c89ebdedca59bbda78fba185fa8f7eac"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 19:02:23 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu May 28 19:02:23 2026 -0400"
      },
      "message": "Merge pull request #510 from apache/add-comprehensive-tests-v8-guides\n\nComprehensive test coverage for the nine Grails 8 v8 sample-app guides"
    },
    {
      "commit": "e6573bf5c89ebdedca59bbda78fba185fa8f7eac",
      "tree": "c7d18aed53b7229549faf6a8fb82753115868e3e",
      "parents": [
        "de58e3a8e487b82b760eb78ea4a60fe734f05f16"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 17:08:36 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 17:08:36 2026 -0400"
      },
      "message": "Add a Testing chapter to grails-docker-bootbuildimage v8\n\nThe app shipped only the forge-default Geb spec and its test build could not\nresolve Testcontainers. The sample now runs 4 green functional tests over the\nActuator health surface the guide configures.\n\nNew \"Testing the Health Probes\" chapter (registered in conf/guides.yml toc)\nvendoring:\n\n- application-test.yml: testcontainers-bom dependency note + create-drop schema\n  and databasemigration.updateOnStart false (the database-migration plugin\n  otherwise blocks schema creation in the test env)\n- ActuatorHealthFunctionalSpec: /actuator/health, liveness and readiness all\n  UP (readiness against Testcontainers PostgreSQL), and env/metrics stay closed\n  - the same readiness endpoint the Compose HEALTHCHECK polls\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "de58e3a8e487b82b760eb78ea4a60fe734f05f16",
      "tree": "4b3ffe870a31b0908e043bd5e2556be220d982e6",
      "parents": [
        "54bad4f33b3c51f245cd13a72b053277705684fa"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 17:02:09 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 17:02:09 2026 -0400"
      },
      "message": "Make grails-multi-module v8 match its dev-reload prose and add test snippets\n\nThe sample app diverged from the guide (the Dev Reload chapter documents the\ngrails-exploded plugin + grails { plugins { } } block, but the apps used a\nplain dependency), the admin REST surface did not route, and the modules had\nonly the forge-default Geb spec. The sample now runs 10 green tests (6 unit +\n4 functional) and matches the documented setup.\n\nExpanded testingPerModule with vendored snippets:\n\n- shared-core BookSpec / BookServiceSpec (the shared domain + @Service)\n- webapp-admin UrlMappings (the new \"/books\"(resources:\u0027book\u0027) so the\n  RestfulController routes) + AdminBookFunctionalSpec (REST CRUD over HTTP)\n- webapp-customer CatalogFunctionalSpec (the read-only catalog)\n\nThe Dev Reload and Webapps chapters already documented the exploded-plugin +\ngrails.plugins wiring; the sample app now actually implements it, so the\nguide\u0027s primary reload recipe works as written.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "54bad4f33b3c51f245cd13a72b053277705684fa",
      "tree": "f4ffde31a70530dfdbc04d1479687936949a50cc",
      "parents": [
        "2b2587cc5ab990524d35539ee94bcb1d19ff4b3d"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 16:47:56 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 16:47:56 2026 -0400"
      },
      "message": "Fix grails-fields-custom-widgets-and-wrappers v8 rendering bugs + Testing chapter\n\nThree custom-widget/table rendering bugs threw at runtime and the seed\nmany-to-many never persisted. The sample app now renders all custom widgets\nand runs 20 green tests (12 unit + 8 integration/Geb).\n\nRe-vendored snippets and added prose:\n\n- isbn _widget.gsp: doubled the backslashes in the pattern attribute (\\d is an\n  illegal Groovy escape in a GSP attribute, so create/edit threw); bookIsbn\n  chapter gains a note explaining the GSP escaping\n- templates/_fields/_table.gsp: domainClass.javaClass.simpleName (was\n  domainClass.simpleName, which threw on HibernatePersistentEntity) - this now\n  matches the appWideTable chapter\u0027s own prose\n- BootStrap: wrap the seed in Book.withTransaction so the Book\u003c-\u003eTag join\n  persists; bootstrapData chapter explains why\n\nNew \"Testing the Widgets and Wrappers\" chapter (registered in conf/guides.yml\ntoc): BookSpec/AuthorSpec (constraints + associations), LibraryIntegrationSpec\n(m2m + one-to-one), LibraryFunctionalSpec (Geb tests that the custom f:table,\ndisplay widgets, ISBN widget, and association widgets actually render).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "2b2587cc5ab990524d35539ee94bcb1d19ff4b3d",
      "tree": "8c49088e9fadc6a8b7ef8d737f0285284450dc03",
      "parents": [
        "dc82cef583ab5b62f9267c29389bcfb4a3b6ad4b"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 16:21:14 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 16:21:14 2026 -0400"
      },
      "message": "Fix grails-vite-spa v8 SPA serving and add a Testing chapter\n\nThe SPA-serving chain was broken end to end and the backend had no tests.\nThe sample app now runs 15 green tests (7 unit + 8 integration/functional)\nand genuinely serves the SPA same-origin.\n\nRe-vendored snippets and corrected prose:\n\n- index.gson: render the book template with collection: (it was building\n  items as JSON strings) and declare Integer bookCount (was Long -\u003e 500)\n- SpaController: serve index.html from the classpath (forward url: 404s in a\n  rest-api app); spaController chapter rewritten to match, toc title updated\n- new AssetController + /assets/** mapping streams the Vite bundle same-origin\n  (the rest-api profile serves no static resources); urlMappings chapter\n  updated to document both routes\n\nNew \"Testing the API and SPA Serving\" chapter (registered in conf/guides.yml\ntoc) vendoring BookSpec, BookControllerSpec, BookIntegrationSpec, and\nApiFunctionalSpec (JSON envelope, POST 201/422, same-origin no-CORS, and the\nSPA shell + /assets bundle served same-origin - which also exercises the Vite\nbuild + copy integration).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "dc82cef583ab5b62f9267c29389bcfb4a3b6ad4b",
      "tree": "eb3851562b8b72197a3717a42407c0605ebf2592",
      "parents": [
        "7e5337ef9eaa958c3cfcb42c474ae8f6813bc4e6"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:49:24 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:49:24 2026 -0400"
      },
      "message": "Add a Testing chapter to grails-tailwindcss v8\n\nThe sample app\u0027s only test was the forge default (a \u0027Welcome to Grails\u0027 title\nassertion that never matched the actual \u0027Welcome to Grails + Tailwind\u0027 page),\nso it had never run. The app now has a real browser test suite (5 Geb specs).\n\n- new \"Testing the Tailwind Build\" chapter (registered in conf/guides.yml toc)\n  vendoring TailwindFunctionalSpec: asserts the compiled app.css is served,\n  that .card/.btn-primary apply real computed styles from the @apply layer,\n  and that the class-based dark-mode toggle works and persists to localStorage\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "7e5337ef9eaa958c3cfcb42c474ae8f6813bc4e6",
      "tree": "4dad39e4f737764199b2e4d2bc641d298be68672",
      "parents": [
        "e14cf263ab2301b2021d8a88f5187d180985f5e5"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:42:41 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:42:41 2026 -0400"
      },
      "message": "Fix grails-htmx v8 HTMX URLs and add a Testing chapter\n\nThe HTMX interactions never worked: createLink(controller, action) reverse\nmapping is ambiguous for the shared /tasks and /tasks/$id REST routes and fell\nback to /task/create, which no mapping serves, so every hx-post/patch/delete\n404\u0027d. The sample app now drives all five swap types green (5 unit + 3\nintegration + 6 Geb functional).\n\n- re-vendored the view partials to build HTMX URLs with createLink(uri:\n  \u0027/tasks...\u0027) so they match the REST mappings\n- urlMappings chapter: explains why createLink(uri:) is used instead of\n  controller/action reverse mapping for the shared REST routes\n- new \"Testing the HTMX UI\" chapter (registered in conf/guides.yml toc):\n  TaskSpec (constraints), TaskIntegrationSpec (findAllByTitleIlike, ordering,\n  toggle), TaskTrackerFunctionalSpec (Geb tests of every HTMX swap, with\n  withConfirm for delete)\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "e14cf263ab2301b2021d8a88f5187d180985f5e5",
      "tree": "1f44ebd755a3ded17bbec2ea286a71fb774e3657",
      "parents": [
        "171d09a8212cdcbad7b7a145d450f4c4ba9913a0"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:19:32 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:19:32 2026 -0400"
      },
      "message": "Make grails-github-actions-cicd v8 CI stages run real tests\n\nThe CI workflow ran ./gradlew functionalTest, but the guide never showed the\ntask that defines it and the app had no unit or integration tests, so two of\nthe four stages were hollow and the functional stage would fail. The sample\napp now runs 6 green tests across the three stages (3 unit + 2 integration +\n1 Geb functional).\n\nRe-vendored snippets and expanded the testing chapters:\n\n- unitTests: adds the Message domain + MessageSpec (DomainUnitTest)\n- integrationTests: adds MessageIntegrationSpec and explains the\n  integrationTest -\u003e *IntegrationSpec / functionalTest -\u003e *FunctionalSpec split\n- functionalTests: documents the build.gradle wiring that actually defines the\n  functionalTest task (a second Test task over the integration-test source set,\n  filtered to the Geb specs) and shows HomePageFunctionalSpec\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "171d09a8212cdcbad7b7a145d450f4c4ba9913a0",
      "tree": "8ad19cf617f5d7bad90c5b381716c435af69a57a",
      "parents": [
        "24e61b80a549376b4e279d4c63c839d7e8f1cd50"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:07:36 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 15:07:36 2026 -0400"
      },
      "message": "Make grails-rest-library v8 compile and add a tested testing chapter\n\nThe sample app did not compile against Grails 8.0.0-SNAPSHOT and had no\ntests. It now builds and ships 27 green tests (19 unit + 8 integration/\nfunctional). Re-vendored snippets and corrected prose:\n\n- BootStrap snippet: drop the manual Logger field that collides with the\n  injected log property (compile failure); bootstrapData chapter now\n  describes the Author.withTransaction wrapper, not @Transactional\n- BookController snippet: bulkCreate uses the injected MessageSource (the\n  message() taglib is not available in a rest-api controller), so an\n  invalid bulk POST returns the documented 422 instead of a 500\n\nNew \"Testing the API\" chapter (registered in conf/guides.yml toc) with\nvendored snippets:\n\n- application-test.yml: testcontainers PostgreSQL + create-drop schema +\n  databasemigration.updateOnStart false (the database-migration plugin\n  otherwise blocks schema creation in the test env)\n- BookSpec / AuthorSpec: domain constraints and the hasMany mapping\n- BookControllerSpec: pagination clamping\n- BookIntegrationSpec: filtering / paging / relationships (seed-aware)\n- BookFunctionalSpec: the JSON surface over HTTP incl. the all-or-nothing\n  bulkCreate contract\n- documents the testcontainers-bom and grails-testing-support-web deps\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "24e61b80a549376b4e279d4c63c839d7e8f1cd50",
      "tree": "5a0bab976c0f963dd9a14c163a568419d3d4dca6",
      "parents": [
        "f346914fa14325ee4396a2b502bae26849732cf2"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 14:47:59 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 14:47:59 2026 -0400"
      },
      "message": "Make grails-spock-test-tour v8 tests actually compile and pass\n\nEvery Spock layer the guide demonstrates now compiles and passes against\nGrails 8.0.0-SNAPSHOT (16 unit + 4 integration/functional/Geb green). The\nspecs were authored but never executed and several were broken.\n\nSnippets re-vendored from the fixed sample app:\n\n- BookSpec: getFieldError(...), the nullable-vs-blank map-constructor gotcha,\n  added blank + maxSize constraint cases\n- BookServiceSpec: flush seeded books so the GORM finders see them\n- BookControllerSpec: RestfulController.show() via params.id (no-arg action)\n- BookIntegrationSpec: flush before the duplicate save; a @Service save throws\n  ValidationException\n- BookController / UrlMappings / book/index.gsp: render an HTML table at /books\n- BookFunctionalSpec: seed committed data via withNewTransaction\n\nProse corrected where it described code that never ran or made false claims:\n\n- domainUnitTest: getFieldError + the nullable-vs-blank gotcha\n- integrationTest: Testcontainers PostgreSQL (not H2); @Service save throws\n  ValidationException (not hasErrors)\n- functionalTest: new \"page the test drives\" section + committed-seed rationale\n- platform: jacoco executionData uses layout.buildDirectory, not deprecated buildDir\n- whatYouWillBuild: index action also renders an HTML table\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "75d0c573e0e21e45b05bd5710877e3e3387b733b",
      "tree": "3759487a4913bdf3f4cdcefd95c5d1074093ea84",
      "parents": [
        "440560f55ffb2dc0954609bd8c7b781340cd22d5"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 13:39:59 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Thu May 28 13:39:59 2026 -0400"
      },
      "message": "Enhance grails-transactional-events v8 with same-transaction orchestration and full test coverage\n\nAdds a second pattern alongside the AFTER_COMMIT example: synchronous,\nin-transaction cross-domain orchestration using CustomerRequest,\nWorkOrder, and WorkOrderAssignee. A plain @EventListener published from\ninside a @Transactional service cascades two status changes in the same\ntransaction, so any failure rolls all of them back atomically - the\nmirror image of the AFTER_COMMIT side-effect pattern.\n\nNew chapters: sameTransactionProblem, workOrderDomainModel,\nsynchronousOrchestration, and atomicRollbackTest, plus registeringListeners\n(the resources.groovy + TransactionalEventListenerFactory wiring the\nlisteners actually need) and testingThePattern (the unit -\u003e integration\n-\u003e functional -\u003e Geb test pyramid).\n\nCorrects the previously incorrect \"Grails auto-registers grails-app/services\nclasses\" claim in transactionalListener and multipleListeners, and updates\nthe AFTER_COMMIT listeners to use withNewTransaction instead of an\n@Transactional(REQUIRES_NEW) annotation that is not proxied on a\nresources.groovy bean.\n\nvalidateGuides -PvalidationMode\u003dboth passes (94 guides, 0 errors) and\nrenderGuide_grails_transactional_events_8 renders all 23 chapters with\nevery include:: directive resolved. The matching sample app on\ngrails-guides/grails-transactional-events grails8 runs 40 green tests\n(unit, integration, functional, and Geb browser).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "440560f55ffb2dc0954609bd8c7b781340cd22d5",
      "tree": "d0bc7f01adc90a697f913bf5fa96104abaf0047b",
      "parents": [
        "c8a52de62d69a2a4c7188cfc5f59b98e9369fe05"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Wed May 27 10:33:44 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Wed May 27 10:33:44 2026 -0400"
      },
      "message": "Address Copilot review feedback on PR #509\n\nFour factual corrections to the guide narrative. No code-snippet\nchanges; the sample app at grails-guides/grails-transactional-events\nis unaffected.\n\n1. transactionPhases.adoc / transactionalListener.adoc: \"publisher\u0027s\n   GORM session is closed\" overstates the Spring lifecycle. After-commit\n   synchronizations run after commit but BEFORE\n   cleanupAfterCompletion(), so resources may still be bound to the\n   thread. Rephrased to: the original transaction has already\n   concluded, and the listener body cannot piggy-back on a transaction\n   that has finished - hence REQUIRES_NEW. Same fix applied to both\n   chapters.\n\n2. whatYouWillBuild.adoc: \"Two Spock integration specs\" mismatched the\n   actual snippet, which is one @Integration spec\n   (OrderServiceIntegrationSpec) with two test methods. Bullet rewritten\n   to match.\n\n3. asyncDispatch.adoc: SimpleAsyncUncaughtExceptionHandler logs at ERROR\n   (verified from Spring source: logger.error(...)), not WARN. Replaced\n   the inaccurate log-level reference and expanded the operational\n   advice about installing an explicit handler.\n\n./gradlew validateGuides -PvalidationMode\u003dboth still passes (94 guides,\n0 errors). renderGuide_grails_transactional_events_8 re-renders all 17\nchapters with zero warnings; the four corrected paragraphs appear in\nthe per-chapter and single-page HTML.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "c8a52de62d69a2a4c7188cfc5f59b98e9369fe05",
      "tree": "c4048be55e8acc68cfaa5e7b12e40b97a79f7f74",
      "parents": [
        "f346914fa14325ee4396a2b502bae26849732cf2"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Wed May 27 09:44:36 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Wed May 27 09:44:36 2026 -0400"
      },
      "message": "Add grails-transactional-events v8 guide\n\nCross-domain business logic in Grails using Spring application events\nwith @TransactionalEventListener(AFTER_COMMIT). The pattern works\nout-of-the-box with GORM because GORM rides on Spring\u0027s\nPlatformTransactionManager: AFTER_COMMIT listeners fire when (and only\nwhen) the publisher\u0027s transaction commits, and are silently skipped on\nrollback.\n\nSample-app-flavour Grails 8 guide that walks through:\n\n- An OrderService that publishes an OrderPlacedEvent POGO from a\n  @Transactional method via ApplicationEventPublisher.\n- Three independent listeners (CustomerLifetimeValueListener,\n  AuditListener, NotificationListener) wired purely by Spring\n  @TransactionalEventListener annotations on grails-app/services/\n  beans.\n- An @Async + @TransactionalEventListener composition for off-thread\n  dispatch, with @EnableAsync wiring on the Application class.\n- A reference table of all four TransactionPhase values\n  (BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION).\n- A side-by-side contrast with GORM domain-class lifecycle callbacks\n  and Grails\u0027 @Subscriber / @Listener annotations.\n- A Spock @Integration spec that opens its own committed and\n  rolled-back transactions to prove the AFTER_COMMIT contract.\n\nRegistry entry added to conf/guides.yml with the standard v8 sample-app\nshape (sourcePath, tags, sampleRef pointing at\ngrails-guides/grails-transactional-events grails8 branch, full toc).\n\n./gradlew validateGuides -PvalidationMode\u003dboth passes (94 guides, 0\nerrors). ./gradlew renderGuide_grails_transactional_events_8 renders\nall 17 chapters with every include:: directive resolved.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "f346914fa14325ee4396a2b502bae26849732cf2",
      "tree": "c374d24e10d4ec9aee0cfe91b295f41da0cf9336",
      "parents": [
        "b57ff3193b0d499f42337e7d6ed10a38bd0118a2",
        "ae1adf362f8ebc156828ce770780bb869845c657"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "matrei@apache.org",
        "time": "Thu May 14 15:56:46 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu May 14 15:56:46 2026 +0200"
      },
      "message": "Merge pull request #507 from apache/add-spring-security-8.0.0-M1\n\nAdd grails-spring-security-8.0.0-M1 release"
    },
    {
      "commit": "ae1adf362f8ebc156828ce770780bb869845c657",
      "tree": "c374d24e10d4ec9aee0cfe91b295f41da0cf9336",
      "parents": [
        "b57ff3193b0d499f42337e7d6ed10a38bd0118a2"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Sat May 09 10:17:53 2026 +0200"
      },
      "committer": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Sat May 09 10:17:53 2026 +0200"
      },
      "message": "Add grails-spring-security-8.0.0-M1 release\n"
    },
    {
      "commit": "b57ff3193b0d499f42337e7d6ed10a38bd0118a2",
      "tree": "0cba2452ab9c9fbbd3355416a28ac8a33707010b",
      "parents": [
        "b53c07d6a5ef672ce4f2db07ef6a332067881ece"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Thu May 07 18:37:52 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Thu May 07 18:37:52 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nAdd 8.0.0-M1 to Grails Versions list\n"
    },
    {
      "commit": "b53c07d6a5ef672ce4f2db07ef6a332067881ece",
      "tree": "b1e8b2735cbcc01bbb97c0185cfac1911b5fa160",
      "parents": [
        "18daf93d43fa48d0dc1674849ed65cf297cf91bd",
        "681bbc4472689ce2474cf286bc87858c03f87891"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:34:02 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon May 04 08:34:02 2026 -0400"
      },
      "message": "Merge pull request #506 from apache/improve-v8-guides-pre-release\n\nPolish all 9 v8 guides for the imminent Grails 8 release"
    },
    {
      "commit": "681bbc4472689ce2474cf286bc87858c03f87891",
      "tree": "b1e8b2735cbcc01bbb97c0185cfac1911b5fa160",
      "parents": [
        "aa1526d3205d73185c5510fd2a542297d07642b2"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:31:04 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:31:04 2026 -0400"
      },
      "message": "Cross-reference grails-doc Fields plugin chapter from grails-fields-custom-widgets-and-wrappers v8\n\ngrails-doc/src/en/guide/theWebLayer/fields.adoc (and its fields/\nsubdirectory) is the official 8.0.x reference for the Fields plugin\nhigh-level f: taglib API. This guide complements it with worked\nexamples of customising the per-property and per-class templates that\nthe plugin\u0027s FormFieldsTemplateService looks up.\n\nChanges:\n* fieldsPluginAlreadyIncluded.adoc: drop the stale \u00278.0.0-SNAPSHOT\u0027\n  hard-coded version reference (matches the grails-core release\n  pinned by the consumer\u0027s project), name the taglib path\n  \u0027grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy\u0027,\n  and add the cross-reference to grails-doc/src/en/guide/theWebLayer/fields.adoc.\n* templateLookup.adoc: cite the source path for the lookup logic\n  (grails-fields/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy)\n  and document the additional _themes/\u003ctheme\u003e/ lookup level controlled\n  by grails.plugin.fields.theme (THEMES_FOLDER constant in\n  FormFieldsTemplateService).\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "aa1526d3205d73185c5510fd2a542297d07642b2",
      "tree": "29178ca588337bf929b9d206d1c2e58e7805c2b0",
      "parents": [
        "0abf497504a3cd8fa19853de73653ed5d142ed42"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:54 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:54 2026 -0400"
      },
      "message": "Correct grails-vite-spa v8 CORS chapter + cross-reference grails-doc\n\nThe cors.adoc chapter previously claimed there was nothing\nGrails-specific for CORS handling and pointed only at the Spring Boot\nCORS reference. That was wrong: grails-doc/src/en/guide/theWebLayer/cors.adoc\ndocuments Grails 8\u0027s first-class grails.cors.enabled configuration\nblock, which produces a Spring CorsConfiguration mapped to /** by\ndefault and supports per-mapping overrides.\n\nThe chapter now shows the canonical grails.cors block:\n\n    grails:\n        cors:\n            enabled: true\n            allowedOrigins:\n                - \u0027https://app.example.com\u0027\n            ...\n\nwhile keeping the original recommendation that the same-origin layout\nthis guide builds removes the need for CORS entirely - the Grails\nconfig is only relevant if you split the SPA and the API across\norigins.\n\nOther chapter additions:\n* spaController.adoc cites the underlying source for the forward\n  method (grails-controllers/src/main/groovy/grails/web/util/WebUtils.java)\n  and the user-facing reference at\n  grails-doc/src/en/guide/theWebLayer/controllers/forwarding.adoc.\n* urlMappings.adoc points at grails-doc/src/en/guide/theWebLayer/urlMappings.adoc\n  and the restfulMappings sub-chapter.\n* devMode.adoc references grails-doc/src/en/guide/gettingStarted/developmentReloading.adoc\n  for the Spring DevTools story.\n* whatYouWillBuild.adoc and backend.adoc add the matching\n  grails-doc/src/en/guide/REST/ pointers.\n\nEvery substantive chapter ends with a \"Files touched\" bullet list\nmatching the established style.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "0abf497504a3cd8fa19853de73653ed5d142ed42",
      "tree": "dd118cba086304bc367862e0ea30b6b7c2852966",
      "parents": [
        "4cf2039e2d4a61f40c0178e3a54f1c0ce9e5227b"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:34 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:34 2026 -0400"
      },
      "message": "Add Files-touched bullets and grails-doc cross-references to grails-docker-bootbuildimage v8\n\nThe bootBuildImage chapter now explicitly cites the Spring Boot\nGradle plugin coordinate (spring-boot-gradle-plugin:4.0.5) verified\nagainst grails-core 8.0.x dependencies.gradle. The whatYouWillBuild\nchapter notes that the official Apache Grails 8 reference manual at\ngrails-doc/src/en/guide/deployment.adoc covers WAR-file deployment\nto a servlet container, while this guide complements that with the\nmodern OCI-image path enabled by Spring Boot 4.\n\nEach chapter (whatYouWillBuild, dockerOverview, bootBuildImage,\nrunImage, envProfiles, postgresWiring, compose, healthCheck,\njvmContainer, nonRoot, dockerfile, ghcr) closes with the\nestablished \"Files touched\" bullet list so a reader can scan\nexactly which on-disk files each chapter expects them to edit.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "4cf2039e2d4a61f40c0178e3a54f1c0ce9e5227b",
      "tree": "bbb414f4b55003dbaee3eba45b9425d0468146c0",
      "parents": [
        "b8fbed4120fb19366e3b98734d82d6f0b36e9913"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:26 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:26 2026 -0400"
      },
      "message": "Fix grails-rest-library v8 BookController bulk-create bug + refresh JSON views\n\nThe bulk-create handler had a real operator-precedence bug:\n\n    if (!request.JSON instanceof List) { ... }\n\nparses as\n\n    if ((!request.JSON) instanceof List) { ... }\n\nwhich is always false (a falsy value \u0027instanceof List\u0027 is never true),\nso the body-shape guard never fired and a single-object request body\nsilently bound nothing. The fix is the parenthesised form\n\u0027if (!(request.JSON instanceof List))\u0027.\n\nThe handler is also modernised to current Grails 8 idioms: bindData()\ninstead of \u0027new Book(json as Map)\u0027 (so domain-class binding rules\nfire); errors.allErrors instead of errors.fieldErrors (catches\nglobal errors too); message(error: error) for i18n message resolution\nthrough the standard Grails MessageSource; and failOnError: true on\nthe final per-row save so a validation slip cannot reach\nproduction. The success path now respond()s through the index view\ntemplate so the response shape stays consistent with the regular\nlist endpoint.\n\nA new index() override paginates the list with a sensible 25/100\ndefault/cap; a new countResources() override surfaces a bookCount\nin the model so JSON views can render a total. Both honour an\noptional ?author\u003d\u003cid\u003e filter via Book.where { author.id \u003d\u003d authorId }.\n\nOther refreshes:\n* AuthorController.groovy and BootStrap.groovy align with the same\n  patterns.\n* All .gson views (_book, _author, index, show) get a cleaner shape\n  matching the chapter walkthrough.\n* New _errors.gson view: structured 422 error response shape used by\n  the validation chapter.\n* New changelog.groovy under grails-app/migrations/: the Liquibase\n  entry-point the database-migration feature requires; the chapter\n  references it via include::../snippets/...[].\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "b8fbed4120fb19366e3b98734d82d6f0b36e9913",
      "tree": "79f550180ee1850b1ccfdb0e135e98af99df3149",
      "parents": [
        "9f7e442e3a8beb87086ceddf28b2181913a66e1f"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:09 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:30:09 2026 -0400"
      },
      "message": "Refactor grails-htmx v8 TaskController + add OOB-swap created partial\n\nThe TaskController action set is tightened for production hygiene:\nexplicit flush:true on every save and delete (deterministic before\nthe response is rendered), validate-before-save flow with explicit\nerror rendering, field-by-field assignment instead of mass assignment\nthrough params (the original \u0027t.properties \u003d params\u0027 allowed\narbitrary fields to slip through), and a new show(Long id) action.\n\nA new _taskCreated.gsp partial demonstrates the canonical htmx\nout-of-band swap pattern: the response renders the new task into\n#taskList while replacing the form with a fresh empty one, all in a\nsingle round trip - a teaching point the chapter narrative now\ncovers explicitly.\n\nOther view-side cleanups:\n* _taskForm.gsp: g:form + g:textField + g:hasErrors structure with\n  encodeAsHTML on error message output.\n* _task / _taskEdit / _taskRows / index / main: consistent formatting,\n  attribute ordering, and label improvements.\n* UrlMappings.groovy and Task.groovy: minor cosmetic alignment with\n  the chapter walkthrough.\n\nEach chapter ends with the established \"Files touched\" bullet list.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "9f7e442e3a8beb87086ceddf28b2181913a66e1f",
      "tree": "7105416e5e9e5e0d0d7a631e0662c01663503725",
      "parents": [
        "1150b5a0e78ef0a04f74860739b3cd983067c5e5"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:29:51 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:29:51 2026 -0400"
      },
      "message": "Add grails-multi-module v8 dev-reload chapter + source-cited descriptor walkthrough\n\nThe most common day-to-day question for a multi-project Grails build is\n\"how do I save a file in shared-core and have my running webapp\nbootRun pick it up?\". Grails 8 answers this with a first-class plugin\n- org.apache.grails.gradle.grails-exploded - that publishes exploded\nclasses/resources from a Grails Plugin subproject so the consuming\nwebapp\u0027s bootRun can request the exploded variant automatically.\nSource: grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/exploded/GrailsExplodedPlugin.groovy.\n\nChanges:\n* New chapter devReload.adoc walks the supported recipe (apply\n  org.apache.grails.gradle.grails-exploded in shared-core, declare\n  the plugin under the grails { plugins { ... } } block in each\n  webapp), includes a what-reloads vs what-restarts table, and keeps\n  the manual bootRun classpath patch + spring.devtools.restart.additional-paths\n  approach as alternatives in NOTE blocks.\n* runningEach.adoc forward-references the new chapter, documents\n  bootJar deployment, and clarifies that shared-core does not produce\n  a deployable bootJar (GrailsPluginGradlePlugin disables bootJar for\n  the plugin project).\n* sharedCore.adoc expands the plugin descriptor explanation with\n  source citations: grails-core/src/main/groovy/grails/plugins/Plugin.groovy\n  for the lifecycle hooks; grails-core/src/main/groovy/org/grails/core/artefact/DomainClassArtefactHandler.java,\n  grails-domain-class/src/main/groovy/org/grails/plugins/domain/support/DefaultMappingContextFactoryBean.groovy,\n  and grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy\n  for cross-plugin domain auto-discovery.\n* New shared-core/src/main/groovy/example/SharedCoreGrailsPlugin.groovy\n  snippet so sharedCore.adoc can include::../snippets/...[].\n* snippets/build.gradle: collapse the dual content { } filter blocks\n  in the snapshots/staging Maven repos into a single block per repo\n  (separate blocks AND-combine, which silently disables the\n  repository - the intent is OR-combine), drop the redundant\n  plugins.withId(\u0027groovy\u0027) guard around useJUnitPlatform.\n* snippets/settings.gradle: minor cleanup matching the chapter.\n* All chapter prose picks up source-citation NOTE blocks and \"Files\n  touched\" bullets to match the established\n  grails-fields-custom-widgets-and-wrappers v8 style.\n* conf/guides.yml: TOC entry for devReload between runningEach and\n  testingPerModule.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "1150b5a0e78ef0a04f74860739b3cd983067c5e5",
      "tree": "e707563914c03db3ca0711b21ddcd0967443ee3c",
      "parents": [
        "0b9571310bdb9b0e1e0efbf1f7baec9159032099"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:29:16 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:29:16 2026 -0400"
      },
      "message": "Align grails-github-actions-cicd v8 with ContainerGebSpec story\n\nThe functional-test job in the CI workflow now runs Geb specs that\nextend grails.plugin.geb.ContainerGebSpec, so the Selenium-Chrome\nbrowser lives inside a Testcontainers container. The legacy\ngeb-with-webdriver-binaries forge feature - and its companion\nwebdriver-binaries-gradle-plugin - are obsolete in Grails 8 and\nremoved throughout.\n\nChanges:\n* createApp.adoc: drop geb-with-webdriver-binaries from the required\n  forge feature list (postgres + testcontainers are sufficient; the\n  ContainerGebSpec test fixtures already ship in the standard web\n  profile).\n* download.adoc: remove the legacy\n  integrationTestImplementation testFixtures(\"org.apache.grails:grails-geb\")\n  + webdriver-binaries Gradle plugin pair from the chapter narrative.\n* functionalTests.adoc: rewrite the chapter to describe Selenium-in-\n  Testcontainers (no host-side Chrome install needed; first cold run\n  pulls selenium/standalone-chrome ~200 MB; subsequent runs reuse\n  the cached layer).\n* howto.adoc: drop the geb-with-webdriver-binaries feature mention\n  from the prereq summary.\n* The other chapters (badge, branchProtection, ciYml, dependabot,\n  ghcrPush, integrationTests, releaseYml, unitTests, workflowOverview,\n  whatYouWillBuild, requirements, gettingStarted, helpWithGrails)\n  pick up the consistent \"Files touched\" bullet style and minor\n  prose tightening.\n\nVerified against grails-core 8.0.x at\ngrails-geb/src/testFixtures/groovy/grails/plugin/geb/.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "0b9571310bdb9b0e1e0efbf1f7baec9159032099",
      "tree": "5eac91b6113ef312e7f91b757747f6b86f1c3f41",
      "parents": [
        "784043aab23b2bf89cc35cd60ff99d6be9aae5ab"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:56 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:56 2026 -0400"
      },
      "message": "Migrate grails-spock-test-tour v8 functional tests to ContainerGebSpec\n\nIn Grails 8 the canonical (and only fully supported) Geb base class is\ngrails.plugin.geb.ContainerGebSpec from\ntestFixtures(\"org.apache.grails:grails-geb\"). It starts a\nSelenium-Chrome browser inside a Testcontainers container, removing\nthe need for host-side WebDriver binaries entirely. The\ngeb-with-webdriver-binaries forge feature from earlier Grails majors\nis obsolete - keeping it adds a second Chrome driver no test ever\nuses.\n\nChanges:\n* functionalTest.adoc rewrites the chapter narrative around\n  ContainerGebSpec, calling out the mandatory @Integration annotation\n  (the Spock extension throws IllegalArgumentException at runtime\n  without it), the absence of @Rollback (functional tests need\n  committed data), and the Docker daemon prerequisite.\n* New BookFunctionalSpec.groovy snippet under\n  src/integration-test/groovy/example/, vendored so the chapter can\n  include::../snippets/...[] like every other code block in the\n  guide. The spec extends grails.plugin.geb.ContainerGebSpec and\n  carries inline Javadoc explaining why @Integration is mandatory.\n* whatYouWillBuild.adoc updates the spec inventory from \"four spec\n  files exercising four test layers\" to \"five spec files exercising\n  the five test layers\", since the functional layer is now first\n  class.\n* howto.adoc drops the geb-with-webdriver-binaries feature from the\n  required forge feature list.\n* conf/guides.yml: subtitle now mentions ContainerGebSpec; the\n  functionalTest TOC title updates from \"Geb Functional Tests\" to\n  \"ContainerGebSpec Functional Tests\".\n\nVerified against grails-core 8.0.x at\ngrails-geb/src/testFixtures/groovy/grails/plugin/geb/.\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "784043aab23b2bf89cc35cd60ff99d6be9aae5ab",
      "tree": "588e775b741ffc554608ae61877e398010d76550",
      "parents": [
        "8a2067c775516e1acede1a9bcee760efb3bfcb3a"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:26 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:26 2026 -0400"
      },
      "message": "Tighten grails-tailwindcss v8 asset-pipeline narrative + Tailwind 4 build refinements\n\nThe asset-pipeline integration was correct end-to-end but only mentioned\nonce, in a NOTE block at the bottom of gradleWiring.adoc. A reader who\nskimmed could easily miss that the asset pipeline is what bundles,\nfingerprints, gzips, and serves the compiled Tailwind CSS as part of\napplication.css.\n\nNarrative changes:\n* whatYouWillBuild.adoc adds the explicit \"...where the asset pipeline\n  bundles, fingerprints, and gzips that file into the application.css\n  bundle the layout serves\" sentence.\n* gradleWiring.adoc promotes the asset-pipeline manifest from the NOTE\n  sidebar into body prose, naming the *\u003d require app directive and\n  the asset:stylesheet taglib that the next chapter uses.\n* firstPaint.adoc adds a one-line callout pointing at \u003casset:stylesheet\n  src\u003d\"application.css\"/\u003e as the asset-pipeline taglib that resolves\n  the manifest at runtime.\n\nSnippet refinements:\n* build.gradle uses Provider syntax for tailwindBuild and npmInstall\n  dependsOn (more idiomatic than the string form), declares\n  package-lock.json as an extra input, scans grails-app/services,\n  grails-app/taglib, and src/main/groovy in addition to views and\n  controllers, and adds testAndDevelopmentOnly Bootstrap webjars so\n  the dev console pages still render.\n* src/main/css/input.css adds tag::components / end::components markers\n  so componentLayer.adoc can include only the relevant block via\n  include::../snippets/src/main/css/input.css[tag\u003dcomponents,indent\u003d0].\n* application.css manifest collapses to *\u003d require app.\n* main.gsp and index.gsp are tightened to match the chapter walkthrough.\n* package.json bumps to a current Tailwind 4 line.\n\nAcross the chapters, every section that touches a file ends with a\n\"Files touched\" bullet list to match the established\ngrails-fields-custom-widgets-and-wrappers v8 style and a couple of\nchapter-end pointers to the matching grails-doc reference manual\nsections (deployment.adoc et al.).\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "8a2067c775516e1acede1a9bcee760efb3bfcb3a",
      "tree": "4c7d43c3563fe9a18c87826db37c9611afd1a050",
      "parents": [
        "18daf93d43fa48d0dc1674849ed65cf297cf91bd"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:01 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 08:28:01 2026 -0400"
      },
      "message": "Replace prev-snapshot.grails.org with start.grails.org across v8 guides\n\nApache Grails 8 ships in the coming weeks. The prev-snapshot.grails.org\nforge that the v8 guides currently point at will go stale on release.\nSwitch every reference to the canonical link:https://start.grails.org[]\nforge UI per the existing pattern in grails-fields-custom-widgets-and-wrappers\nv8.\n\nThe mechanical changes:\n* createApp.adoc / download.adoc / howto.adoc across grails-tailwindcss,\n  grails-github-actions-cicd, grails-rest-library, grails-htmx,\n  grails-multi-module, grails-fields-custom-widgets-and-wrappers, and\n  grails-docker-bootbuildimage all swap prev-snapshot.grails.org for\n  start.grails.org.\n* The download.adoc curl examples are rewritten as browser-based\n  instructions (\"Open start.grails.org, pick the X profile, name\n  the app Y, set the package to Z, tick features W, download the\n  zip\") because start.grails.org is a browser UI; its REST forge\n  endpoint lives on a separate Cloud Run host that is not part of\n  the user-facing interface.\n* The grails-multi-module generateModules.adoc collapses the three\n  curl commands into a single browser walkthrough plus an unzip\n  + rename block.\n* conf/guides.yml: four \"Download a Grails 8 Snapshot Starter\" TOC\n  titles drop the \"Snapshot\" word now that 8 is shipping; the\n  \"rest_api Starter\" title is normalised to \"Rest API Starter\".\n\nAssisted-by: claude-code:claude-4.6-opus\n"
    },
    {
      "commit": "18daf93d43fa48d0dc1674849ed65cf297cf91bd",
      "tree": "ce27e22c7807a30249e14999c18dcbe7051f217a",
      "parents": [
        "18dfc5120c927ea0e5440879421bdb778cff0266",
        "6fde52319b3ce7d291732d4cff81b8cc55675ffe"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 07:02:38 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon May 04 07:02:38 2026 -0400"
      },
      "message": "Merge pull request #505 from apache/retag-guides-canonical-taxonomy\n\nRetag every guide with a generous canonical taxonomy and reshape categories"
    },
    {
      "commit": "6fde52319b3ce7d291732d4cff81b8cc55675ffe",
      "tree": "ce27e22c7807a30249e14999c18dcbe7051f217a",
      "parents": [
        "11e8c0a7c48de1b715cac529a869cc1c846557e3"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 06:31:31 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 06:31:31 2026 -0400"
      },
      "message": "Drop the inaccurate \u0027~70 canonical tags\u0027 figure from tagCloud Javadoc\n\nPer Copilot review on PR #505: the actual taxonomy ended up at ~290\nunique tags (driven by per-guide enrichment, not by a top-down cap),\nso the docstring\u0027s \u0027~70\u0027 figure is misleading. The taxonomy is\nhand-curated but unbounded - the docstring now just says so.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "11e8c0a7c48de1b715cac529a869cc1c846557e3",
      "tree": "f1de996c90568b6843671c7fa68473a50bc0de3f",
      "parents": [
        "526da09eb9e4c25fbcab0dfe3ff05348a6f2c6e3"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 06:19:59 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Mon May 04 06:19:59 2026 -0400"
      },
      "message": "Retag every guide with a generous canonical taxonomy and reshape categories\n\nTags are the primary browse mechanism on the guides index - clicking a\ntag lands on a curated page listing every guide carrying that tag - so\nit is worth investing in a serious taxonomy. The previous YAML had 195\nunique tags from a decade of ad-hoc tagging, with 73% of them appearing\nin just 1-2 guides (typos like aws-elasticbeanstlak, synonyms like\nrest+rest-api+restful-controller, version labels like grails3..grails8,\nbrowser-specific noise like firefox+chrome, and one-off dead-ends).\nAfter this change the registry has 289 thoughtfully assigned tags from\na curated taxonomy, every guide carries 8-15 generous tags reflecting\nwhat it teaches, and the bottom-of-page category grid is reshaped\naround the tracks Apache Grails 7/8 actually publishes today.\n\n\u003d\u003d conf/guides.yml: hand-curated tag list per guide (93 entries) \u003d\u003d\n\nEvery guide now has an explicit, generous tag set covering web stack,\npersistence, testing, security, devops, frontend, libraries, and\npatterns. Tags were derived from each guide\u0027s intro chapter, title,\nsubtitle, and category so they actually describe the guide content.\n\nTop tags after the retag (by occurrence):\n\n  47  rest-api          21  unit-tests        17  gradle        13  spa\n  32  gorm              20  domain-classes    17  json-views    13  hibernate\n  21  testing           19  controllers       16  geb           12  spring-boot\n  21  unit-tests        18  devops            16  gsp           12  authentication\n                        18  functional-tests  15  integration-tests\n\nDistribution:\n   93 tags in 1 guide         60 tags in 3-5 guides     24 tags in 11+ guides\n   92 tags in 2 guides        20 tags in 6-10 guides\n\nSynonym normalisation, typo fixes, and version-label removal applied:\n  rest, restful-controller       -\u003e rest-api\n  unit-test                      -\u003e unit-tests\n  integration-test               -\u003e integration-tests\n  functional-test                -\u003e functional-tests\n  test                           -\u003e testing\n  mock                           -\u003e mocking\n  json views, json-view          -\u003e json-views\n  compose                        -\u003e docker-compose\n  paketo                         -\u003e buildpacks\n  health                         -\u003e actuator\n  fields                         -\u003e fields-plugin\n  log, logs                      -\u003e logging\n  locale                         -\u003e i18n\n  tvml, tvmljs, tvos             -\u003e apple-tv (canonical)\n  aws-elasticbeanstlak (typo)    -\u003e elasticbeanstalk\n  schwatz (typo)                 -\u003e schwartz\n  rabbitMQ (case)                -\u003e rabbitmq\n  jpq-ql (typo)                  -\u003e jpa-ql\n  spring boot (space)            -\u003e spring-boot\n  vue-profile, react-profile     -\u003e vue, react\n\nDropped entirely:\n  grails3..grails8     - version is implicit in the guide URL\n  firefox, chrome      - covered by the geb tag\n  backend, frontend    - vague (replaced by category placement)\n  framework, language  - vague\n  task, execution      - vague when standalone\n  github, git, google  - too generic\n  java                 - all guides are java, no signal\n  ide                  - vague\n  test                 - merged into testing\n  phantomjs, htmlunit  - dead tech\n  spock-spring         - merged into spock\n  web-profile          - profile choices are not navigational\n  profile, profiles    - kept only where actually meaningful\n\n\u003d\u003d conf/guides.yml: 28 category reassignments \u003d\u003d\n\nThe bottom grid now surfaces the tracks Grails 7/8 actually publishes:\n\n  Security (NEW, 7 guides)\n    grails-basicauth, grails-spring-security-core-plugin-custom-authentication,\n    grails-oauth-google, grails-oauth-twitter,\n    grails-custom-security-tenant-resolver, react-spring-security,\n    grails-test-security\n    Previously buried under \"Advanced Grails\" alongside multi-tenancy\n    and SOAP, even though spring-security-* alone covers ~10 guides.\n\n  Frontend SPA (renamed/merged, 14 guides)\n    Combines the previous Grails + React (6) + Grails + Vue.js (3) +\n    Grails + Angular (1) + Grails + AngularJS (2) + iOS/Android/RIA\n    legacy categories. The integration patterns overlap heavily and\n    none of the individual sub-tracks justified its own column on the\n    index. Mobile-client guides (Android, iOS Objective-C, iOS Swift,\n    tvOS) join here since they\u0027re \"frontend client consuming Grails\".\n\n  Grails + Cloud (renamed/broadened, 3 guides)\n    Was Grails + Google Cloud. Now also includes grails-elasticbeanstalk\n    so all cloud-deployment guides browse together regardless of which\n    cloud provider they target.\n\n  Grails + DevOps (gained 3 guides)\n    grails-as-docker-container, adding-commit-info,\n    grails-docker-external-services moved out of \"Advanced Grails\"\n    where they were thematically miscategorised.\n\n  Web Layer (gained 1 guide)\n    vaadin-grails moved here - it\u0027s a server-rendered rich-UI framework,\n    not a SPA.\n\n\u003d\u003d buildSrc/src/main/groovy/website/model/guides/GuidesPage.groovy \u003d\u003d\n\ncategories map: dropped legacy `android`, `angular`, `angularjs`, `ios`,\n`ria` keys (they had 1-2 guides each and have been folded into Frontend\nSPA). Dropped `react` and `vue` keys (now Frontend SPA). Renamed\n`googlecloud` to `cloud`. Added `security`, `spa`. The map now lists\nexactly the 10 tracks rendered on the index, in the order they appear:\napprentice, advanced, weblayer, devops, gorm, testing, security, spa,\nasync, cloud.\n\nmainContent: regenerated the two-column grid as 5 paired rows:\n  Apprentice         | Advanced Grails\n  Web Layer          | Grails + DevOps\n  GORM               | Grails Testing\n  Security           | Frontend SPA\n  Grails Async       | Grails + Cloud\n\nTag cloud: removed the TAG_CLOUD_LIMIT cap. After retagging the\nregistry to a curated taxonomy, every survivor is worth showing - 289\ntags total, sorted alphabetically for display, with occurrence still\ndriving the tagN CSS class so popular tags render larger. The version-\nlabel filter (~/^grails\\d+$/) is kept defensively so any future YAML\ndrift cannot resurrect grails3..grails8 in the cloud.\n\n\u003d\u003d Verified \u003d\u003d\n\n  ./gradlew :buildSrc:test --tests \u0027website.model.guides.GuidesFetcherSpec\u0027\n    -\u003e all 7 pass\n\n  ./gradlew build validateGuides --no-configuration-cache\n    -\u003e BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors\n\nbuild/dist/guides/index.html section headers (in render order):\n  Grails Apprentice, Advanced Grails, Web Layer, Grails + DevOps,\n  GORM, Grails Testing, Security, Frontend SPA, Grails Async,\n  Grails + Cloud\n\nTag cloud: 289 entries (was capped at 50, before that was 195 noisy\nentries - now 289 curated entries with the long tail providing useful\ndeep-link navigation per guide).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "18dfc5120c927ea0e5440879421bdb778cff0266",
      "tree": "83f041c6712bd39f8afcad9c373e7715ecc2d479",
      "parents": [
        "328cd266e027b231de3fe5c69b17926e9423e6e4",
        "526da09eb9e4c25fbcab0dfe3ff05348a6f2c6e3"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 20:47:12 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 20:47:12 2026 -0400"
      },
      "message": "Merge pull request #504 from apache/fix-guides-index-page\n\nFix guides index: surface every YAML entry, drop legacy categories, curate tag cloud"
    },
    {
      "commit": "526da09eb9e4c25fbcab0dfe3ff05348a6f2c6e3",
      "tree": "83f041c6712bd39f8afcad9c373e7715ecc2d479",
      "parents": [
        "328cd266e027b231de3fe5c69b17926e9423e6e4"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 20:42:29 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 20:42:29 2026 -0400"
      },
      "message": "Fix guides index: surface every YAML entry, drop legacy categories, curate tag cloud\n\nFive recently-added Grails 8 guides (multi-module, rest-library, github-actions-cicd)\nplus three older entries (grails-as-docker-container, grails-mock-basics v4,\ncreating-your-first-grails-app v6) were silently absent from\nhttps://grails.apache.org/guides/ even though their per-guide HTML was\nbuilt and uploaded correctly. Three independent bugs combined to make\nthis happen, and the bottom-of-page category grid was simultaneously\noverweight on dead/legacy stacks while missing the modern Web Layer\nsection that every flagship Grails 8 guide lives in.\n\n\u003d\u003d conf/guides.yml: 5 sampleRef.repo collisions \u003d\u003d\n\nThe fetcher used to group guide-versions by sampleRef.repo, so any two\nYAML entries that happened to point at the same external repo merged\ninto a single rendered guide and clobbered each other. Five YAML entries\nwere pointing at the wrong repo (the new Grails 8 sample repo instead of\ntheir own legacy repo):\n\n  - grails-as-docker-container v3, v4 -\u003e grails-as-docker-container\n    (was grails-docker-bootbuildimage; collided with grails-docker-bootbuildimage v8)\n  - grails-multi-project-build v4 -\u003e grails-multi-project-build\n    (was grails-multi-module; collided with grails-multi-module v8)\n  - rest-hibernate v3, v4 -\u003e rest-hibernate\n    (was grails-rest-library; collided with grails-rest-library v8)\n  - grails-on-github-actions v4 -\u003e grails-on-github-actions\n    (was grails-github-actions-cicd; collided with grails-github-actions-cicd v8)\n\nThese look like the residue of repo renames that were applied in the\nsample registry but never propagated to the new entries that needed\ndistinct slugs.\n\n\u003d\u003d GuidesFetcher: identify guides by YAML name, not sampleRef.repo \u003d\u003d\n\nEven with the YAML cleaned up, the fetcher was structurally fragile.\nIt also had two latent bugs:\n\n1. BRANCH_TO_MAJOR map hardcoded master-\u003e4 and was missing grails7,\n   grails8. creating-your-first-grails-app/v6 (whose sampleRef.branch\n   is master) was silently mapped to major version 4, overwriting the\n   real v4 entry in grailsMayorVersionTags. The v6 build was reachable\n   only via direct URL.\n2. grails-mock-basics had v3 and v4 both with branch\u003dmaster under one\n   guide. The slug+branch grouping reduced to {master}, so size\u003d\u003d1\n   triggered toSingleGuide and silently returned the first match (v3).\n\nRefactor parseGuides to walk one Guide per top-level YAML entry:\n\n  - 1 versions: child  -\u003e SingleGuide\n  - 2+ versions: child -\u003e GrailsVersionedGuide, with one rendered link\n                          per version. The YAML version key is used\n                          DIRECTLY as the integer major version, which\n                          eliminates BRANCH_TO_MAJOR entirely.\n\nsampleRef.repo is now treated only as the \"Get the Code\" target on the\nrendered chrome - it is never used to identify or group guides. Two\nYAML entries that happen to point at the same external repo are still\nrendered as two separate guides.\n\nAdds GuidesFetcherSpec with 7 unit tests pinning the regression cases:\n\n  - SingleGuide for 1-version entries\n  - GrailsVersionedGuide for 2+-version entries with the YAML version\n    key preserved as the int major version (covers the master-\u003e4\n    overwrite + missing grails7/grails8)\n  - Two YAML entries sharing sampleRef.repo render as two guides\n    (covers all 4 collision pairs above)\n  - Two YAML versions sharing the same branch are both kept when they\n    belong to the same guide (covers grails-mock-basics)\n  - Future-dated guides are filtered when skipFuture is true\n  - Empty versions: blocks are silently skipped\n  - Sort order is publication-date-descending\n\n\u003d\u003d GuidesPage: trim dead categories, add Web Layer, curate tag cloud \u003d\u003d\n\ncategories map: dropped Grails + Android, Grails + Angular,\nGrails + AngularJS, Grails + iOS, Grails + RIA. Each had 1-2 stale\nguides and was eating equal real estate to Advanced Grails (22 guides).\nTheir guides remain built and reachable via tags, search, and direct\nURL; only the index-page section is gone.\n\nAdded Web Layer (5 guides today: htmx, tailwindcss, vite-spa,\nfields-custom-widgets-and-wrappers, rest-library). Without this\nsection, rest-library was the only one of those guides that didn\u0027t\neven fit in the top-8 Latest Guides sidebar, so it was completely\ninvisible from the index.\n\nReshuffled the two-column grid so the modern stacks (Apprentice,\nAdvanced, Web Layer, DevOps) sit above the older ones (GORM, Testing,\nVue, React, Google Cloud). DevOps in particular was previously buried\nunder the legacy iOS / Android / RIA cluster despite having more guides\nthan any of them.\n\ntagCloud: filters out version-label tags (grails3..grails8) - they\nartificially dominated the cloud by occurrence count without adding\nnavigational value (the version is implicit in the guide URL anyway).\nCaps the visible set to TAG_CLOUD_LIMIT\u003d50 most-used tags before\nre-sorting alphabetically for display, so the long tail of one-shot\nlegacy tags (apple-tv, tvml, aws-elasticbeanstlak, ...) no longer\nclutters the sidebar.\n\n\u003d\u003d Verified \u003d\u003d\n\n  ./gradlew :buildSrc:test --tests \u0027website.model.guides.GuidesFetcherSpec\u0027\n    -\u003e all 7 pass\n\n  ./gradlew build validateGuides\n    -\u003e BUILD SUCCESSFUL, validateGuides 93 guide(s) parsed, 0 errors\n\nLocal build/dist/guides/index.html before-vs-after:\n  - multi-module      0 -\u003e 2 hits\n  - rest-library      0 -\u003e 2 hits\n  - github-actions-cicd                 0 -\u003e 2 hits\n  - grails-as-docker-container          0 -\u003e 2 hits\n  - grails-mock-basics  1 (v3 only) -\u003e 2 (v3 + v4)\n  - creating-your-first-grails-app  2 (v3,v4) -\u003e 3 (v3,v4,v6)\n  - rest-hibernate / grails-on-github-actions / grails-multi-project-build:\n    each one stops being mis-rendered as the wrong v8 guide\n\nLatest Guides sidebar now contains all 8 new Grails 8 guides\n(was 5 of 8: docker-bootbuildimage, htmx, spock-test-tour, tailwindcss,\nvite-spa). Tag cloud is now 50 entries (was 200+).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "328cd266e027b231de3fe5c69b17926e9423e6e4",
      "tree": "4654c1275bfafc6b3c17d7c6329a6eabefef4471",
      "parents": [
        "584dbff3e1f92a3e01fa5dceeedfdaaef97d17c7",
        "85293a7e45410f78b7ca49226b56fc88725ee844"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:59:13 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 19:59:13 2026 -0400"
      },
      "message": "Merge pull request #503 from apache/remove-parity-migration-scaffolding\n\nRemove vestigial parity / structural-diff migration scaffolding"
    },
    {
      "commit": "85293a7e45410f78b7ca49226b56fc88725ee844",
      "tree": "4654c1275bfafc6b3c17d7c6329a6eabefef4471",
      "parents": [
        "584dbff3e1f92a3e01fa5dceeedfdaaef97d17c7"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:52:36 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:52:36 2026 -0400"
      },
      "message": "Remove vestigial parity / structural-diff migration scaffolding\n\nParityCheckGuideTask, StructuralDiffGuidesTask and their AdHocFixtureDiff\nhelper were one-shot migration scaffolding: they compared HTML rendered\nby our local renderer against vendored snapshots of the legacy\nguides.grails.org output to verify the new pipeline reproduced the same\nshape. With the migration complete and the guides themselves\nsubstantially rewritten on grails.apache.org, structural parity with the\nlegacy site is no longer the design goal - \"matches the old HTML\" is now\nnoise instead of signal.\n\nConcrete signals that this code has finished its job:\n\n- Only one parity-baseline snapshot was ever vendored\n  (creating-your-first-grails-app-v6/index.html, ~2900 lines), so the\n  parity check could only run for a single guide out of 93.\n- parityCheckAllGuides was registered but NEVER added to verifyAllGuides\n  (or any other aggregate). It only ran when invoked by hand.\n- StructuralDiffGuidesTask.groovy carried a TODO (\"Integrate captured\n  production baseline snapshots from https://guides.grails.org/\u003cguide\u003e/\n  \u003cversion\u003e/ once they are committed.\") - the additional baselines that\n  would have made it useful were never landed.\n\nThis change deletes:\n- buildSrc/src/main/groovy/website/qa/AdHocFixtureDiff.groovy\n- buildSrc/src/main/groovy/website/gradle/tasks/ParityCheckGuideTask.groovy\n- buildSrc/src/main/groovy/website/gradle/tasks/StructuralDiffGuidesTask.groovy\n- buildSrc/src/test/groovy/website/gradle/tasks/StructuralDiffGuidesTaskSpec.groovy\n- buildSrc/src/test/resources/parity-baseline/creating-your-first-grails-app-v6/index.html\n\nAnd updates the wire-up:\n- RenderGuidesPlugin: drop PARITY_AGGREGATE_TASK / PARITY_BASELINE_ROOT\n  constants, the per-guide parityCheckGuide_* registration loop, the\n  parityTaskNames field on Wiring, and the parityCheckAllGuides aggregate\n  registration. Render-only configuration remains intact.\n- GrailsWebsitePlugin: drop the StructuralDiffGuidesTask import,\n  registration, and verifyAllGuides dependency.\n- AcceptanceReportTask: drop the structuralReportFile input, the\n  StructuralDiffGuidesTask dependsOn wiring, and the structuralDiffGuides\n  column from build/reports/acceptance.csv (header, row, AcceptanceRow,\n  parsing, verdict merging, and detail summarization). The remaining\n  gates (asciidoctorWarningGate, crawlBuiltGuides, cspScan) are unchanged.\n- AcceptanceReportTaskSpec: drop the per-test structural-diff CSV writes\n  and adjust the expected acceptance.csv header / row strings.\n- README: update the \"verifyAllGuides\" description so it no longer claims\n  to run a structural diff step.\n\nKept on purpose:\n- GenerateRedirectStubsTask and GenerateRedirectsManifestTask remain\n  untouched. They are NOT migration scaffolding - they generate the\n  meta-refresh stubs deployed to the legacy guides.grails.org gh-pages\n  branch and the _redirects.json manifest published with the main site,\n  both of which are still load-bearing for legacy URL traffic.\n\nVerified:\n- ./gradlew :buildSrc:test --tests AcceptanceReportTaskSpec -\u003e all 4 pass\n- ./gradlew build validateGuides -x :buildSrc:test -\u003e BUILD SUCCESSFUL,\n  validateGuides 93 guide(s) parsed, 0 errors\n- ./gradlew help --task verifyAllGuides -\u003e task graph still resolves\n- The 5 unrelated :buildSrc:test failures in RecordCompanionReleaseTaskSpec\n  and ValidateGuidesTaskSpec are pre-existing on master (584dbff3e1) and\n  are not caused by this change.\n\nNet diff: 10 files changed, +10 / -3763 lines.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "584dbff3e1f92a3e01fa5dceeedfdaaef97d17c7",
      "tree": "6af7a6785b15d71e19c47acb8099c74a5876f145",
      "parents": [
        "1606bad448f7544133623b7c10a1b269204d2703",
        "63786e5370061005ef11f2e80e401fcd9d56b1bd"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:44:59 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 19:44:59 2026 -0400"
      },
      "message": "Merge pull request #502 from apache/fix-redirect-links\n\nReplace legacy *.grails.org URLs with their canonical grails.apache.org targets"
    },
    {
      "commit": "63786e5370061005ef11f2e80e401fcd9d56b1bd",
      "tree": "6af7a6785b15d71e19c47acb8099c74a5876f145",
      "parents": [
        "1606bad448f7544133623b7c10a1b269204d2703"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:30:49 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:30:49 2026 -0400"
      },
      "message": "Replace legacy *.grails.org URLs with their canonical grails.apache.org targets\n\nThe Plugins link in the site header pointed at https://plugins.grails.org,\nwhich is kept alive only as a meta-refresh redirect back to\nhttps://grails.apache.org/plugins.html. Many other internal links in the\nguides, blog posts, and shared chrome had the same shape - they targeted a\nlegacy *.grails.org host that exists today only to bounce visitors to the\ncanonical grails.apache.org URL. Each of those redirects costs the user\nan extra hop, hurts SEO, and breaks if any of the legacy hosts ever go\naway.\n\nReplace each redirect-bound URL with its canonical destination so internal\nlinks resolve directly:\n\n- plugins.grails.org -\u003e grails.apache.org/plugins.html (paths under\n  /plugin/\u003cvendor\u003e/\u003cname\u003e are 404, so they collapse to the landing page)\n- guides.grails.org\u003cpath\u003e -\u003e grails.apache.org/guides\u003cpath\u003e (path-preserving)\n- docs.grails.org\u003cpath\u003e -\u003e grails.apache.org/docs\u003cpath\u003e (path-preserving)\n- views.grails.org\u003cpath\u003e -\u003e grails.apache.org/docs/latest/guide/theWebLayer.html#gson\n- async.grails.org\u003cpath\u003e -\u003e grails.apache.org/docs/latest/guide/async.html\n- testing.grails.org\u003cpath\u003e -\u003e grails.apache.org/docs/latest/guide/testing.html\n- gsp.grails.org\u003cpath\u003e -\u003e grails.apache.org/docs/latest/guide/theWebLayer.html#gsp\n  (the four subdomains above all redirect to the same fixed anchor on the\n  canonical site - the legacy path component is dropped at redirect time\n  anyway, so we point straight at the anchor)\n- grails.org/\u003cX\u003e.html -\u003e grails.apache.org/\u003cX\u003e.html\n- grails.org/learn.html -\u003e grails.apache.org/learning.html (page renamed)\n- grails.org/plugin/\u003cX\u003e -\u003e grails.apache.org/plugins.html (legacy 404)\n- grails.org/blog\u003cpath\u003e -\u003e grails.apache.org/blog\u003cpath\u003e\n- grails.org/ root and bare grails.org -\u003e grails.apache.org/\n\nIntentionally NOT touched:\n\n- gorm.grails.org/* - some paths (notably /latest/neo4j/manual/...) have\n  no canonical replacement on grails.apache.org because GORM for Neo4j\n  has not been updated for Grails 7/8. The gorm.grails.org URL is still\n  the current canonical destination for those readers.\n- start.grails.org, slack.grails.org, repo.grails.org,\n  prev-snapshot.grails.org - these are canonical hosts that serve content\n  directly (the Forge, the Slack signup, the JFrog Maven repo, and the\n  Grails 8 snapshot Forge).\n- grails.org/buildstatus.html - redirects to a GitHub README, not back\n  to grails.apache.org, so it falls outside the redirect-back-to-here\n  scope of this change.\n- buildSrc/src/test/resources/parity-baseline/ - golden HTML snapshot of\n  the legacy site, used by ParityCheckGuideTask as a regression-test\n  fixture; rewriting it would corrupt the source-of-truth the renderer\n  is diffed against.\n- buildSrc/src/main/groovy/website/qa/AdHocFixtureDiff.groovy,\n  buildSrc/src/main/groovy/website/gradle/tasks/Generate{Redirects,\n  RedirectStubs}{Manifest,}Task.groovy, ParityCheckGuideTask.groovy, and\n  StructuralDiffGuidesTask.groovy - these intentionally reference\n  guides.grails.org as the LEGACY_BASE for the redirect-stub generator\n  and parity baseline.\n- README.md and buildSrc/VENDOR.md - both document the migration itself,\n  so the legacy URLs need to remain readable.\n- minutes/20210321-tab.md - frozen historical record from the TAB.\n\nVerified locally with `./gradlew validateGuides -PvalidationMode\u003dboth`\n(93 guides parsed, 0 errors) and `./gradlew build` (renderSite succeeds;\nthe rendered Plugins link in build/dist/index.html is now\nhttps://grails.apache.org/plugins.html as expected).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "1606bad448f7544133623b7c10a1b269204d2703",
      "tree": "1e87327e1332fbae320a335bdbb3d93c67fc3f72",
      "parents": [
        "e698300f4a22f9ffbb9aaa0021daabd1b6614541",
        "97acadbbcbd1cff7adf3177b401d2ed9ea43fdf2"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:08:49 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 19:08:49 2026 -0400"
      },
      "message": "Merge pull request #501 from apache/surface-orphan-companion-plugins-on-downloads-page\n\nSurface companion plugins on downloads page when target Grails major has not yet shipped"
    },
    {
      "commit": "97acadbbcbd1cff7adf3177b401d2ed9ea43fdf2",
      "tree": "1e87327e1332fbae320a335bdbb3d93c67fc3f72",
      "parents": [
        "e698300f4a22f9ffbb9aaa0021daabd1b6614541"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:07:11 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:07:11 2026 -0400"
      },
      "message": "Surface companion plugins on downloads page when target Grails major has not yet shipped\n\nApache-released companion plugins (e.g. grails-publish 1.0.0-M1 under\ncompanionArtifacts[\u00278\u0027]) used to be invisible on download.html until at\nleast one matching core release landed in coreReleases, because the page\niterates majors via activeMinorLines / latestPreReleasePerMajor, both of\nwhich read only coreReleases.\n\nAdd a new \u0027Plugins for upcoming Apache Grails releases\u0027 section between\nthe \u0027Pre-release (Apache-released)\u0027 grid and \u0027Older Versions\u0027. The section\nrenders one card per major present in companionArtifacts but absent from\ncoreReleases (stable or pre-release). The card lists each companion\u0027s\nSource / SHA512 / ASC verification links plus a release-notes link, and\nomits the core source / binary / wrapper block that doesn\u0027t yet exist.\n\nThe section is driven by a new SiteMap.orphanCompanionMajors helper that\nreturns those orphan majors in descending order, skipping empty companion\nlists. Once any release for an orphan major (including a milestone or RC)\nis appended to coreReleases, the major drops out of the helper and the\nexisting per-major card automatically picks up its companions through\ncompanionArtifactsFor - no YAML edits needed.\n\nVerified by 6 new SiteMapSpec cases covering empty, all-covered,\npre-release-covered, single orphan, multiple orphans (descending order),\nand empty-companion-list scenarios. Verified by full build that download.html\nrenders the new section with the existing companionArtifacts[\u00278\u0027] entry.\n\nAssisted-by: claude-code:claude-4-opus\n"
    },
    {
      "commit": "e698300f4a22f9ffbb9aaa0021daabd1b6614541",
      "tree": "a01d1bf99b2bae8b226d8cc291955ea92b24f594",
      "parents": [
        "b5c0c07d2a089b66f00d3335ce7dd49c6e0bfe5e",
        "c2dbac8a4f3d81907c9bf02091b3b353594f7f12"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 19:00:07 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 19:00:07 2026 -0400"
      },
      "message": "Merge pull request #500 from apache/add-grails-vite-spa-v8\n\nAdd grails-vite-spa v8 guide"
    },
    {
      "commit": "c2dbac8a4f3d81907c9bf02091b3b353594f7f12",
      "tree": "0154ad6e5f5d82568dec4a3cadd8936e04ab33fb",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 18:53:41 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 18:53:41 2026 -0400"
      },
      "message": "Add grails-vite-spa v8 guide\n\nTwo-tier integration guide: Grails 8 rest_api JSON backend plus a Vite/React SPA frontend in a single Gradle multi-project, packaged into one bootJar with the SPA served from the same origin as the API. Replaces the long-broken Webpack-era React/Vue profiles.\n\nGreenfield - new grails-guides/grails-vite-spa repo created public on the grails8 default branch.\n\nThe 14 chapters cover the multi-project layout, the JSON backend (Book + RestfulController + JSON views), the SpaController forwarder for client-side routes, the Vite/React frontend, vite.config.js with /api proxy, the dev mode (two servers behind one origin), the prod build (one bootJar), and why CORS never appears.\n\nVerified locally: validateGuides -PvalidationMode\u003dboth returns 86 guides 0 errors; renderGuide_grails_vite_spa_8 --rerun-tasks --no-configuration-cache renders all 14 chapter HTML pages, no Unresolved directive errors.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "b5c0c07d2a089b66f00d3335ce7dd49c6e0bfe5e",
      "tree": "292d984435c67b99f39b7e544fa98fd49d988d1a",
      "parents": [
        "71523d0078098c260c8723de87a80b544fc8513b",
        "8c86731574649c4aa8e2c6f6bdce240514a67654"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 18:48:38 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 18:48:38 2026 -0400"
      },
      "message": "Merge pull request #499 from apache/add-grails-spock-test-tour-v8\n\nAdd grails-spock-test-tour v8 guide"
    },
    {
      "commit": "8c86731574649c4aa8e2c6f6bdce240514a67654",
      "tree": "33aae37be909d9acc04601ed620274506c1b6837",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:15:02 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:15:02 2026 -0400"
      },
      "message": "Add grails-spock-test-tour v8 guide\n\nWorking examples of every Spock test layer Grails 8 supports.\nGreenfield - new grails-guides/grails-spock-test-tour repo created public\non the grails8 default branch.\n\nThe 13 chapters demonstrate one canonical example per layer:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Five Test Layers (the speed/cost/coverage matrix)\n- DomainUnitTest for Constraints (BookSpec with @Unroll where: table\n  for pageCount)\n- ServiceUnitTest + DataTest for Queries (BookServiceSpec, in-memory\n  GORM, dynamic-finder testing)\n- ControllerUnitTest for Routing (BookControllerSpec, response status +\n  request.json shape)\n- @Integration With @Rollback (BookIntegrationSpec, real Spring\n  context, real datasource, transaction-isolated specs)\n- Geb Functional Tests (Testcontainers Postgres, browser session reuse,\n  cross-references the CI/CD guide for staging)\n- Parameterised where: Data Tables (block discipline, mock-call-count\n  assertions)\n- useJUnitPlatform and JaCoCo Coverage (the wiring most teams get wrong)\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_spock_test_tour_8 --rerun-tasks --no-configuration-cache\n    Renders all 13 chapter HTML pages, no Unresolved directive errors.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "71523d0078098c260c8723de87a80b544fc8513b",
      "tree": "388f270ab4f0563aed6348171e5a293019101346",
      "parents": [
        "f6b8f76f73dc3ca9224a9b10798373d5bdfd58b7",
        "9a44ae9eeb401a9983bd5feaa3c3f6f8cb173e62"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:11:47 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 16:11:47 2026 -0400"
      },
      "message": "Merge pull request #498 from apache/add-grails-multi-module-v8\n\nAdd grails-multi-module v8 guide (rename of grails-multi-project-build)"
    },
    {
      "commit": "9a44ae9eeb401a9983bd5feaa3c3f6f8cb173e62",
      "tree": "a885051cb96e6d52ef93c1765ed70f771464c7c5",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:09:54 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:09:54 2026 -0400"
      },
      "message": "Add grails-multi-module v8 guide\n\nReal-world Grails 8 multi-project layout: one shared-core Grails Plugin\n(domain model + GORM data services), two web apps (customer + admin)\nthat depend on it via implementation project(\u0027:shared-core\u0027) and build\nto independent bootJars sharing the same data model.\n\nCompanion conf/guides.yml change repoints the existing v4 entry from\ngrails-guides/grails-multi-project-build to the freshly-renamed\ngrails-guides/grails-multi-module. The original master branch (and\nforensic/grails4 etc.) is preserved on the renamed repo.\n\nThe 12 chapters cover:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Generate the Three Module Starters (web_plugin + 2 web)\n- Root settings.gradle and build.gradle (subprojects { repositories,\n  useJUnitPlatform } pattern)\n- The shared-core Plugin (Book domain + @Service(Book) BookService data\n  service)\n- The Two Webapps Consume shared-core (single-line dependency, package\n  separation customer.* / admin.* / example.*)\n- Running and Packaging Each Webapp (independent bootRun + bootJar)\n- Per-Module Testing (each module owns its own test suite)\n- Extracting a Plugin From a Monolith (six-step migration recipe)\n- Failure Modes (circular deps, GORM mapping conflicts, schema\n  migration coordination)\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_multi_module_8 --rerun-tasks --no-configuration-cache\n    Renders all 12 chapter HTML pages, no Unresolved directive errors.\n\nThe mode\u003dboth pass also confirms the renamed repo + branch resolve.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "f6b8f76f73dc3ca9224a9b10798373d5bdfd58b7",
      "tree": "8995171f80739292c068335cc2877987c37d879e",
      "parents": [
        "cba360aa9425e2a53b27bfbf00d76a8e9964d400",
        "d40bf5cc77d6a946faf5f33de8a2cd1bfaa9ea8e"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun May 03 16:08:54 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 16:08:54 2026 -0400"
      },
      "message": "Merge pull request #491 from apache/add-companion-major-bootstrap-and-grails-publish-1.0.0-M1\n\nBootstrap recordCompanionRelease for new majors and add Grails 8 grails-publish 1.0.0-M1"
    },
    {
      "commit": "cba360aa9425e2a53b27bfbf00d76a8e9964d400",
      "tree": "e9a488ab99efd46b934168f35e626f1556a4dc4f",
      "parents": [
        "d7cd9d7fb6917b0f43e75a7ba6d798f15c4ce258",
        "cebdcadaaf69553273c0ec50fe7ae602d7284b4e"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 16:02:02 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 16:02:02 2026 -0400"
      },
      "message": "Merge pull request #497 from apache/add-grails-htmx-v8\n\nAdd grails-htmx v8 guide"
    },
    {
      "commit": "cebdcadaaf69553273c0ec50fe7ae602d7284b4e",
      "tree": "38e68b9126efb3ca993eb59bd5a002c5880f8854",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:47:13 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:47:13 2026 -0400"
      },
      "message": "Add grails-htmx v8 guide\n\nWorked example for HTMX-driven interactive UI on a Grails 8 GSP backend.\nA small task tracker with server-rendered initial page, then HTMX-driven\ninline editing, live search, toggle, and optimistic delete with confirm.\n\nSample-app flavour. Greenfield (no upstream rename) - the new\ngrails-guides/grails-htmx repo was created public on the grails8\ndefault branch.\n\nThe 14 chapters walk through:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Creating the Application\n- The Task Domain Class\n- Adding HTMX to the Layout (script tag with SRI hash)\n- URL Mappings (explicit method per route)\n- The TaskController (7 actions, 6 of which return GSP partials)\n- The Initial Page (server-rendered first paint)\n- The Row Partial (one template, four call sites)\n- Inline Edit With Validation (422 returns the same edit fragment with\n  field errors rendered inline)\n- Live Search (hx-trigger\u003d\"keyup changed delay:300ms\")\n- CSRF With HTMX (htmx:configRequest hook reads token from a meta tag)\n- HTMX vs an SPA (when each fits, cross-references the upcoming\n  grails-vite-spa guide)\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_htmx_8 --rerun-tasks --no-configuration-cache\n    Renders all 14 chapter HTML pages, no Unresolved directive errors.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "d7cd9d7fb6917b0f43e75a7ba6d798f15c4ce258",
      "tree": "493986aaef709004a3d630e7ed2f53c94c49db6c",
      "parents": [
        "3ce7cbd928a4d5ac05a2a93ef4445679892c8acf",
        "1db2c26401f187f461b55826b8a91fae144bbd34"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:42:35 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 15:42:35 2026 -0400"
      },
      "message": "Merge pull request #496 from apache/add-grails-rest-library-v8\n\nAdd grails-rest-library v8 guide (rename of rest-hibernate)"
    },
    {
      "commit": "1db2c26401f187f461b55826b8a91fae144bbd34",
      "tree": "f48c067f014b2be9bb97459100f72eba242e8e4e",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:41:09 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:41:09 2026 -0400"
      },
      "message": "Add grails-rest-library v8 guide\n\nWorked example for a Book + Author REST API on Grails 8: RestfulController-\nbased CRUD, JSON views (.gson) with HAL-style _links, structured 422\nvalidation responses with stable error codes, bounded pagination\n(max hard-capped at 100), URL-prefix versioning under /v1/, and an\nall-or-nothing bulk create endpoint.\n\nCompanion conf/guides.yml change repoints the existing v3 and v4 entries\nfrom grails-guides/rest-hibernate to grails-guides/grails-rest-library\n(rename of the upstream repo). The old grails3, grails4, grails-4, master\nbranches are preserved on the renamed repo so the historical guides\nkeep resolving. The grails-guides/rest-mongodb repo is left alone (it\ncovers a different stack).\n\nThe 17 chapters cover:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Creating the Application (download via the rest_api forge type)\n- Domain Model (Book + Author with realistic constraints, lazy loading\n  to head off N+1, bootstrap sample data)\n- URL Mappings and v1 Versioning (resources mapping + nested collection\n  routes for bulk create)\n- RestfulController with a Hard Page Cap (listAllResources override)\n- JSON Views (.gson partial + index + show pattern)\n- Structured 422 Validation Responses (stable field/code/message shape)\n- Pagination, Sorting, Filtering (max/offset/sort/order)\n- All-or-Nothing Bulk Create (transactionStatus.setRollbackOnly)\n- URL-Prefix vs Content-Negotiation Versioning\n- Running the Application (curl smoke tests for every endpoint)\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_rest_library_8 --rerun-tasks --no-configuration-cache\n    Renders all 17 chapter HTML pages, no Unresolved directive errors.\n\nThe mode\u003dboth pass also confirms grails-guides/grails-rest-library\nexists on grails3, grails4, and grails8 branches - i.e. the upstream\nrename and the new branch are intact.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "3ce7cbd928a4d5ac05a2a93ef4445679892c8acf",
      "tree": "c464dcdc063220823e42b962a51faeb153cf9534",
      "parents": [
        "527a6271639fe73852c2e7d057e07360c6524cbd",
        "b96505759092033145dafa5e425b653f4e45480e"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:37:00 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 15:37:00 2026 -0400"
      },
      "message": "Merge pull request #494 from apache/add-grails-docker-bootbuildimage-v8\n\nAdd grails-docker-bootbuildimage v8 guide (rename of grails-as-docker-container)"
    },
    {
      "commit": "527a6271639fe73852c2e7d057e07360c6524cbd",
      "tree": "e4f497119a850ccc52e66b7ead88e69f3a92196e",
      "parents": [
        "23103ea616c06a2dffa388c6c46296c1ea680b62",
        "94b3d4f3790297dedaf72fc7d4eab2de2ac69b72"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:36:36 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 15:36:36 2026 -0400"
      },
      "message": "Merge pull request #495 from apache/add-grails-github-actions-cicd-v8\n\nAdd grails-github-actions-cicd v8 guide (rename of grails-on-github-actions)"
    },
    {
      "commit": "94b3d4f3790297dedaf72fc7d4eab2de2ac69b72",
      "tree": "2659d2a372e892db123764d7d0e26b7bfa4072ed",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:34:13 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:34:13 2026 -0400"
      },
      "message": "Add grails-github-actions-cicd v8 guide\n\nModern CI/CD pipeline for a Grails 8 application. Replaces the seven-\nyear-old grails-on-github-actions/v4 guide whose actions are all\ndeprecated.\n\nCompanion conf/guides.yml change repoints the existing v4 entry from\ngrails-guides/grails-on-github-actions to the renamed\ngrails-guides/grails-github-actions-cicd repo. The original master\nbranch is preserved on the renamed repo so the v4 guide continues to\nresolve.\n\nThe 17 chapters walk through a five-job graph (validation -\u003e unit -\nintegration -\u003e functional -\u003e coverage), a separate tag-triggered\nrelease.yml that builds the OCI image with bootBuildImage and pushes\nto GHCR, plus dependabot.yml with grouped Grails / Spring / testing\nPRs and required-status-check configuration.\n\nForward-references the just-landed grails-docker-bootbuildimage v8\nguide (release.yml consumes its bootBuildImage Gradle task).\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_github_actions_cicd_8 --rerun-tasks --no-configuration-cache\n    Renders all 17 chapter HTML pages, no Unresolved directive errors.\n\nThe mode\u003dboth pass also confirms grails-guides/grails-github-actions-cicd\nexists on master (where the v4 guide points) and grails8 (where this\nguide points), i.e. the upstream rename and new branch are intact.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "b96505759092033145dafa5e425b653f4e45480e",
      "tree": "6fe3501e08f1f0d14781af9e36282237f15728e7",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:27:34 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:27:34 2026 -0400"
      },
      "message": "Add grails-docker-bootbuildimage v8 guide\n\nWorked example for packaging a Grails 8 app as a production-ready OCI\nimage and orchestrating it with Docker Compose + PostgreSQL.\n\nLeads with Spring Boot 4\u0027s bootBuildImage Gradle task (Paketo Buildpacks,\nno Dockerfile), then includes a hand-rolled multi-stage Dockerfile with\nnon-root user (UID 10001) as the alternative path. Both produce\nequivalent runtime behaviour with -XX:+UseContainerSupport and\n-XX:MaxRAMPercentage\u003d75.0 baked in.\n\nThe companion conf/guides.yml change repoints the existing v3 and v4\nentries on lines 1051 and 1079 from grails-as-docker-container to the\nfreshly-renamed grails-docker-bootbuildimage repo. GitHub auto-redirects\nmean the historical v3/v4 sample-app clones keep working via either\nname; the registry now points at the canonical name.\n\nSample-app flavour. Upstream is grails-guides/grails-docker-bootbuildimage\non the new grails8 branch (alongside the preserved grails3/grails4/grails5\nbranches). The complete/ tree generates a ghcr.io image, runs alongside\npostgres:16-alpine via compose.yml, and exposes Spring Boot Actuator\nliveness + readiness probes for the Compose HEALTHCHECK.\n\nThe 18 chapters walk through:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Creating the Application (download via the forge with\n  postgres+testcontainers+database-migration features)\n- Two Paths to a Container Image (Paketo vs Dockerfile mental model)\n- The Spring Boot bootBuildImage Task (canonical path)\n- Run the Image and Verify\n- The Hand-Rolled Multi-Stage Dockerfile (alternative)\n- Environment-Driven application.yml (SPRING_DATASOURCE_* env vars)\n- Container-Friendly JVM Flags\n- Spring Boot Actuator Health Probes (liveness + readiness)\n- Orchestrating the Stack with Docker Compose\n- Wiring the App to a Real Postgres\n- Pushing to GitHub Container Registry\n- Running as a Non-Root User\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_docker_bootbuildimage_8 --rerun-tasks --no-configuration-cache\n    Renders all 18 chapter HTML pages, no Unresolved directive errors.\n\nThe validateGuides mode\u003dboth pass also confirms that\ngrails-guides/grails-docker-bootbuildimage#grails3,\ngrails-guides/grails-docker-bootbuildimage#grails4, and\ngrails-guides/grails-docker-bootbuildimage#grails8 all exist - i.e. the\nupstream rename and the new branch are both intact.\n\nCross-references the upcoming grails-github-actions-cicd v8 guide\n(release.yml workflow consumes the bootBuildImage output).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "23103ea616c06a2dffa388c6c46296c1ea680b62",
      "tree": "d431a63e482e686db166953faaab3dc6727fa81e",
      "parents": [
        "cf0be8da350c8defd9c1b2016faaf61771d81a6a",
        "2b7fe54ed30ab6c1f47bb9e7bcb9bea7a20eb027"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:22:18 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 15:22:18 2026 -0400"
      },
      "message": "Merge pull request #493 from apache/add-grails-tailwindcss-v8\n\nAdd grails-tailwindcss v8 guide"
    },
    {
      "commit": "2b7fe54ed30ab6c1f47bb9e7bcb9bea7a20eb027",
      "tree": "ad144cedbef2f853623f1bb0152491c4d5e63dbd",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:05:40 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 15:05:40 2026 -0400"
      },
      "message": "Add grails-tailwindcss v8 guide\n\nWorked example for wiring Tailwind CSS 4 into a Grails 8 GSP application\nvia a Gradle-driven\npx @tailwindcss/cli build step. Targets the same\naudience as the recently-landed grails-fields-custom-widgets-and-wrappers\nv8 guide: server-rendered Grails apps that want a modern utility-first\nCSS pipeline without abandoning GSP for an SPA.\n\nSample-app flavour. Upstream is grails-guides/grails-tailwindcss on the\ngrails8 branch, with the standard initial/ (vanilla Grails 8 starter\ngenerated from prev-snapshot.grails.org) + complete/ (Tailwind wiring\napplied) layout. The complete/ build was verified end-to-end:\n`npm install` + `npx @tailwindcss/cli` produces a 16KB minified\n`app.css` in 62ms against Tailwind 4.2.4.\n\nThe 14 chapters walk through the full integration:\n\n- Getting Started (whatYouWillBuild, requirements, howto)\n- Creating the Application (download via the forge create endpoint)\n- Installing Tailwind CSS 4\n- Configuring Tailwind for Grails (CSS-first config via @source +\n  @custom-variant; no tailwind.config.js needed in v4)\n- Wiring Tailwind into the Gradle Build (npmInstall + tailwindBuild\n  Exec tasks hooked into processResources and compileGroovy)\n- Applying Tailwind to the GSP Layout (rewritten main.gsp + index.gsp)\n- Class-Based Dark Mode (pre-paint script + localStorage toggle)\n- Reusable Component Classes with @apply (btn-primary, card, nav-link)\n\nVerified locally:\n\n  ./gradlew validateGuides -PvalidationMode\u003dboth\n    [validateGuides] mode\u003dboth: 86 guide(s) parsed, 0 SKIP-warned, 0 errors\n\n  ./gradlew renderGuide_grails_tailwindcss_8 --rerun-tasks --no-configuration-cache\n    Renders all 14 chapter HTML pages, no Unresolved directive errors,\n    clean prev/next navigation. (--no-configuration-cache mirrors the\n    existing publishMainSite workaround in 0a0201f898.)\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "cf0be8da350c8defd9c1b2016faaf61771d81a6a",
      "tree": "404892cccc10d1032db378dd3d0cfee15dfcafad",
      "parents": [
        "41851a5a5f38b8bece0f2745221069ca4f3f3b52",
        "97f47ade12a9f2d204b1b87ea96be8c7456104bb"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun May 03 14:49:39 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 14:49:39 2026 -0400"
      },
      "message": "Merge pull request #492 from apache/keys-downloads-fix\n\nFix Key URL"
    },
    {
      "commit": "97f47ade12a9f2d204b1b87ea96be8c7456104bb",
      "tree": "404892cccc10d1032db378dd3d0cfee15dfcafad",
      "parents": [
        "41851a5a5f38b8bece0f2745221069ca4f3f3b52"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun May 03 14:32:15 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun May 03 14:32:15 2026 -0400"
      },
      "message": "All links to checksums, detached signatures and public keys must reference the Apache Distribution Directory (downloads.apache.org) and use https:// (SSL)\n"
    },
    {
      "commit": "d40bf5cc77d6a946faf5f33de8a2cd1bfaa9ea8e",
      "tree": "dad0165472f1dbc28cd30b7257905dc11016118a",
      "parents": [
        "07363e7a36126e21ebe39d2b2a112ccac6f5ab18"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 14:11:03 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 14:11:03 2026 -0400"
      },
      "message": "Add Grails 8 companionArtifacts block with grails-publish 1.0.0-M1\n\nThe grails-publish Gradle plugin (used to publish Grails artifacts to\nMaven Central) is the first Grails-8-targeted Apache release in the\ncompanion ecosystem. It ships ahead of Grails 8 core because the\ntoolchain has to be in place before the runtime can be cut.\n\nBootstraps the \u00278\u0027: block under companionArtifacts: with this single\nentry. mirrorDirectory, releaseNotesRepo, and displayName mirror the\n\u00277\u0027: entry for the same artifact since they are stable per-plugin\nidentifiers, not per-major. The renderer (DownloadPage,\nDocumentationPage, hero cards) only iterates over majors that have\na corresponding stable core release listed in coreReleases:, so this\nentry is dormant in the rendered site until 8.0.0-M1 core ships - at\nwhich point the plugin\u0027s mirror URL and release-notes link are\nalready populated.\n\nCompanion to the previous commit\u0027s recordCompanionRelease bootstrap\nsupport: this is exactly the kind of edit the new-major path now\nautomates, but doing it as a one-shot data PR here exercises the\nmanual schema before the workflow has a chance to run for an\n8-targeted plugin.\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "07363e7a36126e21ebe39d2b2a112ccac6f5ab18",
      "tree": "9a1150c06baaf5a99e79e90d540a34af906db2b0",
      "parents": [
        "41851a5a5f38b8bece0f2745221069ca4f3f3b52"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 14:10:49 2026 -0400"
      },
      "committer": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 14:10:49 2026 -0400"
      },
      "message": "Extend recordCompanionRelease to bootstrap new majors and new artifacts\n\nCompanion artifacts ship independently of Grails core, so each (grailsMajor,\nartifactId) pair has its own first-publish moment. Today the task can only\nbump the version: line of an entry that already exists - any first-time\nappearance forces a manual edit of conf/releases.yml because the task\ndoesn\u0027t know mirrorDirectory, releaseNotesRepo, or displayName.\n\nThis commit lets the task perform the first-time edit too when those three\ndescriptor flags are passed via -P. The decision is automatic based on what\nthe file already contains:\n\n  - existing artifactId entry under the major  -\u003e bump version: in place\n    (unchanged behaviour; descriptor flags ignored to prevent accidental\n    URL drift mid-major)\n  - \u0027N\u0027: major block exists but the artifactId is not listed\n    -\u003e append a new entry to the end of the block; descriptor flags required\n  - no \u0027N\u0027: block at all\n    -\u003e append a new major block at the end of companionArtifacts:,\n       preserving the blank-line separator between it and coreReleases:;\n       descriptor flags required\n\nscanLayout is a single-pass line scanner that records every index the\naction needs (companion-section header, target-major end, target-artifact\nversion line, companion-section end). The two insertion paths trim back\nover trailing blank lines so an inserted entry lands directly after the\nlast meaningful content line rather than after the blank-line separator.\n\nrelease-companion.yml gains three optional workflow_dispatch inputs\n(mirror_directory, release_notes_repo, display_name) that are forwarded\nto the task. Operators only fill them on the first publish for each\n(major, artifactId) pair; subsequent bumps leave them blank.\n\n11 new RecordCompanionReleaseTaskSpec scenarios cover all three paths\nplus the missing-flags, missing-core-flags, and no-companionArtifacts-\nsection error cases, plus the two cross-major insertion-point cases\nthat surfaced during development (target major is the last block in the\nsection vs target major is followed by a higher major).\n\nAssisted-by: claude-code:claude-opus-4-7\n"
    },
    {
      "commit": "41851a5a5f38b8bece0f2745221069ca4f3f3b52",
      "tree": "b90fb9aea6fcd881800f0bd866b50417defec8a7",
      "parents": [
        "967175525b2dc9b2484c760e50867f0d857080a1"
      ],
      "author": {
        "name": "James Fredley",
        "email": "jamesfredley@users.noreply.github.com",
        "time": "Sun May 03 13:40:26 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun May 03 13:40:26 2026 -0400"
      },
      "message": "Delete .playwright-mcp directory"
    },
    {
      "commit": "967175525b2dc9b2484c760e50867f0d857080a1",
      "tree": "441d60b3a68cf2c5d8efbb335397dd3920a9c24c",
      "parents": [
        "9adb0592a8aa63e3646f268122c42adb4c9bb489"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:14:45 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:14:45 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nBump grails-publish to 0.0.5 for Grails 7\n"
    },
    {
      "commit": "9adb0592a8aa63e3646f268122c42adb4c9bb489",
      "tree": "739269a8f76382d358bbe5cb0faab13fd3760b55",
      "parents": [
        "16b8fd753c9dfff4be39c6f534179591076d7f13"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:13:17 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:13:17 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nBump grails-github-actions to 1.0.2 for Grails 7\n"
    },
    {
      "commit": "16b8fd753c9dfff4be39c6f534179591076d7f13",
      "tree": "d5a5fa14f9984de1310d79d328795d04e8925ee6",
      "parents": [
        "d12eb09af31b9eb27c735f16492236ae1b9a7add"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:06:20 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 16:06:20 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nBump grails-spring-security to 7.0.2 for Grails 7\n"
    },
    {
      "commit": "d12eb09af31b9eb27c735f16492236ae1b9a7add",
      "tree": "37b8c4dab574d0c19ba8167cf4fdaa7330658026",
      "parents": [
        "a5c5694b873ece2365d595d5ad118bdca3890ecf"
      ],
      "author": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 15:22:12 2026 +0000"
      },
      "committer": {
        "name": "grails-build",
        "email": "grails-build@users.noreply.github.com",
        "time": "Sun May 03 15:22:12 2026 +0000"
      },
      "message": "Update conf/releases.yml\n\nAdd 7.1.1 to Grails Versions list\n"
    }
  ],
  "next": "a5c5694b873ece2365d595d5ad118bdca3890ecf"
}
