[{"data":1,"prerenderedAt":1042},["ShallowReactive",2],{"navigation":3,"\u002Fgetting-started\u002Freleases":553,"\u002Fgetting-started\u002Freleases-surround":770,"$f2l91utyllys1t":775},[4,22,64,89,215,503],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","1.getting-started\u002F1.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation",{"title":15,"path":16,"stem":17},"Key Ideas","\u002Fgetting-started\u002Fkey-ideas","1.getting-started\u002F3.key-ideas",{"title":19,"path":20,"stem":21},"Releases","\u002Fgetting-started\u002Freleases","1.getting-started\u002F4.releases",{"title":23,"path":24,"stem":25,"children":26},"Overview","\u002Fcore-concepts","2.core-concepts\u002F0.index",[27,28,32,36,40,44,48,52,56,60],{"title":23,"path":24,"stem":25},{"title":29,"path":30,"stem":31},"How It All Works","\u002Fcore-concepts\u002Farchitecture","2.core-concepts\u002F1.architecture",{"title":33,"path":34,"stem":35},"The Data Model","\u002Fcore-concepts\u002Fthe-data-model","2.core-concepts\u002F2.the-data-model",{"title":37,"path":38,"stem":39},"Layouts & Pages","\u002Fcore-concepts\u002Flayouts-and-pages","2.core-concepts\u002F3.layouts-and-pages",{"title":41,"path":42,"stem":43},"Dynamic Pages","\u002Fcore-concepts\u002Fdynamic-pages","2.core-concepts\u002F4.dynamic-pages",{"title":45,"path":46,"stem":47},"Components","\u002Fcore-concepts\u002Fcomponents","2.core-concepts\u002F5.components",{"title":49,"path":50,"stem":51},"Draft & Publish Workflow","\u002Fcore-concepts\u002Fdraft-and-publish","2.core-concepts\u002F6.draft-and-publish",{"title":53,"path":54,"stem":55},"The Admin Panel","\u002Fcore-concepts\u002Fadmin-panel","2.core-concepts\u002F7.admin-panel",{"title":57,"path":58,"stem":59},"Caching","\u002Fcore-concepts\u002Fcaching","2.core-concepts\u002F8.caching",{"title":61,"path":62,"stem":63},"Glossary","\u002Fcore-concepts\u002Fglossary","2.core-concepts\u002F9.glossary",{"title":23,"path":65,"stem":66,"children":67},"\u002Fguides","3.guides\u002F0.index",[68,69,73,77,81,85],{"title":23,"path":65,"stem":66},{"title":70,"path":71,"stem":72},"Your First Layout","\u002Fguides\u002Fyour-first-layout","3.guides\u002F1.your-first-layout",{"title":74,"path":75,"stem":76},"Your First Page Template","\u002Fguides\u002Fyour-first-page-template","3.guides\u002F2.your-first-page-template",{"title":78,"path":79,"stem":80},"Your First Component","\u002Fguides\u002Fyour-first-component","3.guides\u002F3.your-first-component",{"title":82,"path":83,"stem":84},"Alternative UI Variants","\u002Fguides\u002Falternative-ui-variants","3.guides\u002F4.alternative-ui-variants",{"title":86,"path":87,"stem":88},"Your First Dynamic Page","\u002Fguides\u002Fyour-first-dynamic-page","3.guides\u002F5.your-first-dynamic-page",{"title":23,"path":90,"stem":91,"children":92},"\u002Fapi","4.api\u002F0.index",[93,94,98,102,120,162,166,170,195,199,203,207,211],{"title":23,"path":90,"stem":91},{"title":95,"path":96,"stem":97},"Bundle Setup","\u002Fapi\u002Fbundle-setup","4.api\u002F02.bundle-setup",{"title":99,"path":100,"stem":101},"Configuration Reference","\u002Fapi\u002Fconfiguration","4.api\u002F03.configuration",{"title":103,"path":104,"stem":105,"children":106,"page":119},"Users","\u002Fapi\u002Fusers","4.api\u002F04.users",[107,111,115],{"title":108,"path":109,"stem":110},"Authentication","\u002Fapi\u002Fusers\u002Fauthentication","4.api\u002F04.users\u002F1.authentication",{"title":112,"path":113,"stem":114},"User Emails","\u002Fapi\u002Fusers\u002Fuser-emails","4.api\u002F04.users\u002F2.user-emails",{"title":116,"path":117,"stem":118},"Access Control","\u002Fapi\u002Fusers\u002Faccess-control","4.api\u002F04.users\u002F3.access-control",false,{"title":45,"path":121,"stem":122,"children":123,"page":119},"\u002Fapi\u002Fcomponents","4.api\u002F05.components",[124,128,149],{"title":125,"path":126,"stem":127},"Creating Components","\u002Fapi\u002Fcomponents\u002Fcreating-components","4.api\u002F05.components\u002F1.creating-components",{"title":129,"path":130,"stem":131,"children":132,"page":119},"Annotations","\u002Fapi\u002Fcomponents\u002Fannotations","4.api\u002F05.components\u002F2.annotations",[133,137,141,145],{"title":134,"path":135,"stem":136},"Publishable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fpublishable","4.api\u002F05.components\u002F2.annotations\u002F1.publishable",{"title":138,"path":139,"stem":140},"Uploadable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable","4.api\u002F05.components\u002F2.annotations\u002F2.uploadable",{"title":142,"path":143,"stem":144},"Timestamped","\u002Fapi\u002Fcomponents\u002Fannotations\u002Ftimestamped","4.api\u002F05.components\u002F2.annotations\u002F3.timestamped",{"title":146,"path":147,"stem":148},"Explicit Allow Only","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fexplicit-allow-only","4.api\u002F05.components\u002F2.annotations\u002F4.explicit-allow-only",{"title":150,"path":151,"stem":152,"children":153,"page":119},"Built Ins","\u002Fapi\u002Fcomponents\u002Fbuilt-ins","4.api\u002F05.components\u002F3.built-ins",[154,158],{"title":155,"path":156,"stem":157},"Collection Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fcollection-component","4.api\u002F05.components\u002F3.built-ins\u002F1.collection-component",{"title":159,"path":160,"stem":161},"Form Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fform-component","4.api\u002F05.components\u002F3.built-ins\u002F2.form-component",{"title":163,"path":164,"stem":165},"Page Data & Nested Pages","\u002Fapi\u002Fdynamic-pages","4.api\u002F06.dynamic-pages",{"title":167,"path":168,"stem":169},"Routes","\u002Fapi\u002Froutes","4.api\u002F07.routes",{"title":171,"path":172,"stem":173,"children":174,"page":119},"Data Fixtures","\u002Fapi\u002Fdata-fixtures","4.api\u002F08.data-fixtures",[175,179,183,187,191],{"title":176,"path":177,"stem":178},"Getting Started with Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgetting-started","4.api\u002F08.data-fixtures\u002F1.getting-started",{"title":180,"path":181,"stem":182},"Builder Reference","\u002Fapi\u002Fdata-fixtures\u002Fbuilder-reference","4.api\u002F08.data-fixtures\u002F2.builder-reference",{"title":184,"path":185,"stem":186},"Seeding Content","\u002Fapi\u002Fdata-fixtures\u002Fseeding-content","4.api\u002F08.data-fixtures\u002F3.seeding-content",{"title":188,"path":189,"stem":190},"Appending & Multiple Scaffolds","\u002Fapi\u002Fdata-fixtures\u002Fappending","4.api\u002F08.data-fixtures\u002F4.appending",{"title":192,"path":193,"stem":194},"Generating Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgenerating-fixtures","4.api\u002F08.data-fixtures\u002F5.generating-fixtures",{"title":196,"path":197,"stem":198},"Real-Time (Mercure)","\u002Fapi\u002Freal-time","4.api\u002F09.real-time",{"title":200,"path":201,"stem":202},"HTTP Caching","\u002Fapi\u002Fhttp-caching","4.api\u002F10.http-caching",{"title":204,"path":205,"stem":206},"Orphaned Data","\u002Fapi\u002Forphaned-data","4.api\u002F11.orphaned-data",{"title":208,"path":209,"stem":210},"Console Commands","\u002Fapi\u002Fconsole-commands","4.api\u002F12.console-commands",{"title":212,"path":213,"stem":214},"Debugging & Errors","\u002Fapi\u002Fdebugging","4.api\u002F13.debugging",{"title":23,"path":216,"stem":217,"children":218},"\u002Fnuxt-module","5.nuxt-module\u002F0.index",[219,220,224,237,257,286,307,430,464,467,495,499],{"title":23,"path":216,"stem":217},{"title":221,"path":222,"stem":223},"Module Setup","\u002Fnuxt-module\u002Fmodule-setup","5.nuxt-module\u002F01.module-setup",{"title":225,"path":226,"stem":227,"children":228,"page":119},"Configuration","\u002Fnuxt-module\u002Fconfiguration","5.nuxt-module\u002F02.configuration",[229,233],{"title":230,"path":231,"stem":232},"Nuxt Config","\u002Fnuxt-module\u002Fconfiguration\u002Fnuxt-config","5.nuxt-module\u002F02.configuration\u002F1.nuxt-config",{"title":234,"path":235,"stem":236},"Site Config & SEO","\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo","5.nuxt-module\u002F02.configuration\u002F2.site-config-and-seo",{"title":238,"path":239,"stem":240,"children":241,"page":119},"Building Your Ui","\u002Fnuxt-module\u002Fbuilding-your-ui","5.nuxt-module\u002F03.building-your-ui",[242,246,250,253],{"title":243,"path":244,"stem":245},"Layouts","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-layouts","5.nuxt-module\u002F03.building-your-ui\u002F1.creating-layouts",{"title":247,"path":248,"stem":249},"Page Templates","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-page-templates","5.nuxt-module\u002F03.building-your-ui\u002F2.creating-page-templates",{"title":45,"path":251,"stem":252},"\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-components","5.nuxt-module\u002F03.building-your-ui\u002F3.creating-components",{"title":254,"path":255,"stem":256},"CLI Generator","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcwa-cli","5.nuxt-module\u002F03.building-your-ui\u002F4.cwa-cli",{"title":258,"path":259,"stem":260,"children":261,"page":119},"Component Helpers","\u002Fnuxt-module\u002Fcomponent-helpers","5.nuxt-module\u002F04.component-helpers",[262,266,270,274,278,282],{"title":263,"path":264,"stem":265},"Images & Media","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fimages-and-uploads","5.nuxt-module\u002F04.component-helpers\u002F1.images-and-uploads",{"title":267,"path":268,"stem":269},"Collections & Pagination","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fcollections-and-pagination","5.nuxt-module\u002F04.component-helpers\u002F2.collections-and-pagination",{"title":271,"path":272,"stem":273},"HTML Content","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fhtml-content","5.nuxt-module\u002F04.component-helpers\u002F3.html-content",{"title":275,"path":276,"stem":277},"Rich-Text Editor","\u002Fnuxt-module\u002Fcomponent-helpers\u002Frich-text-editor","5.nuxt-module\u002F04.component-helpers\u002F4.rich-text-editor",{"title":279,"path":280,"stem":281},"Real-Time Updates","\u002Fnuxt-module\u002Fcomponent-helpers\u002Freal-time-updates","5.nuxt-module\u002F04.component-helpers\u002F5.real-time-updates",{"title":283,"path":284,"stem":285},"Forms","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fforms","5.nuxt-module\u002F04.component-helpers\u002F6.forms",{"title":287,"path":288,"stem":289,"children":290,"page":119},"Cwa Components","\u002Fnuxt-module\u002Fcwa-components","5.nuxt-module\u002F05.cwa-components",[291,295,299,303],{"title":292,"path":293,"stem":294},"\u003CCwaComponentGroup \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-component-group","5.nuxt-module\u002F05.cwa-components\u002F1.cwa-component-group",{"title":296,"path":297,"stem":298},"\u003CCwaPage \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-page","5.nuxt-module\u002F05.cwa-components\u002F2.cwa-page",{"title":300,"path":301,"stem":302},"\u003CCwaLink \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-link","5.nuxt-module\u002F05.cwa-components\u002F3.cwa-link",{"title":304,"path":305,"stem":306},"\u003CCwaDefaultLayout \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-default-layout","5.nuxt-module\u002F05.cwa-components\u002F5.cwa-default-layout",{"title":308,"path":309,"stem":310,"children":311,"page":119},"Composables","\u002Fnuxt-module\u002Fcomposables","5.nuxt-module\u002F06.composables",[312,315,323,348,368,385,405],{"title":23,"path":313,"stem":314},"\u002Fnuxt-module\u002Fcomposables\u002Foverview","5.nuxt-module\u002F06.composables\u002F0.overview",{"title":316,"path":317,"stem":318,"children":319,"page":119},"Layout","\u002Fnuxt-module\u002Fcomposables\u002Flayout","5.nuxt-module\u002F06.composables\u002F1.layout",[320],{"title":316,"path":321,"stem":322},"\u002Fnuxt-module\u002Fcomposables\u002Flayout\u002Fuse-cwa-layout","5.nuxt-module\u002F06.composables\u002F1.layout\u002F1.use-cwa-layout",{"title":324,"path":325,"stem":326,"children":327,"page":119},"Component","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent","5.nuxt-module\u002F06.composables\u002F2.component",[328,332,336,340,344],{"title":329,"path":330,"stem":331},"Component (recommended)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-component","5.nuxt-module\u002F06.composables\u002F2.component\u002F1.use-cwa-component",{"title":333,"path":334,"stem":335},"Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F2.use-cwa-resource",{"title":337,"path":338,"stem":339},"File Field","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-file-field","5.nuxt-module\u002F06.composables\u002F2.component\u002F3.use-cwa-file-field",{"title":341,"path":342,"stem":343},"Collection Pagination","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-pagination","5.nuxt-module\u002F06.composables\u002F2.component\u002F4.use-cwa-collection-pagination",{"title":345,"path":346,"stem":347},"Collection Resource (deprecated)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F9.use-cwa-collection-resource",{"title":349,"path":350,"stem":351,"children":352,"page":119},"Form","\u002Fnuxt-module\u002Fcomposables\u002Fform","5.nuxt-module\u002F06.composables\u002F3.form",[353,356,360,364],{"title":349,"path":354,"stem":355},"\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form","5.nuxt-module\u002F06.composables\u002F3.form\u002F1.use-cwa-form",{"title":357,"path":358,"stem":359},"Form Input","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-input","5.nuxt-module\u002F06.composables\u002F3.form\u002F2.use-cwa-form-input",{"title":361,"path":362,"stem":363},"Form Repeated","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-repeated","5.nuxt-module\u002F06.composables\u002F3.form\u002F3.use-cwa-form-repeated",{"title":365,"path":366,"stem":367},"Form Collection","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-collection","5.nuxt-module\u002F06.composables\u002F3.form\u002F4.use-cwa-form-collection",{"title":369,"path":370,"stem":371,"children":372,"page":119},"Admin Manager","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager","5.nuxt-module\u002F06.composables\u002F4.admin-manager",[373,377,381],{"title":374,"path":375,"stem":376},"Manager Tab","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-manager-tab","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F1.use-cwa-resource-manager-tab",{"title":378,"path":379,"stem":380},"Resource Model","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-model","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F2.use-cwa-resource-model",{"title":382,"path":383,"stem":384},"Resource Upload","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-upload","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F3.use-cwa-resource-upload",{"title":386,"path":387,"stem":388,"children":389,"page":119},"Utilities","\u002Fnuxt-module\u002Fcomposables\u002Futilities","5.nuxt-module\u002F06.composables\u002F5.utilities",[390,394,398,402],{"title":391,"path":392,"stem":393},"Resource Endpoint","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-endpoint","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F1.use-cwa-resource-endpoint",{"title":395,"path":396,"stem":397},"Query Model","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-query-bound-model","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F2.use-query-bound-model",{"title":399,"path":400,"stem":401},"Resource Route","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-route","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F3.use-cwa-resource-route",{"title":271,"path":403,"stem":404},"\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-html-content","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F4.use-html-content",{"title":406,"path":407,"stem":408,"children":409,"page":119},"Auth","\u002Fnuxt-module\u002Fcomposables\u002Fauth","5.nuxt-module\u002F06.composables\u002F6.auth",[410,414,418,422,426],{"title":411,"path":412,"stem":413},"Login","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-login","5.nuxt-module\u002F06.composables\u002F6.auth\u002F1.use-login",{"title":415,"path":416,"stem":417},"Forgot Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-forgot-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F2.use-forgot-password",{"title":419,"path":420,"stem":421},"Reset Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-reset-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F3.use-reset-password",{"title":423,"path":424,"stem":425},"Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F4.use-verify-email",{"title":427,"path":428,"stem":429},"Resend Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-resend-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F5.use-resend-verify-email",{"title":431,"path":432,"stem":433,"children":434,"page":119},"Cwa Api","\u002Fnuxt-module\u002Fcwa-api","5.nuxt-module\u002F07.cwa-api",[435,438,442,446,449,452,456,460],{"title":23,"path":436,"stem":437},"\u002Fnuxt-module\u002Fcwa-api\u002Foverview","5.nuxt-module\u002F07.cwa-api\u002F1.overview",{"title":439,"path":440,"stem":441},"Resources","\u002Fnuxt-module\u002Fcwa-api\u002Fresources","5.nuxt-module\u002F07.cwa-api\u002F2.resources",{"title":443,"path":444,"stem":445},"Resources Manager","\u002Fnuxt-module\u002Fcwa-api\u002Fresources-manager","5.nuxt-module\u002F07.cwa-api\u002F3.resources-manager",{"title":406,"path":447,"stem":448},"\u002Fnuxt-module\u002Fcwa-api\u002Fauth","5.nuxt-module\u002F07.cwa-api\u002F4.auth",{"title":283,"path":450,"stem":451},"\u002Fnuxt-module\u002Fcwa-api\u002Fforms","5.nuxt-module\u002F07.cwa-api\u002F5.forms",{"title":453,"path":454,"stem":455},"Admin","\u002Fnuxt-module\u002Fcwa-api\u002Fadmin","5.nuxt-module\u002F07.cwa-api\u002F6.admin",{"title":457,"path":458,"stem":459},"Site Config","\u002Fnuxt-module\u002Fcwa-api\u002Fsite-config","5.nuxt-module\u002F07.cwa-api\u002F7.site-config",{"title":461,"path":462,"stem":463},"Orphaned Resources","\u002Fnuxt-module\u002Fcwa-api\u002Forphaned-resources","5.nuxt-module\u002F07.cwa-api\u002F8.orphaned-resources",{"title":108,"path":465,"stem":466},"\u002Fnuxt-module\u002Fauthentication","5.nuxt-module\u002F08.authentication",{"title":468,"path":469,"stem":470,"children":471,"page":119},"Cwa Layer","\u002Fnuxt-module\u002Fcwa-layer","5.nuxt-module\u002F9.cwa-layer",[472,475,479,483,487,491],{"title":23,"path":473,"stem":474},"\u002Fnuxt-module\u002Fcwa-layer\u002Foverview","5.nuxt-module\u002F9.cwa-layer\u002F1.overview",{"title":476,"path":477,"stem":478},"Auth Pages","\u002Fnuxt-module\u002Fcwa-layer\u002Fauth-pages","5.nuxt-module\u002F9.cwa-layer\u002F2.auth-pages",{"title":480,"path":481,"stem":482},"Admin Panel","\u002Fnuxt-module\u002Fcwa-layer\u002Fadmin-panel","5.nuxt-module\u002F9.cwa-layer\u002F3.admin-panel",{"title":484,"path":485,"stem":486},"Styling","\u002Fnuxt-module\u002Fcwa-layer\u002Fstyling","5.nuxt-module\u002F9.cwa-layer\u002F4.styling",{"title":488,"path":489,"stem":490},"Date Pickers","\u002Fnuxt-module\u002Fcwa-layer\u002Fdate-pickers","5.nuxt-module\u002F9.cwa-layer\u002F5.date-pickers",{"title":492,"path":493,"stem":494},"Maintenance","\u002Fnuxt-module\u002Fcwa-layer\u002Fmaintenance","5.nuxt-module\u002F9.cwa-layer\u002F6.maintenance",{"title":496,"path":497,"stem":498},"PWA & Offline","\u002Fnuxt-module\u002Fpwa","5.nuxt-module\u002F91.pwa",{"title":500,"path":501,"stem":502},"Page Caching","\u002Fnuxt-module\u002Fpage-caching","5.nuxt-module\u002F92.page-caching",{"title":504,"path":505,"stem":506,"children":507},"Deploying a CWA Site","\u002Fdeployment","6.deployment\u002F0.index",[508,509,513,517,521,525,529,533,537,541,545,549],{"title":504,"path":505,"stem":506},{"title":510,"path":511,"stem":512},"Docker","\u002Fdeployment\u002Fdocker","6.deployment\u002F1.docker",{"title":514,"path":515,"stem":516},"Kubernetes & Helm","\u002Fdeployment\u002Fkubernetes","6.deployment\u002F2.kubernetes",{"title":518,"path":519,"stem":520},"CI\u002FCD","\u002Fdeployment\u002Fci-cd","6.deployment\u002F3.ci-cd",{"title":522,"path":523,"stem":524},"CI\u002FCD Variables","\u002Fdeployment\u002Fvariables","6.deployment\u002F4.variables",{"title":526,"path":527,"stem":528},"Cloudflare","\u002Fdeployment\u002Fcloudflare","6.deployment\u002F5.cloudflare",{"title":530,"path":531,"stem":532},"Origin Protection","\u002Fdeployment\u002Forigin-protection","6.deployment\u002F5.origin-protection",{"title":534,"path":535,"stem":536},"Load Testing","\u002Fdeployment\u002Fload-testing","6.deployment\u002F6.load-testing",{"title":538,"path":539,"stem":540},"Performance Audit","\u002Fdeployment\u002Fperformance-audit","6.deployment\u002F6.performance-audit",{"title":542,"path":543,"stem":544},"Reading Lighthouse Scores","\u002Fdeployment\u002Freading-lighthouse-scores","6.deployment\u002F6.reading-lighthouse-scores",{"title":546,"path":547,"stem":548},"Faster Functional Tests","\u002Fdeployment\u002Ffaster-functional-tests","6.deployment\u002F7.faster-functional-tests",{"title":550,"path":551,"stem":552},"Troubleshooting","\u002Fdeployment\u002Ftroubleshooting","6.deployment\u002F7.troubleshooting",{"id":554,"title":19,"badge":555,"body":556,"description":764,"extension":765,"links":555,"meta":766,"navigation":767,"path":20,"seo":768,"stem":21,"__hash__":769},"docs\u002F1.getting-started\u002F4.releases.md",null,{"type":557,"value":558,"toc":758},"minimark",[559,563,596,603,629,634,637,640,644,650,749],[560,561,562],"p",{},"A CWA site is built from three parts, each released on its own:",[564,565,566,574,583],"ul",{},[567,568,569,573],"li",{},[570,571,572],"strong",{},"api-components-bundle",", the Symfony bundle, installed with Composer.",[567,575,576,582],{},[570,577,578],{},[579,580,581],"code",{},"@cwa\u002Fnuxt",", the Nuxt module, installed from npm.",[567,584,585,588,589,592,593,595],{},[570,586,587],{},"The template"," (components-web-app), the project ",[579,590,591],{},"create-cwa"," generates. ",[579,594,591],{}," has the same version as the template release it installs.",[560,597,598,599,602],{},"All three are in ",[579,600,601],{},"2.0.0-alpha",". Pre-releases can change behaviour or configuration between versions, so update the three together and follow the upgrade notes of every release you skip over.",[604,605,607],"callout",{"icon":606},"i-heroicons-light-bulb",[560,608,609,612,613,616,617,620,621,623,624,628],{},[570,610,611],{},"The docs follow the development branches."," They describe the bundle's ",[579,614,615],{},"main",", the module's ",[579,618,619],{},"dev"," and the template's ",[579,622,615],{},", which can be ahead of the last release. A recent change carries a short marker such as ",[625,626,627],"em",{},"Since bundle 2.0.0-alpha.16",", with a link to its upgrade notes.",[630,631,633],"h2",{"id":632},"latest-releases","Latest releases",[560,635,636],{},"Each release's notes come straight from GitHub, including its upgrade notes. Template releases also say which bundle and module versions they move to.",[638,639],"release-notes",{},[630,641,643],{"id":642},"the-modules-minimum-bundle","The module's minimum bundle",[560,645,646,647,649],{},"Some ",[579,648,581],{}," releases need a newer bundle. Use at least this bundle version with each module version:",[651,652,653,669],"table",{},[654,655,656],"thead",{},[657,658,659,664,666],"tr",{},[660,661,662],"th",{},[579,663,581],{},[660,665,572],{},[660,667,668],{},"Why",[670,671,672,693,714,731],"tbody",{},[657,673,674,681,686],{},[675,676,677,680],"td",{},[579,678,679],{},"2.0.0-alpha.10"," and later",[675,682,683],{},[579,684,685],{},"2.0.0-alpha.13",[675,687,688,689,692],{},"Publishing sends ",[579,690,691],{},"\"publishedAt\": \"now\"",", which older bundles reject",[657,694,695,700,705],{},[675,696,697],{},[579,698,699],{},"2.0.0-alpha.9",[675,701,702],{},[579,703,704],{},"2.0.0-alpha.11",[675,706,707,708,711,712],{},"For the ",[570,709,710],{},"Invalid files"," report; everything else works with ",[579,713,699],{},[657,715,716,721,725],{},[675,717,718],{},[579,719,720],{},"2.0.0-alpha.8",[675,722,723],{},[579,724,699],{},[675,726,707,727,730],{},[570,728,729],{},"Public without a route"," toggle",[657,732,733,742,746],{},[675,734,735,738,739],{},[579,736,737],{},"2.0.0-alpha.4"," – ",[579,740,741],{},"2.0.0-alpha.7",[675,743,744],{},[579,745,720],{},[675,747,748],{},"Live updates use the Mercure 1.0 protocol, which a 0.x hub rejects",[560,750,751,752,757],{},"The module's other requirements, such as its Nuxt version, are in ",[753,754,756],"a",{"href":755},"\u002Fnuxt-module\u002Fmodule-setup#requirements","Module Setup → Requirements",".",{"title":759,"searchDepth":760,"depth":760,"links":761},"",2,[762,763],{"id":632,"depth":760,"text":633},{"id":642,"depth":760,"text":643},"The latest api-components-bundle, @cwa\u002Fnuxt and template releases, with their notes and upgrade steps from GitHub.","md",{},true,{"title":19,"description":764},"kvrhsk3uZoyccGfWpoyHlkfJUU-9xBfVbfucI1HaXY8",[771,773],{"title":15,"path":16,"stem":17,"description":772,"children":-1},"The handful of terms the guides use — layouts, pages, component groups, IRIs, uiComponent, page data and edit mode — each in a sentence or two.",{"title":23,"path":24,"stem":25,"description":774,"children":-1},"How a CWA site works, from the request to the data model, caching and the admin. Who this section is for and the order to read it.",[776,782,788,793,798,803,808,813,819,824,829,834,839,844,849,853,858,863,868,873,877,882,887,891,896,901,906,911,916,920,925,930,934,938,942,946,951,955,960,964,969,974,978,982,986,992,996,1001,1005,1011,1016,1020,1025,1029,1034,1038],{"part":777,"tag":778,"name":778,"publishedAt":779,"url":780,"body":781},"bundle","2.0.0-alpha.18","2026-10-13T01:22:37Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.18","### Breaking\n- `RouteGenerator` takes a `ValidatorInterface` ([#433](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F433))\n- A page's `uiComponent` is stored as the full component name, `CwaPage\u003CName>`: a POST or PATCH sending the short name (`PrimaryPageTemplate`, as the admin's page modals do) stores and returns `CwaPagePrimaryPageTemplate`, so pages written through the API and through `CwaFixtureBuilder` agree and `generate-fixtures` reloads them unchanged. Layouts are unchanged. Upgrade step: run `silverback:api-components:prefix-page-ui-components` once to prefix pages already stored with the short name; it is safe to run again ([#431](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F431))\n\n### Fixed\n- `POST \u002F_\u002Froutes\u002Fgenerate` is a 422 with a `path` violation, and creates nothing, when the parent's path and the title slug together are longer than 255 characters; `RouteGenerator::create()` throws the same `ValidationException` for fixtures. `PATCH \u002F_\u002Froutes\u002F{id}` with `cascadeChildPaths` checks every descendant's new path before writing anything, so a rename that would push one over 255 characters is a 422 naming that descendant instead of a 500 after the route and part of the tree were already moved. A cascade to a path under the route's own path no longer recurses without end when an unrouted child page loops back to it ([#433](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F433))\n- A value longer than its `VARCHAR(255)` column (a page's `title` or `metaDescription`, any `reference`, `uiComponent`, route `path`\u002F`name`, component position `pageDataProperty`\u002F`pageDataClass`, site config `key`, form `formType`, collection `resourceIri`, user `username`\u002F`emailAddress`\u002F`newEmailAddress`) is a 422 with a field violation instead of a 500 from MySQL or PostgreSQL ([#430](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F430))\n- A `PUT` to a user resource updates only the fields it sends. API Platform's default `standard_put` (on 4.x and 5.x) built a new user from the body, so a `PUT` returned 200 after blanking the password hash and resetting roles, `enabled` and email verification, or failed with 422\u002F500 when it sent no email address. The bundle sets `standard_put: false` on every `Put` of an `AbstractUser` resource ([#429](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F429))\n- `CwaFixtureBuilder` creates the groups of components declared inside a `nested()` closure, with their positions, so a scaffold generated from a site with nested pages no longer reloads those pages emptied below their first component ([#428](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F428))\n",{"part":783,"tag":784,"name":784,"publishedAt":785,"url":786,"body":787},"template","v2.0.0-alpha.28","2026-10-12T22:22:11Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.28","### Upgrade notes\n- Update api-components-bundle to 2.0.0-alpha.17 (`composer update components-web-app\u002Fapi-components-bundle`, or copy `api\u002Fcomposer.lock`) and copy the migration `Version20261012160000`, which drops variant file info written before alpha.17 (api-components-bundle#421); each row is read again from its stored variant on the next render. Nothing to run by hand: the deploy's new API pod starts with an empty cache, and a Cloudflare site's deploy already purges everything. Locally, run `bin\u002Fconsole silverback:api-components:purge-http-cache` after migrating. [c754732](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc754732709ae8e6b81967715cdbb93a075a5fcf7) [55e6beb](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F55e6beb6f660d16e567479159a49557c7c5fbdf6)\n- Copy `app\u002Fnuxt.config.ts`'s `precompressBuildAssets` helper and the module that calls it, and delete `compressPublicAssets` from its `nitro` block: `\u002F_nuxt` assets are still precompressed, but files in `public\u002F` no longer are, so a large file there no longer adds minutes to every app build (#155). [c9da319](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc9da31999e01bd848cd179d03ecd8aa9ac2b186f)\n\n### Fixed\n- api-components-bundle 2.0.0-alpha.17: storing an imagine variant again replaces its file info, so `_metadata.mediaObjects` reports the variant actually stored (api-components-bundle#421). [c754732](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc754732709ae8e6b81967715cdbb93a075a5fcf7)\n- App builds no longer brotli-compress everything in `public\u002F`: only `\u002F_nuxt` assets are precompressed. A 178 MB PDF in `public\u002F` had made a downstream site's app build take ~7 minutes instead of about one (#155). [c9da319](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc9da31999e01bd848cd179d03ecd8aa9ac2b186f)\n",{"part":777,"tag":789,"name":789,"publishedAt":790,"url":791,"body":792},"2.0.0-alpha.17","2026-10-12T22:10:38Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.17","### Fixed\n- Storing an imagine variant again replaces its file info, so `_metadata.mediaObjects` reports the variant actually stored after a filter's format, quality or size changes. Variant rows written before this fix stay stale: delete them with `DELETE FROM _acb_imagine_cached_file_metadata WHERE filter \u003C> '';`, then purge the HTTP cache, and each row is read again from its stored variant on the next render ([#421](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F421), [#422](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F422))\n",{"part":783,"tag":794,"name":794,"publishedAt":795,"url":796,"body":797},"v2.0.0-alpha.27","2026-10-12T02:56:21Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.27","### Upgrade notes\n- Copy `helm\u002Fcwa\u002Ftemplates\u002Fimage-variants-cronjob.yaml`, the `cronjobs.imageVariants` block of `helm\u002Fcwa\u002Fvalues.yaml` and `bin\u002Fdevops\u002Fk8s.sh`: the image variants job now generates in its own Kubernetes Jobs (#153). It runs on production by default again; set `CWA_CI_IMAGE_VARIANTS_PRODUCTION=\"false\"` to turn it off, and delete a `\"true\"` you set for alpha.26. [1c4b9f6](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1c4b9f6a89ff9a032b41a5548e331b7076d90c8f) [d400d1e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd400d1ea431cf94f80b679c19a2da465ac7c99da) [27141f0](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F27141f017e7a0348fc721617e8b769930fd8be12)\n\n### Changed\n- The image variants job generates in one-off Kubernetes Jobs with their own memory limit (`CWA_CI_IMAGE_VARIANTS_MEMORY`, default 1536Mi), repeating with `--max-memory` until nothing is missing, so it can no longer run the serving API pod out of memory (#153). [1c4b9f6](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1c4b9f6a89ff9a032b41a5548e331b7076d90c8f)\n",{"part":783,"tag":799,"name":799,"publishedAt":800,"url":801,"body":802},"v2.0.0-alpha.26","2026-10-12T02:05:06Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.26","### Upgrade notes\n- Update api-components-bundle to 2.0.0-alpha.16 (`composer update components-web-app\u002Fapi-components-bundle`, or copy `api\u002Fcomposer.lock`). No migration, no config change. [30673ac](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F30673ac2e83a57176a09551c59de415994ec2d07)\n- **The `image variants` job is now opt-in on production** (`CWA_CI_IMAGE_VARIANTS_PRODUCTION=\"true\"`). Its first production run ran out of memory in the serving API pod and the site returned 503s (#153). Leave it off on a site with many or large images until it runs as its own Kubernetes Job. Copy `bin\u002Fdevops\u002Fk8s.sh`, `.gitlab-ci.yml` and `.github\u002Fworkflows\u002Fproduction.yml`. If alpha.25's job already failed on your site there's nothing to undo: missing variants are generated on their first read, as before. [ab86132](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fab86132f6f5a0400330ec2ef007e4f0db04ae816)\n- **Corrections to alpha.25's upgrade notes:** don't run `imagine:generate` by hand in the API pod of a site with many or large images either (the same out-of-memory risk). And to delete the old variants, remove every file under the bucket's `cache\u002F` folder that doesn't end in `.webp`, which also covers stored names without an extension: `gcloud storage ls \"gs:\u002F\u002F\u003Cbucket>\u002Fcache\u002F**\" | grep -v '\\.webp$' | gcloud storage rm -I`. Do it after the alpha.25 deploy, whose purge makes pages render with the WebP variants (#153). [ab86132](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fab86132f6f5a0400330ec2ef007e4f0db04ae816)\n\n### Changed\n- api-components-bundle 2.0.0-alpha.16: `imagine:generate --limit` and `--max-memory` stop a run with exit 3 while variants are still missing, so it can be re-run in fresh processes until done; `--max-memory` counts libvips's native memory (#153). [30673ac](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F30673ac2e83a57176a09551c59de415994ec2d07)\n\n### Fixed\n- The `image variants` job reports an out-of-memory kill (exit 137) as such, instead of as a failed variant (#153). [ab86132](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fab86132f6f5a0400330ec2ef007e4f0db04ae816)\n",{"part":777,"tag":804,"name":804,"publishedAt":805,"url":806,"body":807},"2.0.0-alpha.16","2026-10-12T01:56:26Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.16","### Added\n- `imagine:generate --limit=N` and `--max-memory=768M` stop a run cleanly with exit 3, meaning variants are still missing: run it again in a fresh process until it exits 0. `--max-memory` checks the process's resident memory (`VmRSS`), which counts libvips's native allocations that `memory_limit` cannot see, and every run generates at least one variant. The libvips operation cache is switched off for the run ([#418](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F418), [#419](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F419))\n",{"part":783,"tag":809,"name":809,"publishedAt":810,"url":811,"body":812},"v2.0.0-alpha.25","2026-10-12T00:56:57Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.25","> **⚠ Before deploying alpha.25:** set `CWA_CI_IMAGE_VARIANTS_PRODUCTION=\"false\"` on a site with many or large images, and don't run `imagine:generate` by hand in its API pod. The new `image variants` job generates in the serving API pod, and on its first production run it ran out of memory and the site returned 503s ([#153](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F153)). To delete the old variants, remove every file under `cache\u002F` that doesn't end in `.webp`, rather than only `.png`\u002F`.jpg` files. The next release makes the job opt-in.\n\n### Upgrade notes\n- Update api-components-bundle to 2.0.0-alpha.15 (`composer update components-web-app\u002Fapi-components-bundle`, or copy `api\u002Fcomposer.lock`). No migration, no config change. A project's own `#[UploadableField]` can override the new bucket `Cache-Control` with `cacheControl:`\u002F`imagineCacheControl:`, or send none with `false`; the defaults suit tokenised uploads (#151). [5e91c95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5e91c95c93f320341f2c8c6e61457054c38b8d2b)\n- Copy `api\u002Fconfig\u002Fpackages\u002Fliip_imagine.yaml` (or add `format: webp` and `quality: 80` to each filter set of your own), so every image variant is WebP (#149), and the new post-deploy job: `generate_image_variants` in `bin\u002Fdevops\u002Fk8s.sh` plus the `image variants \u003Ctrack>` jobs and the warm jobs' `needs` in `.gitlab-ci.yml`, or the \"Generate image variants\" steps in `.github\u002Fworkflows\u002Fci.yml` and `production.yml` (#152). The next production deploy then generates every image's WebP variants in the API pod and purges the HTTP cache by itself. Without the job, run it by hand after the deploy, in the API pod: `bin\u002Fconsole silverback:api-components:imagine:generate --dry-run` (how many are missing), `bin\u002Fconsole silverback:api-components:imagine:generate` (exit code 1 means a variant failed; `-v` lists which; on a large site `--filter=general` or `--class='App\\Entity\\Image'` narrows a run), then `bin\u002Fconsole silverback:api-components:purge-http-cache`. [4d36b95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4d36b95cd2d5b55b95d2d44fde65e5bfd4dc2e6a) [eef09ea](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Feef09ea786f06db0c264ccddc3a6db03f956ad35)\n- Then delete the old variants: only the `.png`\u002F`.jpg`\u002F`.jpeg`\u002F`.gif` files under the bucket's `cache\u002F` folder (`liip:imagine:cache:remove` would also delete the new WebP ones). [4d36b95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4d36b95cd2d5b55b95d2d44fde65e5bfd4dc2e6a)\n- Then back-fill `Cache-Control` on the files already in the bucket, which keep GCS's one-hour default (only new writes get the header). With gcloud, in this order so variants end with the 30-day value: `gcloud storage objects update \"gs:\u002F\u002F\u003Cbucket>\u002F**\" --cache-control=\"public, max-age=31536000, immutable\"`, then `gcloud storage objects update \"gs:\u002F\u002F\u003Cbucket>\u002Fcache\u002F**\" --cache-control=\"public, max-age=2592000\"`. A CDN in front of the bucket (the `GCLOUD_PUBLIC_URL` host) keeps its copies until they expire or are purged (#151). [5e91c95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5e91c95c93f320341f2c8c6e61457054c38b8d2b)\n\n### Added\n- After each production deploy, a new `image variants` job generates any image variants not stored yet (a new filter, or #149's WebP) in the API pod, before the cache warm, and purges the HTTP cache only if it made any, so visitors' requests no longer generate them. `CWA_CI_IMAGE_VARIANTS_PRODUCTION` is on unless `\"false\"`; `CWA_CI_IMAGE_VARIANTS_STAGING`, `_REVIEW` and `_CANARY` are off unless `\"true\"`. A failure never fails the deploy (#152). [eef09ea](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Feef09ea786f06db0c264ccddc3a6db03f956ad35)\n\n### Changed\n- CI image builds keep their whole build cache (`mode=max`), so an app build with an unchanged lockfile skips `pnpm fetch` and the base setup, and a new branch's first build reuses the default branch's cache instead of starting cold. Projects get it by copying `bin\u002Fdevops\u002Fk8s.sh`. [9dd4d2b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F9dd4d2bdeff09e1c4cd9f0c75e2697f009a5f5d0)\n- @cwa\u002Fnuxt 2.0.0-alpha.11: the confirm dialog library, the orphan report requests, the page cache warm reader and the Mercure client no longer load in every visitor's entry, visitors' pages stop preloading admin-only components, and the default OG image's logo is white and no longer cut off. [f5b719d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff5b719d52124d3b8495affeb8d4280ab7bda8a87)\n- api-components-bundle 2.0.0-alpha.15: uploads and image variants in the bucket get a long `Cache-Control` (a year for originals, 30 days for variants), and `silverback:api-components:imagine:generate` builds missing variants ahead of time (#151). [5e91c95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5e91c95c93f320341f2c8c6e61457054c38b8d2b)\n- Every image variant (`thumbnail`, `general`, `large`) is WebP, whatever the upload's format, so PNG logos and screenshots are no longer served as PNG (a 128 KB logo variant is now 41.5 KB) (#149). [4d36b95](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4d36b95cd2d5b55b95d2d44fde65e5bfd4dc2e6a)\n\n### Fixed\n- App image builds no longer hang when Alpine's package mirror stalls: `apk add` is time-limited and retried, with a fallback mirror (copy `app\u002FDockerfile`). [265fa3b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F265fa3bc7efd16d0cff924247d9bd157e0cf7af7)\n",{"part":814,"tag":815,"name":815,"publishedAt":816,"url":817,"body":818},"module","v2.0.0-alpha.11","2026-10-11T23:56:47Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.11","- The confirm dialog library is loaded only when a dialog is shown, not for every visitor ([#382](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F382), [6b3be663](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F6b3be663))\n- The orphan report requests and the page cache warm reader load on first use, out of every visitor's entry ([#382](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F382), [70d3fc5e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F70d3fc5e))\n- The Mercure client loads once the page has hydrated instead of in every visitor's entry; visitors' live updates are unchanged ([#382](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F382), [bc72d93b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fbc72d93b))\n- Anonymous visitors' pages no longer preload the admin's per-component manager or the empty-position placeholder ([#382](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F382), [a6689f7d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fa6689f7d))\n- The default OG image shows the CWA logo in white instead of black, and is rendered 630px high so the site name at the bottom is no longer cut off ([#372](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F372), [01e03ccb](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F01e03ccb))\n",{"part":777,"tag":820,"name":820,"publishedAt":821,"url":822,"body":823},"2.0.0-alpha.15","2026-10-11T22:56:29Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.15","### Breaking\n- Uploads and imagine variants on Google Cloud Storage and S3 are written with a `Cache-Control`: `public, max-age=31536000, immutable` for originals and `public, max-age=2592000` for variants, configured by `silverback_api_components.uploadable.cache_control.original`\u002F`.imagine` (`null` for none) and per field by `#[UploadableField(cacheControl:, imagineCacheControl:)]` (`false` for none). Files stored earlier keep theirs until updated in the bucket. `UploadableFileManager` takes an `UploadableCacheControl` ([#414](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F414), [#415](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F415))\n\n### Added\n- `silverback:api-components:imagine:generate` generates every imagine variant not stored yet, one image at a time, so adding a filter no longer makes visitors' requests generate them, with the field's `imagineCacheControl`: run it as a one-off Job after the deploy, then purge the HTTP cache. `--filter` and `--class` narrow it (an unknown one exits 2), `--dry-run` counts what is missing, `-v` lists each variant; it exits 1 if a variant failed. `ImagineFilterGenerator::filter()` reports whether a filter was resolved, skipped over the memory budget or failed ([#413](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F413), [#416](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F416))\n",{"part":783,"tag":825,"name":825,"publishedAt":826,"url":827,"body":828},"v2.0.0-alpha.24","2026-10-11T13:04:44Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.24","### Upgrade notes\n- Copy `helm\u002Fcwa\u002Ftemplates\u002Fpwa-deployment.yaml` and `helm\u002Fcwa\u002Ftemplates\u002Fdeployment.yaml` (or just their `lifecycle.preStop` blocks) and deploy, to stop 502s during deploys. The first deploy with them still rolls the old pods without the fix; every deploy after it is clean. [4932989](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4932989bca167be2102adce72655f14e4523417d)\n\n### Fixed\n- Deploys no longer give 502s while pods roll: the PWA holds 5s in a preStop before Nitro closes its listener, and the API's preStop waits 5s instead of 1s, so neither stops serving while it is still an endpoint (#148). [4932989](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4932989bca167be2102adce72655f14e4523417d)\n",{"part":783,"tag":830,"name":830,"publishedAt":831,"url":832,"body":833},"v2.0.0-alpha.23","2026-10-11T01:47:11Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.23","### Upgrade notes\n- Update api-components-bundle to 2.0.0-alpha.14 (`composer update components-web-app\u002Fapi-components-bundle`, or copy `api\u002Fcomposer.lock`); no migration. Only a site with AVIF, HEIC or HEIF uploads is affected: their variants are generated again as WebP on first read, and the old `.avif`\u002F`.heic`\u002F`.heif` variant files are left in the bucket unused. To delete them, run `bin\u002Fconsole liip:imagine:cache:remove` in the API pod, which removes every filter's variants, so each image is regenerated on its next read (expect a burst, as after the `large` filter in alpha.22). Leaving them costs only storage. [f238cbe](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff238cbe17964c8f667c627217b0afee14d55b19a)\n\n### Added\n- The API image also builds Souin's Redis store (go-redis, patched with darkweak\u002Fstorages#65 so pods sharing Redis purge every page under a tag), ready for scaled mode; nothing uses it yet, and `bin\u002Ftest\u002Fsouin-redis-shared-purge.sh` runs in the unit-tests job with a Redis service (#85). [70943ad](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F70943ad3d95e7fc9eda84708ec3a1d6b0bdd9084)\n\n### Changed\n- api-components-bundle 2.0.0-alpha.14: variants of AVIF, HEIC and HEIF originals are WebP (vips can't encode those formats). [f238cbe](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff238cbe17964c8f667c627217b0afee14d55b19a)\n\n### Fixed\n- An image whose filter fails to generate (a codec the driver lacks, a corrupt file) is served as the original and logged, instead of a 500 for the image and every page containing it; such uploads no longer fail either (api-components-bundle#410). [f238cbe](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff238cbe17964c8f667c627217b0afee14d55b19a)\n- The `Image` placeholder fades for an image that loaded before hydration, instead of covering it (#146). [fc67690](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ffc67690a353c7ceee6b8184eedf92b8e29f37f80)\n",{"part":777,"tag":835,"name":835,"publishedAt":836,"url":837,"body":838},"2.0.0-alpha.14","2026-10-11T01:36:55Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.14","### Breaking\n- Imagine variants of an AVIF, HEIC or HEIF original are saved as WebP (`cache\u002F\u003Cfilter>\u002Fphoto-1a2b3c4d.webp`) unless the filter set has a `format`, on every driver: vips cannot encode them and other drivers often cannot. Variants an earlier version stored for such originals under `.avif`\u002F`.heic`\u002F`.heif` are no longer used and are generated again as WebP; remove the old files with `bin\u002Fconsole liip:imagine:cache:remove` (or `--filter=\u003Cfilter>` for each filter used on such images). The bundle replaces `liip_imagine.filter.manager`'s class with `Silverback\\ApiComponentsBundle\\Imagine\\FilterManager` unless the application set its own. `MediaObjectFactory` no longer takes a logger ([#410](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F410), [#411](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F411))\n\n### Fixed\n- An imagine filter that fails to generate (a codec the driver lacks, a corrupt image) is skipped and logged at error, and the original is served, instead of a 500 for the resource holding the image and every page manifest containing it. Uploads of such images no longer fail either ([#410](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F410), [#411](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F411))\n",{"part":783,"tag":840,"name":840,"publishedAt":841,"url":842,"body":843},"v2.0.0-alpha.22","2026-10-10T23:10:57Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.22","### Upgrade notes\n- Update @cwa\u002Fnuxt to 2.0.0-alpha.10 and api-components-bundle to 2.0.0-alpha.13 in the same deploy: the module's \"Publish now\" sends `publishedAt: \"now\"`, which older bundles refuse. [5766b27](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5766b27554c931f0a2c4ccda27c1799dff52f369)\n- The new `large` imagine filter is generated for each existing image the first time it's read without one, so expect a burst of generation after the deploy. A project with its own image fields adds `'large'` to their `imagineFilters` to get the same `srcset`. [1af82fa](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1af82fafc23b07ef40aa82c349e56e73e6c09500)\n\n### Added\n- The `Image` component serves a responsive `srcset` (the module's, from 2.0.0-alpha.10) with `sizes`, and a `large` filter (2400 px) so retina screens don't fetch the original upload. [1af82fa](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1af82fafc23b07ef40aa82c349e56e73e6c09500)\n\n### Changed\n- @cwa\u002Fnuxt 2.0.0-alpha.10: \"Publish now\" publishes by the API's clock (needs bundle 2.0.0-alpha.13, so update both together), `srcset` on `useCwaFile`\u002F`withFile`\u002F`useCwaFileField`, and admin fixes. [5766b27](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5766b27554c931f0a2c4ccda27c1799dff52f369)\n- api-components-bundle 2.0.0-alpha.13: imagine variants with a `format` get that format's extension, an upload is stored with its content's extension, `publishedAt: \"now\"` publishes by the server's clock, and `liip:imagine:cache:remove` no longer throws. [82ae211](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F82ae211d18d27cd8d08fb9eb892d3ccaeed5004b)\n- Cached pages are stored compressed: the cache sits outside `encode` and keys on `Accept-Encoding` normalised to br, gzip or none, so a hit is never recompressed (brotli capped a php pod at ~110 page views\u002Fs); zstd is no longer offered (#145). [3add461](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3add4619edd1e7949b3baadc09e3a8abbe9a942a)\n",{"part":814,"tag":845,"name":845,"publishedAt":846,"url":847,"body":848},"v2.0.0-alpha.10","2026-10-10T22:51:55Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.10","- **Breaking:** immediate publishing (the Publish button, Publish now, a schedule time already past, and adding a component published) sends `\"publishedAt\": \"now\"` so the API publishes by its own clock; needs api-components-bundle 2.0.0-alpha.13 or later ([#381](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F381), [98659370](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F98659370))\n- `useCwaFile`, `withFile` and `useCwaFileField` expose `srcset`: each sized variant of the field (original and imagine filters), ascending by width with the endpoint's `?published=` query, for a responsive `\u003Cimg>` ([#377](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F377), [797a8ff1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F797a8ff1))\n- A component added with `instantAdd` is selected even while text in the previously selected component is still highlighted; a user click during a text selection is still ignored ([#379](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F379), [c5031fa4](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fc5031fa4))\n- A route whose parent's go-live date has already passed no longer says \"A parent route holds this page back\" in the Routes tab ([#378](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F378), [7e1e434f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F7e1e434f))\n- Clicks inside the stranded component groups modal no longer reach the page behind it, which hid the manager and left draft view ([#380](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F380), [eb9940b1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Feb9940b1))\n- Publishing a draft from a browser whose clock is ahead of the API no longer leaves the component showing \"Resource … has not been requested\": a publish is decided from the API's response, not the browser clock, so a draft the API saved as scheduled stays loaded ([#373](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F373), [9e25d3f3](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F9e25d3f3))\n",{"part":777,"tag":685,"name":685,"publishedAt":850,"url":851,"body":852},"2026-10-10T22:43:39Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.13","### Breaking\n- Imagine variants of a filter set with a `format` are stored with that format's extension (`cache\u002F\u003Cfilter>\u002Fphoto-1a2b3c4d.webp`, not `.png`), so they are served with the right `Content-Type`. Existing variants for those filters are generated again under the new name; clear them with `bin\u002Fconsole liip:imagine:cache:remove --filter=\u003Cfilter>` for each such filter, as nothing else removes the old files ([#404](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F404))\n- `ImagineRemoveEvent::$paths` is always an array (empty for every path), and `FileInfoCacheManager::deleteCaches()` with `null` filters deletes only variant rows, keeping the original's row, as the cache resolver only removes variants ([#406](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F406))\n\n### Added\n- `\"publishedAt\": \"now\"` on a publishable POST, PUT or PATCH publishes by the server's clock, so a browser whose clock is ahead of the server no longer leaves \"Publish now\" as a draft scheduled a moment ahead. Only the exact string `now` is resolved; a caller who may not publish still cannot set it, and it is a 422 on `?published=true` like any publication date change ([#408](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F408))\n\n### Fixed\n- `liip:imagine:cache:remove --filter=\u003Cfilter>` and `liip:imagine:cache:remove` no longer fail with a `TypeError` after deleting the files: the file info rows of those filters are deleted for every path, and the originals' rows kept ([#405](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F405), [#406](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F406))\n- `CacheManager::remove()` accepts a single path or filter as a string, as liip's signature allows, instead of throwing a `TypeError` ([#403](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F403), [#406](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F406))\n- An upload is stored with the extension of its content: a PNG uploaded as `holiday.jpg` is stored as `.png`, and one with no extension gets `.png`. A valid extension (`.jpeg`) is kept, and plain text or an unrecognised type keeps the client's extension ([#404](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F404))\n- An imagine variant with no file info row (rows lost, or a bucket shared with another database) reports its real type, size and dimensions instead of `-1` and an empty type: the stored variant is read through `FlysystemCacheResolver` and its row saved ([#404](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F404))\n",{"part":783,"tag":854,"name":854,"publishedAt":855,"url":856,"body":857},"v2.0.0-alpha.21","2026-10-10T22:18:03Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.21","### Upgrade notes\n- Remove every `generate_jwt_keys` line from your `.gitlab-ci.yml` and GitHub workflows: `deploy` now handles the secrets itself, and the function is gone. On GitHub, map an optional `APP_SECRET` secret into the deploy jobs as the template does. [816997e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F816997eaebb7cfb167172e38065042ee5a42f710)\n\n### Fixed\n- Deploys keep `APP_SECRET`, `MERCURE_JWT_SECRET` and the JWT key pair from the live Secret instead of generating new ones, so logins, signed URLs and Mercure cookies survive a deploy; `APP_SECRET` can now be set as a CI variable (#144). [816997e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F816997eaebb7cfb167172e38065042ee5a42f710)\n",{"part":783,"tag":859,"name":859,"publishedAt":860,"url":861,"body":862},"v2.0.0-alpha.20","2026-10-10T20:35:31Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.20","\n### Upgrade notes\n- Copy `liip_imagine.yaml`, `api\u002Fsrc\u002FEntity\u002FImage.php` and the `Image` component (`app\u002Fapp\u002Fcwa\u002Fcomponents\u002FImage\u002FImage.vue`). No regenerate step: each existing image gets its `general` file on the first read after the deploy (about 0.2 s for 12 MP, up to 1 s for a 30 MP PNG, within the imagine budget), and the deploy's cache warm usually does that. Existing thumbnails keep their old, bigger encoding; delete the bucket's `cache\u002Fthumbnail\u002F` folder to have them re-encoded the same way. A published image with no file is left as it is until it is next published or edited. ([#143](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F143), [de6075a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fde6075a46ec1c1d81f6dce7dff349b919f8cc465))\n- **Lower your image upload caps to fit the memory budget:** set `Assert\\Image(maxPixels: …)` on `Image::$file`, and on any other uploadable image field your project adds, to **30 MP** with the vips driver or **20 MP** if you stay on GD (was 40 MP). Larger images no longer fit the 320M imagine budget: they'd be stored with no thumbnails (the original served, a warning logged). The module downscales above 20 MP in the browser, so this mainly affects direct API uploads. ([#136](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F136), [#141](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F141))\n- Imagine now uses libvips: copy `api\u002FDockerfile` (libvips, `ffi`, the `VIPS_*`\u002F`MALLOC_*` ENVs), `conf.d\u002F10-app.ini` and `20-app.dev.ini`, `api\u002Fconfig\u002Fpreload.php` and `preload_vips.php`, `liip_imagine.yaml`, and `composer require rokka\u002Fimagine-vips jcupitt\u002Fvips:^2.6 ext-ffi:*`; rebuild the dev php image. Keep `preload_vips.php` while the driver is vips, or concurrent uploads segfault in worker mode. ([#141](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F141), [8255e8c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8255e8c72529a20dcd0d5106a5fa1cf7cb9b97f4))\n- Bundle 2.0.0-alpha.12: take the migration `Version20261010134316`. It does what `silverback:api-components:deduplicate-file-info` does (originals get an empty filter, duplicate file-info rows are removed) before adding the unique `(path, filter)` index, so no manual step is needed; running the command first is harmless. Remove any `imagine.memory_limit: 320M` you copied from the template: it's now the bundle's default. With GD, sources over about 23 MP get no thumbnails (the original is served and a warning logged). ([11e5c7a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F11e5c7a74af4e0d7668a071462f8ed9a39923b5a))\n- Copy `api\u002Ffrankenphp\u002FCaddyfile`, `conf.d\u002F10-app.ini`, `api\u002Fconfig\u002Fpackages\u002Fsilverback_api_components.yaml` (`imagine.memory_limit`, needs bundle 2.0.0-alpha.11), `api\u002Fsrc\u002FEntity\u002FImage.php`, `bin\u002Fdevops\u002Fk8s.sh` and the chart's `values.yaml`, `_helpers.tpl` and `deployment.yaml`. Replace a `CWA_API_FRANKENPHP_WORKER_NUM` CI variable with `CWA_CI_API_WORKERS` (deploys now refuse it), and check the budget fits any `CWA_CI_API_MEMORY_LIMIT` you set. ([#136](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F136), [1868035](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1868035b41a4edd48d42b84c2de736026c8f5ce6))\n- Bundle 2.0.0-alpha.11 adds `invalid_files` to `_acb_orphaned_file_report`: take the migration `Version20261010125853` (existing report rows get `[]`). ([84de158](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F84de158ea8e8054b10714717028a56f549075e9f))\n- Delete any `NUXT_SITE_URL` CI variable: deploys now set it to `https:\u002F\u002F$DOMAIN` and refuse a CI variable of that name. ([#140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F140), [45a3794](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F45a379404852fc164b15f706d0aac3479c61b6ab))\n- Copy `api\u002Ffrankenphp\u002Fsouin\u002Fv1.7.9-singleflight-5xx.patch` with its `COPY` and `patch` lines in `api\u002FDockerfile` (after the singleflight patch), and `bin\u002Ftest\u002Fsouin-coalesced-5xx.sh` with its line in the unit-tests job. ([#137](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F137), [6df1b15](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6df1b15050970bbc45e8348d10d0a9787477365c))\n- The per-visitor rate limit needs the ingress to keep the client address (`externalTrafficPolicy: Local`); the GKE cluster's controller now does, with 2 replicas and a PDB. A project on another cluster must set the same, or the limit never applies. ([#135](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F135), [85f9c28](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F85f9c286d7fc0da9610a3764623baa4f51c28800))\n\n### Fixed\n- `create-cwa`: declining fixtures removes only the sample content (`AppScaffold.php`, `Parts\u002F`) and keeps `UsersFixture.php`, so `doctrine:fixtures:load` (and the deploy's fixtures job) still creates the admin from `ADMIN_*`. The README and closing message say so. ([#142](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F142), [bc55a2f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fbc55a2fe539379c1e7b2b57e5b7aead5033aba12))\n- Cached pages' canonical URL and `og:url` use the site's host, not whichever host rendered the page: deploys set `NUXT_SITE_URL` from `DOMAIN` (so `project.sh` sites and staging get their own). ([#140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F140), [45a3794](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F45a379404852fc164b15f706d0aac3479c61b6ab))\n- `PURGE`\u002F`BAN` sent to the site return 405 instead of a misleading 200 (they never purged anything; the `CACHE_ACL` clause they passed through was unreachable and is gone). Purge one page by tag through Souin's admin API; see CLAUDE.md. ([#138](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F138), [1c87cc0](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1c87cc0e0ea92b26c16bb846df36b5e659ca8f93))\n- Requests Souin coalesces onto an upstream 5xx get that 5xx instead of an empty 200 (a new Souin patch, `v1.7.9-singleflight-5xx.patch`). ([#137](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F137), [6df1b15](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6df1b15050970bbc45e8348d10d0a9787477365c))\n\n### Changed\n- Images: a `general` filter (1200 px) that the `Image` component renders (it showed the 500 px thumbnail, at most 300 px wide); filters encode JPEG at quality 82 (was 90) and PNG at level 9 (was 0, uncompressed), so a 12 MP photo's display file is 91 KB, not 172 KB; publishing an image without a file is refused (`requiredOnPublish`). ([#143](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F143), [de6075a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fde6075a46ec1c1d81f6dce7dff349b919f8cc465))\n- `app\u002F.dockerignore` also leaves out local env files (`.env`, `.env.local`, `.env.*.local`, which `nuxt build` would read), `.pnpm-store\u002F`, `node-compile-cache\u002F`, logs, OS files, the Dockerfile and compose files, so local builds match CI's. ([#139](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F139), [2e2ae16](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F2e2ae160dee8e8834dc99038062b9004b494a369))\n- The imagine driver is libvips (FFI, `ffi.enable = true`, untrusted loaders blocked) instead of GD: a 12 MP thumbnail takes ~200 ms and ~50 MB instead of ~320 ms and 140 MB, and large originals no longer exhaust `memory_limit`. Image uploads are accepted up to 30 MP. `driver: gd` in `liip_imagine.yaml` switches back (then lower the cap to 20 MP). ([#141](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F141), [8255e8c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8255e8c72529a20dcd0d5106a5fa1cf7cb9b97f4), [903b0a3](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F903b0a3d06bcb93d838382ff1fb7d2e82a3eb873))\n- `.gitignore` ignores `.claude` (Claude Code's local settings and worktrees). ([3cd1e20](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3cd1e20f4e3e80a057d52ab5fc527141c13f9b7c))\n- `@cwa\u002Fnuxt` 2.0.0-alpha.9 (admin): reordering applies once per move even if a component group is mounted twice; the orphaned files page shows invalid files (needs bundle 2.0.0-alpha.11+); a failed background refresh no longer turns on-screen components into error notices; the Info tab's delete button says Delete Draft \u002F Delete Live. ([fc37313](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ffc37313f933c6b101fe67ed100349107b91f03c9))\n- Bundle 2.0.0-alpha.12: a unique index on imagine file info, and `imagine.memory_limit` defaults to 320M, so the template no longer sets it. ([11e5c7a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F11e5c7a74af4e0d7668a071462f8ed9a39923b5a))\n- php requests run under `memory_limit` 128M (`FRANKENPHP_MEMORY_LIMIT`; console commands 256M), imagine filters under 320M, and `GOMEMLIMIT` is what that budget leaves Go (160MiB of 1Gi) instead of 80% of the limit. `values.yaml` states the budget; `CWA_CI_API_WORKERS`, `CWA_CI_API_PHP_MEMORY_LIMIT`, `CWA_CI_API_IMAGINE_MEMORY_LIMIT` and `CWA_CI_API_RESERVED_MEMORY` set it, and the deploy fails before the cluster when it doesn't fit. ([#136](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F136), [1868035](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1868035b41a4edd48d42b84c2de736026c8f5ce6))\n- `Image::$file` accepts up to 30 MP (was 40): what the 320M imagine budget can thumbnail with vips (20 MP with GD). ([#136](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F136), [1868035](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1868035b41a4edd48d42b84c2de736026c8f5ce6))\n- Bundle 2.0.0-alpha.11: imagine filters are generated within a memory budget (oversized ones skipped, not fatal), uploadables no longer save `NULL` filenames, the orphan report lists invalid files, and `purge-http-cache --path`\u002F`--tag` purge one page or tag. ([84de158](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F84de158ea8e8054b10714717028a56f549075e9f))\n",{"part":814,"tag":864,"name":864,"publishedAt":865,"url":866,"body":867},"v2.0.0-alpha.9","2026-10-10T13:49:02Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.9","- Move up \u002F Move down move a component one place even when its component group is on screen more than once: the reorder queue is held once per group instead of per mounted group, so each move is applied and sent once ([#374](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F374), [d2aa2579](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fd2aa2579))\n- The orphaned files page lists **Invalid files**: files in use that break their upload field's current rules, each with a link to its resource, the field, path, adapter and every violation. They are report-only and never deleted, and the settings notice counts them. Needs api-components-bundle 2.0.0-alpha.11 or later ([#376](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F376), [09450e77](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F09450e77))\n- A failed background re-fetch (after a Mercure reconnect or an update message) no longer turns on-screen components into \"Error loading resource … Unknown error\" notices: a loaded resource keeps its last data unless the API answers 4xx ([#375](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F375), [d998476a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fd998476a))\n- The manager's Info tab names the version a delete removes: **Delete Draft** for a draft and **Delete Live** for the live version of a publishable component; other resources still say **Delete** ([#371](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F371), [fd3b4d97](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Ffd3b4d97))\n",{"part":777,"tag":869,"name":869,"publishedAt":870,"url":871,"body":872},"2.0.0-alpha.12","2026-10-10T13:29:56Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.12","### Breaking\n- Upgrade step: the imagine file info table (`_acb_imagine_cached_file_metadata`) gets its `unique_cache_item` index on `(path, filter)` and a `NOT NULL DEFAULT ''` `filter` column. Run `silverback:api-components:deduplicate-file-info` first (gives originals an empty filter and removes duplicate rows; safe to run again), then generate and run a migration ([#399](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F399))\n\n### Changed\n- `silverback_api_components.imagine.memory_limit` defaults to `320M` instead of `512M`. With GD, sources over about 23 MP now get no imagine filters (the original is served and a warning logged); set it higher, or use the vips driver, to filter larger images ([#400](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F400))\n\n### Fixed\n- The file info table's unique index on path and filter now exists: it was declared where Doctrine ignores it, and originals are stored with an empty filter so the index covers them on every database. Caching file info a concurrent request already saved keeps that row ([#399](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F399))\n",{"part":777,"tag":704,"name":704,"publishedAt":874,"url":875,"body":876},"2026-10-10T12:55:05Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.11","### Breaking\n- `MediaObjectFactory` and `UploadableFileManager` take an `ImagineFilterGenerator` (`$imagineFilterGenerator`) instead of liip's `FilterService`; an application that constructs or decorates them must pass the generator ([#397](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F397))\n\n### Added\n- `silverback_api_components.imagine.memory_limit` (default `512M`, `null` to disable): imagine filters are generated with `memory_limit` raised to it and restored afterwards, so an application can run its workers with a lower global `memory_limit` ([#397](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F397))\n- `silverback:api-components:purge-http-cache --tag \u003Ctag> --path \u003C\u002Fpath>` (both repeatable) purges only those tags through the configured purger, never a full flush: `--path` purges the tag a write to the route at that path purges (`\u003Croute import prefix>\u002F_\u002Froutes\u002F\u003Cpath>`), whether or not a route exists there. With Souin that is a surrogate-key purge, which also reaches a CDN that purges by tag. Exits 1 when the purge fails, 2 for a tag containing a space or comma, and 0 with nothing purged when no purger is configured ([#396](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F396))\n- Invalid files in the orphaned file report: `invalidFiles` lists each referenced stored file that breaks its uploadable field's current `File`\u002F`Image` constraints (`Default` group, composites such as `When` honoured) with its resource, field, path and violation messages, checked from cached file info or, without a row, from the object without decoding it; report only, never modified or deleted. Upgrade step: new `invalid_files` column on `_acb_orphaned_file_report`; generate and run a migration ([#391](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F391))\n\n### Fixed\n- An imagine filter whose source is too large to generate within the memory budget is skipped and logged instead of exhausting PHP's memory, on read and on upload; the response still carries the original. The estimate uses the measured cost of the configured driver (GD, Imagick, vips) ([#397](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F397))\n- Reading uploadables no longer saves `NULL` filenames: the uploadable normalizer stopped nulling the file fields on the entity, and caching file info (for a file seen for the first time, or an imagine filter generated on read) inserts only its own row instead of flushing every pending change. A page whose request generated a filter or cached file info lost the images of the components serialised before it ([#393](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F393))\n",{"part":783,"tag":878,"name":878,"publishedAt":879,"url":880,"body":881},"v2.0.0-alpha.19","2026-10-10T10:13:19Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.19","\n### Upgrade notes\n- Copy `bin\u002Fdevops\u002Fsetup.sh`, `k8s.sh`, the warm cache and performance audit jobs in `.gitlab-ci.yml` (they pass no URL now) and, on GitHub, `performance-audit.yml`. A deploy job with neither an environment url nor a `DOMAIN` now fails instead of deploying to `test-domain.com`. ([#132](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F132), [#134](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F134), [7b26140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7b26140895c524ad5083e48470883bffd99d3410))\n\n### Changed\n- The bundle's `website_name` (the name in emails) comes from `WEBSITE_NAME`, default `New Website` in `api\u002F.env`; set `CWA_API_WEBSITE_NAME` on deploys, or export it per site from `bin\u002Fdevops\u002Fproject.sh`. ([GitLab #6](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fissues\u002F6), [c1a3201](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc1a3201e24378ef54e7bd64393110bb6b1a0cfce))\n\n### Added\n- `bin\u002Fdevops\u002Fproject.sh` can add reserved names (ones its own chart values set) with `SITE_ENV_RESERVED_API_EXTRA` \u002F `SITE_ENV_RESERVED_PWA_EXTRA`. ([#133](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F133), [7b26140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7b26140895c524ad5083e48470883bffd99d3410))\n\n### Fixed\n- `.gitlab-ci.yml` loses comments the comment trim left pointing at nothing. ([a628710](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fa6287100e0c3b9dee2330c6590c07c5f6d2cee7c))\n- The warm cache and performance audit use `https:\u002F\u002F$DOMAIN`, so they follow a `DOMAIN` set in `project.sh`. ([#132](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F132), [7b26140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7b26140895c524ad5083e48470883bffd99d3410))\n- `check_kube_domain` can fail: no `test-domain.com` fallback, and an empty `DOMAIN` or one with a leading or trailing dot (an empty `KUBE_INGRESS_BASE_DOMAIN`) stops the deploy. ([#134](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F134), [7b26140](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7b26140895c524ad5083e48470883bffd99d3410))\n",{"part":783,"tag":883,"name":883,"publishedAt":884,"url":885,"body":886},"v2.0.0-alpha.18","2026-10-10T00:08:28Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.18","\n### Changed\n- Bundle 2.0.0-alpha.10, `symfony\u002Fmercure-bundle` 0.6 and `symfony\u002Fmercure` 0.9 ([api-components-bundle#384](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F384)). `mercure.yaml` pins `cookie_name: '__Secure-mercure_access_token'`: mercure-bundle 0.6 drops the prefix when `kernel.debug` is on, and the hub wouldn't read it. ([b71fd4d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fb71fd4d1f6193ab6c80fd05c07a9cb3e576414fe))\n- Comments are one or two lines; the reasoning lives in issues, this changelog and CLAUDE.md. Comment lines only, so downstream copies can take the files as they are. ([#131](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F131), [5f81e4c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5f81e4c6b80fc68f98f6e5464dbac52ce612e65e), [42b2163](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F42b21638d481b87918959f85a83af4716a432f4e), [f7dfc96](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff7dfc9646db57f8c6a2bbe3dbbaf23147aa65fb5), [ac49bda](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fac49bda4657cc4ecf17bd3f53a1729e9af61fc6c), [90ca38d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F90ca38d75185d37aa47c90e5d5d587428ad13131), [a76d7a4](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fa76d7a4f5e2073aae835908c17d7d5f14f9ce316))\n- API Platform 5.0.3, Doctrine ORM 3.7.5, liip\u002Fimagine-bundle 2.18.1, Nuxt 4.6.1, satori 0.47 (for nuxt-og-image), `@nuxt\u002Fdevtools` 4.0.0-beta.4 and patch updates. ([30ef432](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F30ef432becfe328d9c57357aecf2fbca1005974b))\n\n### Added\n- Load test: `page_views` (a page load or a client-side navigation), the resource manifest's resources fetched on each navigation (`MAX_RESOURCES`, default 20), a Cloudflare `cf-cache-status` line, `RANDOM_SEED` for repeatable runs, and `ORIGIN_IP` to measure the origin without the CDN. ([!13](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F13))\n- Deploy hooks, so a project keeps `bin\u002Fdevops\u002Fsetup.sh` and `k8s.sh` unedited: an optional `bin\u002Fdevops\u002Fproject.sh` (sourced by `setup.sh`; it can set `DOMAIN` and site settings) and its `project_values \u003Ctrack>` (chart values, applied last). `check_kube_domain` and `persist_environment_url` work from `DOMAIN`, and the latter also writes `environment_url.env` (`SITE_ENVIRONMENT_URL`). ([!14](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F14))\n",{"part":777,"tag":679,"name":679,"publishedAt":888,"url":889,"body":890},"2026-10-09T23:28:58Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.10","### Changed\n- `symfony\u002Fmercure` ^0.9 and `symfony\u002Fmercure-bundle` ^0.6 are allowed alongside ^0.8 and ^0.5 ([#385](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F385))\n",{"part":783,"tag":892,"name":892,"publishedAt":893,"url":894,"body":895},"v2.0.0-alpha.17","2026-10-09T22:50:45Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.17","\n### Upgrade notes\n- Copy `run_test_functional` again: it unsets `JWT_SECRET_KEY`, `JWT_PUBLIC_KEY` and `JWT_PASSPHRASE`, whose CI values (key contents) failed the functional tests and printed the encrypted key in the log. If that happened, erase the job log, or rotate the keypair. ([#130](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F130), [c1d2581](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc1d2581a2c1478d618765474b5d5808857b98868))\n",{"part":783,"tag":897,"name":897,"publishedAt":898,"url":899,"body":900},"v2.0.0-alpha.16","2026-10-09T16:15:59Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.16","\n### Upgrade notes\n- **Rename your CI variables before or with the next deploy: the old names are no longer read.** ([3a4ab9a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3a4ab9a2996c7b2639c93fa3f2afd9d90c157185), [1960c43](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1960c431e3688591ce1d87e5f11d6ffdb541c9c4), [151fa6f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F151fa6fb4e46b62fe711783f7f078f4064183d64), [d64c278](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd64c278997770b07fb0c2d04ed2fa7383059ab03))\n  - `CWA_CI_` + the same name: `BUILD_DISABLED`, `TEST_DISABLED`, `REVIEW_DISABLED`, `STAGING_ENABLED`, `CANARY_ENABLED`, `FIXTURES_PURGE`, `RESET_DATABASE`, `HELM_UNINSTALL`, `DEPLOYMENT_BRANCH`, `TRACE`, `INGRESS_ENABLED`, `CLUSTER_ISSUER`, `TLS_CERTIFICATE_TIMEOUT`, `INGRESS_RATE_LIMIT_*`, `PWA_*` (sizing), `ORPHAN_SCAN*`, `WARM_CACHE_*`, `PERFORMANCE_AUDIT_*`.\n  - `CWA_CI_API_` + the same name: `REPLICA_COUNT`, `AUTOSCALE`, `AUTOSCALE_*`.\n  - `PHP_CPU_REQUEST`, `PHP_MEMORY_REQUEST`, `PHP_MEMORY_LIMIT`, `PHP_GOMEMLIMIT` → `CWA_CI_API_CPU_REQUEST`, `_MEMORY_REQUEST`, `_MEMORY_LIMIT`, `_GOMEMLIMIT`.\n  - `PRODUCTION_API_DISABLED` → `CWA_CI_PRODUCTION_DISABLED`, `CI_DISABLED` (GitHub) → `CWA_CI_DISABLED`, `ENABLE_DATABASE_FIXTURES` → `CWA_CI_FIXTURES_ENABLED`, `LETSENCRYPT_SECRET_NAME` → `CWA_CI_TLS_SECRET_NAME`, `KUBE_INGRESS_WWW_REDIRECT` → `CWA_CI_WWW_REDIRECT`, `DATABASE_IMAGE_TAG` → `CWA_CI_POSTGRES_IMAGE_TAG`, `POSTGRESQL_ENABLED` → `CWA_CI_POSTGRES_ENABLED`.\n  - `CWA_API_` + the same name: `CACHE_QUERY_ALLOWLIST`, `CADDY_TRUSTED_PROXIES`, `CLOUDFLARE_IP_RANGES`, `RATE_LIMIT_*`, `FRANKENPHP_MAX_WAIT_TIME`, `CLOUDFLARE_PURGE_*`, `CADDY_GLOBAL_CONFIG`, `JWT_COOKIE_SAMESITE`, `MERCURE_JWT_ALGORITHM`.\n- Copy `run_test_functional` from `bin\u002Fdevops\u002Fk8s.sh` **and** add `MAILER_DSN=null:\u002F\u002Fnull` to `api\u002F.env.test`: the job unsets `MAILER_DSN` and `MAILER_EMAIL` so a test that sends email can't deliver it through your live relay (every CI variable reaches the test job and beats `api\u002F.env.test`), and the null transport stops it falling back to `.env`'s `smtp-relay`, which CI doesn't have. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- Optional: add a `GITHUB_TOKEN` CI variable (a fine-grained token with no permissions is enough) and copy the composer step from `api\u002FDockerfile`, `build_api` from `bin\u002Fdevops\u002Fk8s.sh`, the `COMPOSER_AUTH` block at the end of `bin\u002Fdevops\u002Fsetup.sh` and the build step's `GITHUB_TOKEN` in `.github\u002Fworkflows\u002Fci.yml`, so composer installs stop failing on GitHub's anonymous rate limit. Without the token nothing changes. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- Delete a `VARNISH_TOKEN` CI variable if you have one: nothing reads it any more. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- Delete any `NUXT_CWA_API_URL`, `NUXT_PUBLIC_CWA_API_URL`, `NUXT_PUBLIC_CWA_API_URL_BROWSER` or `NUXT_PUBLIC_CWA_ENVIRONMENT` CI variable: the deploy now fails on names the chart sets. Copy `bin\u002Fdevops\u002Fk8s.sh`, the chart and, on GitHub, `github-export-secrets.sh` with its step; move site settings from build args to runtime env. ([#125](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F125)) ([497e8ac](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F497e8ac74cf3ba8b066982df0192bff238abd906))\n- Rename `POSTGRES_USERNAME` to `POSTGRES_USER`. `POSTGRES_USER`\u002F`_PASSWORD`\u002F`_DB` now take effect in-cluster (it always ran as `example` \u002F `!ChangeMe!` \u002F `api`): if you set them, a new user or database restarts PostgreSQL empty, and a new password alone needs `kubectl delete pod \u003Crelease>-postgresql-0`. ([e15f168](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe15f16818a61f9a3f68697808bea5998bf34b0dc))\n- Fix the seeded layout once (fixtures keep existing rows): set the `main` layout's UI component to `CwaLayoutPrimary` in the admin, or `UPDATE _acb_layout SET ui_component = 'CwaLayoutPrimary' WHERE ui_component = 'CwaLayoutCwaLayoutPrimary';` then `bin\u002Fconsole silverback:api-components:purge-http-cache`. ([#126](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F126), [6c0b9d8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6c0b9d8514e64ea42e4162eeeda8f50dbb582ce3))\n\n### Added\n- Site settings without template edits: `NUXT_*` CI variables reach the PWA, `CWA_API_\u003CNAME>` the API as `\u003CNAME>`, through `envFrom`. ([#125](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F125)) ([497e8ac](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F497e8ac74cf3ba8b066982df0192bff238abd906))\n- `CWA_ENVIRONMENT` \u002F `NUXT_PUBLIC_CWA_ENVIRONMENT` name the environment from the deploy's track. ([#125](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F125)) ([497e8ac](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F497e8ac74cf3ba8b066982df0192bff238abd906))\n- GitHub deploys export `NUXT_*` and `CWA_API_*` secrets, masked, without a workflow edit per secret. ([#125](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F125)) ([75a416b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F75a416bc14edcdd601918bf44fa291f391abcbcf))\n- Composer authenticates to github.com with an optional `GITHUB_TOKEN`, passed to the API build as a build secret, with at most 6 parallel downloads. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- Optional in-cluster PostgreSQL persistence (`CWA_CI_POSTGRES_PERSISTENCE`, `_PERSISTENCE_SIZE`, `CWA_CI_POSTGRES_STORAGE_CLASS`). A deploy that would change a live volume stops first and says what to run; review cleanup deletes the review volume. ([7675190](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F767519054d9b8473a42eb30144ea6eaeae65d39a))\n\n### Changed\n- The template's CI settings are `CWA_CI_*`. ([3a4ab9a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3a4ab9a2996c7b2639c93fa3f2afd9d90c157185), [1960c43](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1960c431e3688591ce1d87e5f11d6ffdb541c9c4), [151fa6f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F151fa6fb4e46b62fe711783f7f078f4064183d64))\n- The API's optional runtime settings are set as `CWA_API_*` through the passthrough instead of their own chart wiring; an empty `CWA_API_` value is left out. ([d64c278](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd64c278997770b07fb0c2d04ed2fa7383059ab03))\n- A canary release's SSR minimum defaults to 1 pod (production keeps 2). ([#124](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F124), [095c074](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F095c074eba4a660412fad08c74a6415c11591115))\n\n### Fixed\n- The fixtures seed the layout as `CwaLayoutPrimary` (was `CwaLayoutCwaLayoutPrimary`), so pages render through `primary.vue`. ([#126](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F126), [6c0b9d8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6c0b9d8514e64ea42e4162eeeda8f50dbb582ce3))\n- A `create-cwa` project without fixtures no longer keeps fixture classes that reference deleted ones; the fixtures wire themselves with attributes. ([#127](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F127), [78478ed](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F78478ed90647a427646e9305057bb6b8b78a0fd2))\n- GitLab creates the `main` pipeline with production switched off and fixtures on. ([e7d1be3](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe7d1be335105348503aaf74e0407598756d34760))\n- A downstream functional test that sends email no longer reaches the live mail relay (`.env.test` uses the null transport), and tests that sign in get a JWT keypair. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- The registry pull secret is applied rather than replaced with `--force`, so releases that share a namespace don't race (\"already exists\"). The first deploy after this prints a harmless one-off warning about a missing `last-applied-configuration` annotation. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- `helm lint` and `helm template` work on the chart's own defaults: `jwt-passphrase` defaults to `\"\"` instead of failing `b64enc`. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n- Release steps tag with `--cleanup=verbatim`, so a tag message keeps the changelog's `###` headings. ([56f3065](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F56f3065abea729f33375ee5db664510114fed341))\n- `POSTGRES_USER`\u002F`_PASSWORD`\u002F`_DB` reach the in-cluster PostgreSQL and its `database-url`, now percent-encoded. ([e15f168](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe15f16818a61f9a3f68697808bea5998bf34b0dc))\n- A `KUBERNETES_VERSION` or `HELM_VERSION` CI variable is honoured on GitLab, as on GitHub; `setup.sh` used to overwrite it. ([13243ae](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F13243aea131252dd9be88174b9d57a5b35a44864))\n\n### Removed\n- `apiSecretToken` (`VARNISH_TOKEN`), which nothing in the chart read. ([!12](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F12), [#123](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F123))\n",{"part":783,"tag":902,"name":902,"publishedAt":903,"url":904,"body":905},"v2.0.0-alpha.15","2026-10-08T12:37:36Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.15","Since [v2.0.0-alpha.14](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.14).\n\n### Upgrade notes\n- Update `components-web-app\u002Fapi-components-bundle` to 2.0.0-alpha.9 and `@cwa\u002Fnuxt` to 2.0.0-alpha.8 **together**, and add the migration for the new `is_reachable_without_route` column on `_acb_page` and `_acb_abstract_page_data` (generate your own with `doctrine:migrations:diff`, or copy `Version20261008121224.php` if your schema matches the template's). The module's toggle needs the bundle's column. ([d1849ab](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd1849abb898385590190d470a883c22ff62018fb), [cca6db5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fcca6db54b842e47328c78deb96f455e8ceea7064))\n- Migrations are upward-only: delete `down()` from your migrations, including new ones, so a rollback aborts instead of dropping data. ([3fbfb4e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3fbfb4e142d6a7f20714843ff24c45e1b43c6ffc))\n\n### Added\n- Pages and page data have a **Public without a route** admin toggle (bundle `isReachableWithoutRoute`): a routeless page loaded by a custom fetch IRI, such as a share link, can be read by visitors (api-components-bundle#381, cwa-nuxt-module#369). ([d1849ab](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd1849abb898385590190d470a883c22ff62018fb), [cca6db5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fcca6db54b842e47328c78deb96f455e8ceea7064))\n\n### Removed\n- Every migration's `down()`; Doctrine's inherited one aborts. ([3fbfb4e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3fbfb4e142d6a7f20714843ff24c45e1b43c6ffc))\n\n",{"part":783,"tag":907,"name":907,"publishedAt":908,"url":909,"body":910},"v2.0.0-alpha.14","2026-10-08T12:32:03Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.14","Since [v2.0.0-alpha.13](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.13).\n\n### Upgrade notes\n- **Production certificates are opt-in again:** with no `CLUSTER_ISSUER` variable, deploys use `letsencrypt-staging` (as before alpha.11). Every production site must set `CLUSTER_ISSUER=letsencrypt-prod`; alpha.12's advice that it could be deleted is withdrawn. Check it exists **before** syncing `bin\u002Fdevops\u002Fsetup.sh`. ([5f3f785](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5f3f78521fca025b9d6f9f0fdb8132cbb679b92d))\n- Replace alpha.13's `routeRules` `\u002Fsw.js` block with `registerWebManifestInRouteRules: true` in the `pwa` block of `app\u002Fnuxt.config.ts`. ([e4ebb26](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe4ebb264617e5013ff4b5a70d477c27edd451209))\n\n### Changed\n- An unset `CLUSTER_ISSUER` gives `letsencrypt-staging`, so a misconfigured domain fails against the staging issuer's limits rather than production's; an explicit empty value still turns cert-manager off. ([5f3f785](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5f3f78521fca025b9d6f9f0fdb8132cbb679b92d))\n- The service worker and manifest headers now come from `@vite-pwa\u002Fnuxt` (`public, max-age=0, must-revalidate`, plus the manifest's `Content-Type`) instead of the template's own rule. ([e4ebb26](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe4ebb264617e5013ff4b5a70d477c27edd451209))\n\n",{"part":814,"tag":912,"name":912,"publishedAt":913,"url":914,"body":915},"v2.0.0-alpha.8","2026-10-08T12:20:54Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.8","- Pages and page data have a **Public without a route** toggle in their settings, so a routeless page loaded by a custom fetch IRI can be read by visitors; it has no effect while the page has a route, and a flagged page shows as public (green) like a routed one. Needs api-components-bundle 2.0.0-alpha.9 or later ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F369), [5a919456](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F5a919456))\n",{"part":777,"tag":699,"name":699,"publishedAt":917,"url":918,"body":919},"2026-10-08T12:06:51Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.9","### Added\n- `isReachableWithoutRoute` on pages and page data (admin-only, default `false`): a page with no route that is flagged is readable as though a live route reached it, so an application can serve it from its own routes (e.g. the module's fetch by IRI) with `routable_security` set. Its components, manifest and ancestors follow, as for a routed page, and its nearest routed ancestor must still be readable. A page with a route ignores the flag. Upgrade step: new `is_reachable_without_route` column on the page and page data tables; generate and run a migration ([#382](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F382))\n",{"part":783,"tag":921,"name":921,"publishedAt":922,"url":923,"body":924},"v2.0.0-alpha.13","2026-10-08T10:44:26Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.13","## [2.0.0-alpha.13](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.13) - 2026-10-08\n\nSince [v2.0.0-alpha.12](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.12).\n\n### Upgrade notes\n- Copy the `routeRules` block (`\u002Fsw.js` with `cache-control: no-cache`) into `app\u002Fnuxt.config.ts`. Then a Cloudflare zone can rely on Cloudflare's default caching of static files: no blanket \"Bypass cache for everything\" rule and no `\u002F_nuxt` exception rule. ([4add7fe](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4add7fe97b9b191b5eec862dd8d50d27b4a70f37))\n\n### Fixed\n- After a deploy, a CDN could serve the previous build's service worker for hours, because Nitro sent `\u002Fsw.js` with no `Cache-Control` and Cloudflare caches `.js` by default. It now goes out `no-cache`. ([4add7fe](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4add7fe97b9b191b5eec862dd8d50d27b4a70f37))\n",{"part":783,"tag":926,"name":926,"publishedAt":927,"url":928,"body":929},"v2.0.0-alpha.12","2026-10-08T09:03:57Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.12","## [2.0.0-alpha.12](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.12) - 2026-10-08\n\nSince [v2.0.0-alpha.11](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.11).\n\n### Upgrade notes\n- **Copy `bin\u002Fdevops\u002Fsetup.sh` before deleting `CLUSTER_ISSUER`.** v2.0.0-alpha.11's `letsencrypt-prod` default never applied, because `setup.sh` set an unset value to `letsencrypt-staging` first, so a site that deleted the variable got an untrusted staging certificate on its next deploy. With this release's `setup.sh` (no `CLUSTER_ISSUER` default) and `k8s.sh`, an unset value gives `letsencrypt-prod`; an empty value still turns cert-manager off. Until you've synced both, keep `CLUSTER_ISSUER=letsencrypt-prod`. ([0225e8c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F0225e8ce48cbed0e49627938d965f335884e2269), [#119](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F119))\n\n### Fixed\n- An unset `CLUSTER_ISSUER` now really gives `letsencrypt-prod`, and `setup.sh` no longer warns about an unset `CORS_ALLOW_ORIGIN` or `TRUSTED_HOSTS`, which `deploy` derives. ([0225e8c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F0225e8ce48cbed0e49627938d965f335884e2269), [#119](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F119))\n",{"part":783,"tag":815,"name":815,"publishedAt":931,"url":932,"body":933},"2026-10-08T08:23:36Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.11","## [2.0.0-alpha.11](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.11) - 2026-10-08\n\nSince [v2.0.0-alpha.10](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.10).\n\n### Upgrade notes\n- Deploys now default `INGRESS_ENABLED=true`, `CLUSTER_ISSUER=letsencrypt-prod`, `CORS_ALLOW_ORIGIN`, `TRUSTED_HOSTS` and `MERCURE_CORS_ORIGIN` (exact matches for the deploy's own hostname and, in production, `KUBE_INGRESS_ALIAS_DOMAINS`), `DATABASE_SSL_MODE=require` when `POSTGRESQL_ENABLED=false`, and an unset `CADDY_CACHE_CDN_CONFIG` now really means `strategy hard`. Copy `apply_site_defaults` and `site_hosts` from `bin\u002Fdevops\u002Fk8s.sh` and the `caddy-cache-cdn-config` changes in `helm\u002Fcwa`, deploy once, then delete the CI variables that only repeated these values. Keep `CORS_ALLOW_ORIGIN`\u002F`TRUSTED_HOSTS` if another site calls this API from the browser; set `CLUSTER_ISSUER` to an empty value on a cluster without cert-manager. Don't delete `CADDY_CACHE_CDN_CONFIG=strategy hard` before that deploy. ([be14ab0](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fbe14ab050a7f2b7d6bb843ab4dc85982fede316c))\n\n### Fixed\n- An unset `CADDY_CACHE_CDN_CONFIG` replaced the Caddyfile's `strategy hard` with an empty `cdn` block (Kubernetes received a newline), so Souin would keep stale copies of purged responses; every site had to set `strategy hard` by hand. ([be14ab0](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fbe14ab050a7f2b7d6bb843ab4dc85982fede316c))\n",{"part":783,"tag":845,"name":845,"publishedAt":935,"url":936,"body":937},"2026-10-08T07:51:05Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.10","## [2.0.0-alpha.10](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.10) - 2026-10-08\n\nSince [v2.0.0-alpha.9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.9).\n\n### Upgrade notes\n- Production CPU requests are lower: php 200m → 100m, SSR 250m → 100m, and the SSR autoscaler's CPU target 70% → 175%, so a pod is still added at about 175m. A site that sets only `PWA_AUTOSCALE_CPU_PERCENT` or only `PWA_CPU_REQUEST` must set the other to match (request × target ≈ 175m), or SSR scales on ordinary traffic. ([e79ad3b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe79ad3b7fc18feb7f20fcfe8d810da3ef1973c09))\n\n### Changed\n- Production reserves less CPU per site: php and SSR pods request 100m instead of 200m and 250m. With page HTML cached, SSR pods mostly idle; the autoscaler still adds an SSR pod at about one render a second. ([e79ad3b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe79ad3b7fc18feb7f20fcfe8d810da3ef1973c09))\n",{"part":783,"tag":864,"name":864,"publishedAt":939,"url":940,"body":941},"2026-10-07T23:21:01Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.9","## [2.0.0-alpha.9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.9) - 2026-10-08\n\nSince [v2.0.0-alpha.8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.8).\n\n### Upgrade notes\n- Copy the `nitro` block from `app\u002Fnuxt.config.ts`, and the `goMemLimit` lines and `cwa.php.goMemLimit` helper from `helm\u002Fcwa` and `bin\u002Fdevops\u002Fk8s.sh`. ([5e732e9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5e732e9d36688fcbdb59b656f47ccbae1ccfd8e2))\n- Copy the `@cache_tag` matcher and `header @cache_tag` block from `api\u002Ffrankenphp\u002FCaddyfile`, and the `NODE_OPTIONS` line from `app\u002FDockerfile`; if your deploy sets `NODE_OPTIONS`, add `--max-http-header-size=65536` to it. A site whose sitemap is empty, or whose SSR log shows `UND_ERR_HEADERS_OVERFLOW`, needs this. ([3eae185](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3eae1859ff82264773072b57856febb83e420a91))\n\n### Fixed\n- SSR fetches of large API collections no longer fail with `UND_ERR_HEADERS_OVERFLOW` (which left the sitemap empty from about 100 routes): `Cache-Tag` is added only for public hosts, never for SSR's internal ones, and the app image allows 64 KB of response headers. ([3eae185](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3eae1859ff82264773072b57856febb83e420a91), [#118](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F118))\n- php no longer runs out of memory under a burst of first-time visitors. Nitro now precompresses `\u002F_nuxt` assets at build time (`nitro.compressPublicAssets`), so php's Caddy stops brotli-compressing every JS chunk per request, and the php container gets `GOMEMLIMIT` at 80% of its memory limit (`PHP_GOMEMLIMIT` overrides, `off` unsets). ([5e732e9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5e732e9d36688fcbdb59b656f47ccbae1ccfd8e2), [#117](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F117))\n- A deploy now fails before helm, with a message naming the line, when `CADDY_CACHE_CDN_CONFIG` has a `cdn` directive with no value or an unexpanded `$NAME` (e.g. `api_key $CLOUDFLARE_API_TOKEN` when the token is a Protected GitLab variable and the branch isn't protected). Souin panics parsing it, so php crash-looped while helm reported the release deployed. ([f0e62d7](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff0e62d7b2a9dba93aa4b1a6e6a458d89bfdd8153), [#116](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F116))\n",{"part":783,"tag":912,"name":912,"publishedAt":943,"url":944,"body":945},"2026-10-07T19:12:27Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.8","Since [v2.0.0-alpha.7](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.7).\n\n### Upgrade notes\n- Sites with `provider cloudflare` in `CADDY_CACHE_CDN_CONFIG`: replace `api\u002Ffrankenphp\u002Fsouin\u002Fv1.7.9-cloudflare-purge.patch` with the new one (the Dockerfile lines are unchanged apart from a comment) and rebuild php. Cloudflare's purge limit is per account, so when several sites, or staging and production, share an account, give each a share of it with `CLOUDFLARE_PURGE_REQUESTS`, `CLOUDFLARE_PURGE_WINDOW` and `CLOUDFLARE_PURGE_BURST` (or set `CLOUDFLARE_PURGE_PLAN` if the account isn't on Free). To pass them through, copy the `compose.yaml`, `bin\u002Fdevops\u002Fk8s.sh` and `helm\u002Fcwa` changes from the same commit. ([37afcb1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F37afcb10d2e4594c3e36b6f462ab76e3569fdacb))\n\n### Fixed\n- Cloudflare edge purges are no longer lost to Cloudflare's per-account purge rate limit (Free: 5 a minute, burst 25), which left pages stale at the edge for up to a year after a short editing session. The Cloudflare Souin patch now queues purges, deduplicates tags, sends up to 100 per request at the plan's rate, retries a `429` after `Retry-After` (and 5xx or network errors), and turns a backlog too big to send within a minute into one purge everything. Optional settings: `CLOUDFLARE_PURGE_PLAN` (`free` default, `pro`, `business`, `enterprise`), or `CLOUDFLARE_PURGE_REQUESTS` per `CLOUDFLARE_PURGE_WINDOW` and `CLOUDFLARE_PURGE_BURST`; set a lower rate when staging and production, or several sites, share a Cloudflare account. Only used with `provider cloudflare`. ([37afcb1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F37afcb10d2e4594c3e36b6f462ab76e3569fdacb), [#115](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F115))\n",{"part":783,"tag":947,"name":947,"publishedAt":948,"url":949,"body":950},"v2.0.0-alpha.7","2026-10-06T17:15:57Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.7","Since [v2.0.0-alpha.6](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.6).\n\n### Changed\n- `@cwa\u002Fnuxt` 2.0.0-alpha.7: page-data pages no longer show the previous page's dynamic component to visitors after client-side navigation, and pick up live changes to it (cwa-nuxt-module#368). ([e746bd9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe746bd989ba2da1573300e76bbab8e250d30d836))\n\n### Fixed\n- A full flush (\"Purge all cached data\", `purge-http-cache`, fixture loads, and deploys with `provider cloudflare`) no longer closes the Badger or Nuts store, which left nothing cached until php restarted (`BADGER-INSERTION-ERROR`). A fourth Souin patch makes the flush clear the surrogate keys instead of shutting the storage down; `bin\u002Ftest\u002Fsouin-flush-storage.sh` checks otter, badger and nuts in CI. Otter, the default, was never affected. ([8ebf5e9](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8ebf5e994687014d0496da33a209eafd2137cd93), [#114](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F114))\n",{"part":814,"tag":947,"name":947,"publishedAt":952,"url":953,"body":954},"2026-10-06T11:52:29Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.7","- Visitors no longer see the previous page's dynamic component after client navigation between page-data pages, get live page-data component changes, and keep a dynamic position when its component is deleted: a dynamic position is recognised by `_metadata.isDynamicPosition`, since `pageDataProperty` is only sent to admins ([#368](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F368), [60c9635c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F60c9635c))\n",{"part":783,"tag":956,"name":956,"publishedAt":957,"url":958,"body":959},"v2.0.0-alpha.6","2026-10-06T11:03:46Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.6","Since [v2.0.0-alpha.5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.5).\n\n### Upgrade notes\n- In `app\u002Fnuxt.config.ts`, write the PWA `cacheWillUpdate` as a single expression: `async ({ response }) => \u002Fno-store|private\u002F.test(response.headers.get('cache-control') || '') || response.status !== 200 ? null : response`. Workbox joins the function onto one line in `sw.js`, and from Nuxt 4.6.0 (which the next `@cwa\u002Fnuxt` requires) its source keeps no semicolons, so the old multi-statement version fails the build with \"Unable to write the service worker file … Missing semicolon\". Works on Nuxt 4.5 too. ([eac5e7b](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Feac5e7bee474f04347a180e642163f7057e71949))\n\n### Changed\n- `@cwa\u002Fnuxt` 2.0.0-alpha.6 and Nuxt 4.6.0 (the module now requires it). Apply the `cacheWillUpdate` upgrade note above in the same change, or the build fails writing `sw.js`. ([125661d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F125661d07cd8c160e77fd5878e7854854ff39aa7))\n\n### Added\n- CI's unit-tests job runs `bin\u002Ftest\u002Fcaddy-validate.sh`: `frankenphp validate` on the image's Caddyfile for helm's `SERVER_NAME=:80` and compose's multi-port one. `adapt` only parses, so errors raised while modules load (a second unnamed Mercure hub, #113; a `#` inside the cache CEL expression) passed CI and only showed when php didn't start. ([843082a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F843082a54c663a44bc6267314f491647ec94275e), [f3912b0](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff3912b07f766bacf190e3ec57e81a4d0eb9d93d8))\n",{"part":814,"tag":956,"name":956,"publishedAt":961,"url":962,"body":963},"2026-10-06T10:40:42Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.6","- **Breaking:** the minimum Nuxt version is 4.6.0, the first release that keeps layer pages reached through a symlink out of prefetch itself, so the module's workaround for it is removed ([#361](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F361), [6bb0743a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F6bb0743a))\n- **Security:** requires `vue` ^3.5.43, which fixes GHSA-g2v6-rqmx-r4w6 in `@vue\u002Fserver-renderer` ([675cf507](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F675cf507))\n- In development with Vue 3.5.42 or later, selecting a newly added component whose element renders after it mounts outlines that component, not the next one in the group ([532ebbed](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F532ebbed))\n- On Nuxt 4.6, a page that throws while rendering a route the API serves is no longer sent as a cacheable 500: an error render is recognised by Nuxt's `ssrContext.error`, which also covers Nuxt 5's inline error rendering ([#340](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F340), [11f72946](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F11f72946))\n",{"part":783,"tag":965,"name":965,"publishedAt":966,"url":967,"body":968},"v2.0.0-alpha.5","2026-10-05T19:31:16Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.5","Since [v2.0.0-alpha.4](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.4).\n\n### Upgrade notes\n- **Add `name cwa` to the Caddyfile's `mercure` block** (or copy the template's). Without it, php doesn't start in the compose stack on an image with Mercure 1.0.3 or later. ([51ea465](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F51ea465fc34749c1ceabfcf754705cbf4f4b220e), [#113](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F113))\n\n### Fixed\n- php starts again in the compose stack (local dev and `compose.prod.yaml`) on Mercure 1.0.3+: the hub is named, so the `localhost`\u002F`php.local:80`\u002F`php.local:443` servers share one hub instead of being refused as two unnamed hubs. Helm deploys weren't affected. ([51ea465](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F51ea465fc34749c1ceabfcf754705cbf4f4b220e), [#113](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F113))\n",{"part":783,"tag":970,"name":970,"publishedAt":971,"url":972,"body":973},"v2.0.0-alpha.4","2026-10-05T18:08:49Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.4","Since [v2.0.0-alpha.3](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.3).\n\n### Upgrade notes\n- **Rebuild the API image** before deploying this Caddyfile: it uses two new Caddy modules (`query_allowlist`, `rate_limit`), and an old image refuses it. Locally: `docker compose build php && docker compose up -d php`. No new variable is required. If your pages read a query parameter other than `page`, `search`, `order[...]` or `cwa_force`, add it to `CACHE_QUERY_ALLOWLIST`, or SSR stops seeing it. Behind a proxy with public addresses other than Cloudflare (e.g. a Google load balancer), add its ranges to `CADDY_TRUSTED_PROXIES`. ([4cbe05a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4cbe05a949518be7da67995f6d247807e7a39b7f), [#106](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F106))\n- **GitHub projects:** variables you had added by hand to a workflow's `env:` can stay, but are no longer needed. Any repository variable now reaches `k8s.sh`, so check none holds a stale value you were relying on GitHub to ignore. ([8f290d8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8f290d85d234a60da74b0eec6be8294764b7e842), [#91](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F91))\n- The API's liveness probe now checks php over HTTP instead of TCP, so a pod whose php stops answering is restarted after about a minute. That includes a database outage. Rebuild the API image to pick up the patched Souin, and recreate the API pod. ([9b7bca6](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F9b7bca608d019231a9458ea2406a3a4e4d881208), [#104](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F104))\n- **Mercure 1.0 (protocol change):** the API image builds Mercure 1.0, and the template now speaks its protocol: OAuth 2.0 access tokens, `match=` subscriptions, the `__Secure-mercure_access_token` cookie. **Deploy the bundle (2.0.0-alpha.8) and module updates in this release together with the rebuilt API image**: an older module subscribes with `topic=`, which the hub rejects. Copy `api\u002Fconfig\u002Fpackages\u002Fmercure.yaml` (`protocol_version: '1.0'`, the `iss`\u002F`sub`\u002F`client_id` claims) and the Caddyfile's `mercure` block (an `issuer` block, `resource_identifier` pinned to `MERCURE_PUBLIC_URL`). Mercure 1.0 also rejects unknown directives and removed `demo` and `ui`: if `MERCURE_EXTRA_DIRECTIVES` sets either, use `debugger` (dev only). Anything of your own that read the `mercureAuthorization` cookie must change. An image built before this release can't load the Caddyfile. ([28fa2bb](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F28fa2bb2b989fcdff007d84676931ed68e3a922e), [1a9f7a5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1a9f7a5ea2a43f6d314f9c00f18137a241be3567), [#67](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F67), [api-components-bundle#377](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F377))\n- The API uses API Platform's default request handling (`MainController` and the provider\u002Fprocessor chain) instead of Symfony event listeners: `use_symfony_listeners: true` is gone from `api_platform.yaml`. A site with its own code on `EventPriorities` or `kernel.view`\u002F`kernel.request` for API resources must move it to a state provider or processor decorator, or set the flag back. ([fc0afb7](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ffc0afb71bc7274db9cc6e3dd4ff51dbb64874650))\n- **Behat is gone.** API tests are PHPUnit functional tests on API Platform's `ApiTestCase` (`api\u002Ftests\u002FFunctional`, base class `FunctionalTestCase`, which rebuilds the schema per test and refuses a database whose name lacks `test`). CI's `behat tests` job is now `functional tests` (`run_test_functional`; GitHub: `functional-tests`), and `production`\u002F`canary` need it instead. A project with its own Behat features ports them to `tests\u002FFunctional`, or keeps Behat and its `behat.yml`, `features\u002F` and `run_test_behat` itself. ([8885b3c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8885b3c01adafbe6e37263553d84ae9cd572b714))\n- **Cloudflare API rule (only if you added the optional one):** add `and not starts_with(http.request.uri.path, \"\u002F_api\u002F_\u002Fcomponent_positions\u002F\")` and purge everything once. Page-data positions vary by the `path` request header, which Cloudflare ignores, so page-data pages (e.g. blog articles) showed one page's content after client-side navigation. ([39c5f4a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F39c5f4a97d71e2e1edbdb7b5cb38440a4b247ddc), [docs#134](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fdocs\u002Fissues\u002F134))\n\n### Changed\n- With `provider cloudflare`, a full flush (\"Purge all cached data\", `purge-http-cache`, every fixture load) also purges everything at Cloudflare, and deploys flush instead of purging only rendered HTML, so edge-cached `\u002F_api` responses can't outlive a release. No more manual Purge Everything. Rebuild the API image (the Souin Cloudflare patch changed). ([6a8c898](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6a8c8984b1408d48fced26655ad32b83afcbaeac))\n- The `api`, `app`, `helm` and load-test READMEs are current: working docs links (cwa.rocks), the Nuxt 4 and pnpm workflow, and load testing since the query allowlist and rate limit. ([3d38d3a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3d38d3a8638b34d3365e2534e2c8feac4a44edf7), [#112](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F112))\n- Caddy's access log records the visitor's resolved address as `visitor_ip` (behind Cloudflare, `client_ip` is Cloudflare's). The helm chart now requires `mercure.publicUrl` instead of falling back to an `http` URL that Mercure 1.0's `__Secure-` cookie refuses. ([b3b8622](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fb3b8622d3fc1375da564668aab3dea9053e1807f), [#110](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F110), [#111](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F111))\n- `@cwa\u002Fnuxt` `2.0.0-alpha.4` (Mercure 1.0 `match=` subscriptions, the `cwa_force` loop fix, stranded component group and orphaned file admin tools; needs Nuxt `>=4.5.2`), and every PHP and front-end dependency at its latest release, except TypeScript (still `6.0.3`: `vue-tsc` can't run on TypeScript 7) and `justinrainbow\u002Fjson-schema` (5.x, held by `behatch\u002Fcontexts` until Behat was removed; now 6.x). ([109c3e7](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F109c3e7a611fba4da26e34d293d5f2098ce34958))\n- Bundle `2.0.0-alpha.8`: Mercure 1.0 only, a liveness endpoint (`\u002F_\u002Fhealth\u002Flive`), an orphaned file report (new table; the migration is included), file clean-up when uploadable entities are removed. The bundle now works with `use_symfony_listeners` on or off. ([1a9f7a5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1a9f7a5ea2a43f6d314f9c00f18137a241be3567))\n- pnpm 12.9.1 (drops the v11-only `confirmModulesPurge` setting, which pnpm 12 refuses) and PHPUnit 13.4.1. ([e919d06](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe919d069d24b7e74f59395d49772b394a70b036d))\n- `@cwa\u002Fnuxt` `2.0.0-alpha.5`: navigating between page-data pages no longer shows the previous page's content under the new title and then a blank body ([cwa-nuxt-module#368](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F368)); live page-data updates show on other open pages straight away. ([f67cb96](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ff67cb9621f76665ae4c960f9743a91ec4f9de89e))\n\n### Added\n- Origin protection: pages drop query parameters they don't use before the cache (so `\u002F?x=1` is a cache hit), a per-visitor rate limit on cache misses that understands Cloudflare, and FrankenPHP sheds load with a fast 503 instead of queueing into a 504. All on by default, each tunable or off by an optional variable. `compose.prod.yaml` sets `CADDY_EDGE=true`, so a client facing Caddy directly can't dodge the limit with invented `X-Forwarded-For` headers. ([4cbe05a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4cbe05a949518be7da67995f6d247807e7a39b7f), [d0021ba](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd0021ba64992a631fa7afa3c815754bb00f5d71e), [#106](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F106))\n\n### Fixed\n- The API image builds again: FrankenPHP 1.13.0 requires Mercure 1.0.3, so the Mercure 0.24.2 pin is gone. ([28fa2bb](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F28fa2bb2b989fcdff007d84676931ed68e3a922e), [#67](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F67))\n- Souin's Cloudflare purge now works (it called the wrong endpoint and failed silently), accepts a scoped Cache Purge token, and logs failures; responses carry `Cache-Tag` so Cloudflare can purge by tag. Only matters with `provider cloudflare` in `CADDY_CACHE_CDN_CONFIG`; rebuild the php image. ([cd99b55](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fcd99b5541becc7e9b5539c29e2bb929769e97842), [#108](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F108))\n- GitHub deploys now receive every repository and environment variable, as GitLab does, so `INGRESS_ENABLED`, `CLUSTER_ISSUER` and the sizing, Caddy and mail variables take effect. Branch names no longer reach CI scripts unquoted. ([8f290d8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8f290d85d234a60da74b0eec6be8294764b7e842), [#91](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F91))\n- A large file in Nuxt's `public\u002F` (e.g. a PDF brochure) no longer runs the API container out of memory when someone downloads it. Only pages and the sitemap go through the page cache now, and PDFs under `\u002F_api` are really excluded. ([7f84445](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7f84445927f3ff128cc5f2928a22f2a9d9cdd18e), [#105](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F105))\n- One stalled request no longer leaves the API pod unready for good. Souin is patched (darkweak\u002Fsouin#850) so a timed-out call releases its cache key, and the kubelet's probes skip the cache. ([9b7bca6](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F9b7bca608d019231a9458ea2406a3a4e4d881208), [#104](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F104))\n\n### Security\n- Pages no longer keep `cwa_force` in the default `CACHE_QUERY_ALLOWLIST`: an anonymous `?cwa_force=\u003Canything but true>` sent the server render into an infinite redirect loop, pinning an SSR pod until its liveness probe restarted it ([cwa-nuxt-module#366](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F366); the module fix is in its next release). A site that set `CACHE_QUERY_ALLOWLIST` itself should remove `cwa_force` from it. ([3f7130f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3f7130f73ee2cdfab4579a26aefe2e4fd9dc0465))\n- `api\u002F.env`'s `TRUSTED_PROXIES` default trusted `172.0.0.0\u002F8`, which includes public addresses; it's now the private `172.16.0.0\u002F12`. No deployment used the default (compose and the chart set their own). Copy the fix to any project-specific `.env.local`. ([b5aa360](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fb5aa360393fe77f0d2dbfd3ca33fa44bf7fe069b), [#107](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F107))\n",{"part":814,"tag":965,"name":965,"publishedAt":975,"url":976,"body":977},"2026-10-05T15:17:43Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.5","- A live page-data update that swaps a dynamic component now shows on other open pages straight away, instead of after the next navigation ([3938a1f3](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F3938a1f3))\n- On a nested URL, adding a component to a dynamic position on a shallower page-data page writes it to that page's page data, not the routed child's, and its tab lists that page's properties ([bf37fd73](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fbf37fd73))\n- A template page shared at two depths now renders each depth's own dynamic components; the console warning says what is left: a shallower depth shows a change to which component its page data holds after the next navigation ([05f4611e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F05f4611e))\n- Navigating between page-data pages no longer shows the previous page's dynamic component under the new title and then a blank body: a dynamic position renders the component the displayed page's manifest resolved for it until it is re-fetched for that path ([#368](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F368), [1e76b367](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F1e76b367))\n- The build warns, naming the file and the reason, when a `\u003CCwaComponentGroup>` (a bound reference at an unrecognised location, a `v-bind` object) or an unreadable file switches stranded component group warnings off for the whole site ([#367](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F367), [ceee52b2](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fceee52b2))\n",{"part":814,"tag":970,"name":970,"publishedAt":979,"url":980,"body":981},"2026-10-04T17:19:39Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.4","- **Security:** `?cwa_force=` with any value other than `true` no longer sends the server render into an endless redirect that pinned an SSR process; the parameter is removed whatever its value ([#366](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F366), [038e1364](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F038e1364))\n- **Breaking:** live updates subscribe with the Mercure 1.0 protocol (`match=*`, and `last_event_id` on reconnect), so the API's hub must be Mercure 1.0 (api-components-bundle 2.0.0-alpha.8); a 0.x hub rejects the subscription ([#364](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F364), [61dd29f8](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F61dd29f8))\n- A `\u003CCwaComponentGroup>` whose location is page data (or any type that cannot own a group) is no longer created or attached, and the admin console warns to pass the page IRI instead: such a group was invisible to visitors, listed as orphaned, and was PATCHed with an `undefined` property on every admin load ([#363](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F363), [0a02879d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F0a02879d))\n- A warning in the admin header counts stranded component groups: groups attached to the page, its layout or a component on it whose reference no `\u003CCwaComponentGroup>` in the app's templates declares, such as the old group left behind when a reference is renamed or a legacy group located at a draft IRI; the declarations are read from the app's Vue files at build time. Its modal lists their components and merges each one to the end of a shown group or deletes it ([#360](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F360), [94481372](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F94481372), [fee09284](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Ffee09284))\n- `\u002F_cwa\u002Forphaned` also reports stored files: orphaned files nothing references, which can be deleted after a confirmation, unknown files the API cannot prove it wrote, which are only listed, and missing files, whose resource can be viewed; site settings scans files separately and counts both reports in its notice. Needs an api-components-bundle release containing #371 ([#362](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F362), [60a0d792](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F60a0d792), [0d76809e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F0d76809e))\n- The module declares its real minimum Nuxt version, `>=4.5.2`, so Nuxt warns an app on an older version instead of failing later ([e0ef82ed](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fe0ef82ed))\n- View on a never-published orphaned component shows its content instead of a 404 ([#359](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F359), [292f7885](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F292f7885))\n- Every `\u002F_cwa` admin page is guarded by the `cwa-admin` middleware: a signed-in non-admin goes home, and a signed-out visitor goes to login from the browser, since with a cross-origin API the server cannot see the session ([dd4f8a6d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fdd4f8a6d), [5be5b635](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F5be5b635))\n",{"part":777,"tag":720,"name":720,"publishedAt":983,"url":984,"body":985},"2026-10-04T14:37:05Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.8","### Breaking\n- `api_platform.use_symfony_listeners` is no longer forced on: the bundle follows API Platform's default (off). An application that needs API Platform's listeners must enable it itself; the bundle works either way ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n- The bundle's API behaviour runs in state provider and processor decorators instead of `EventPriorities` listeners. Removed: `DenyAccessListener`, `ComponentUsageEventListener`, `RouteEventListener`, `DeletedResourceEventListener`, `UploadableEventListener` and the listeners' request\u002Fview methods ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n- Renamed: `EventListener\\Api\\UserEventListener` → `Helper\\User\\UserWriteNotifier` (`postWrite()` → `notify()`, id `silverback.api_components.helper.user.write_notifier`); `EventListener\\Api\\CollectionApiEventListener` → `Helper\\Collection\\CollectionPopulator` (`transform()` → `populate()`, id `silverback.api_components.helper.collection.populator`); the draft merge moved from `PublishableEventListener` to `Helper\\Publishable\\PublishableDraftMerger` (id `silverback.api_components.helper.publishable.draft_merger`) ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n- Upload and download operations no longer use custom controllers; `UploadStateProvider`\u002F`DownloadStateProvider` run them from the read chain ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n- Mercure 1.0 protocol only: subscribe grants are URL Patterns in `authorization_details` (`…\u002F:id\\?`, and `…\u002F:id\\?draft=1` for draft access) and the cookie is `__Secure-mercure_access_token`; `symfony\u002Fmercure` ^0.8 and `symfony\u002Fmercure-bundle` ^0.5 are required. Upgrade steps: set `protocol_version: '1.0'` and `jwt.claims` `iss`\u002F`sub`\u002F`client_id` on the hub in `mercure.yaml`; in the Caddyfile, move the keys into an `issuer \u003Ciss> { publisher { jwt … } subscriber { jwt … } }` block matching `iss`, pin `resource_identifier` to the public hub URL (the tokens' `aud`; publishing through the internal URL is 401 otherwise) and drop `protocol_version_compatibility`; on a plain-HTTP hub URL set a prefix-less `cookie_name` on both sides ([#379](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F379))\n\n### Added\n- `GET \u002F_\u002Fhealth\u002Flive` (`api_components_health_live`): an anonymous, `private, no-store` liveness check that touches no database, cache or remote service, so it stays 200 through a database outage; use it for the liveness probe and `\u002F_\u002Fhealth` for readiness ([#375](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F375))\n- Orphaned file report (admin): `POST \u002F_\u002Forphaned_files\u002Fscan`, `GET \u002F_\u002Forphaned_files` and `silverback:api-components:scan-orphaned-files` report stored files under the uploadable fields' filesystems and prefixes that no row references (older than `orphaned_files.minimum_age`, default one hour; imagine cache and `orphaned_files.excluded_paths` excluded) as orphaned when the bundle named them or has file info for them and as unknown otherwise, and rows whose file is missing; `POST \u002F_\u002Forphaned_files\u002Fdelete` deletes selected orphaned paths or all of them, re-checked against a fresh scan, and never an unknown file. Upgrade step: the report is stored in a new table, `_acb_orphaned_file_report`; generate and run a migration ([#373](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F373))\n\n### Fixed\n- Stored files, their imagine variants and `_acb_file_info` rows are deleted whenever Doctrine removes an uploadable entity (group, page and orphan cascades, `POST \u002F_\u002Forphaned_resources\u002Fdelete`, direct removal), only after the flush succeeds, and never while another row still references the path; a failed file delete is logged, not a 500 ([#372](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F372))\n- `GET \u002Fme` and `GET \u002F…\u002F{id}\u002Fusage` work with `use_symfony_listeners: false`: `\u002Fme` keeps the controller of the `Get` it is built from and `UserStateProvider` resolves the token user itself, and the usage operation is named after its key ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n\n### Tooling\n- `API_PLATFORM_USE_SYMFONY_LISTENERS=0|1` sets the flag in the Behat test app, with a cache directory per value, and CI runs one Behat leg with it on ([#369](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F369))\n- `simple-phpunit` runs PHPUnit 13.4 ([#378](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F378))\n",{"part":783,"tag":987,"name":988,"publishedAt":989,"url":990,"body":991},"v2.0.0-alpha.3","2.0.0-alpha.3","2026-09-26T10:50:08Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.3","Since [v2.0.0-alpha.2](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.2).\n\n### Upgrade notes\n- A deploy with `JWT_SECRET_KEY` set but no `JWT_PASSPHRASE` now fails with a clear message, instead of getting a random passphrase that can't decrypt the key. ([85d3f43](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F85d3f43e2ec75af0b19b9a18a56010c6dd16eaa8))\n- **Run the new migration** for bundle 2.0.0-alpha.6's orphaned-resource report table, and update `@cwa\u002Fnuxt` to 2.0.0-alpha.3 at the same time. `clean-orphaned` no longer deletes anything. ([8bab085](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8bab085d597f165d950772faa878160146520525))\n\n### Changed\n- `generate_jwt_keys` derives `JWT_PUBLIC_KEY` from the secret key when it's empty. ([85d3f43](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F85d3f43e2ec75af0b19b9a18a56010c6dd16eaa8))\n- `api-components-bundle` 2.0.0-alpha.6 and `@cwa\u002Fnuxt` 2.0.0-alpha.3: an orphaned-resources admin page with bulk delete, and a scan that can email admins when the result changes. ([8bab085](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8bab085d597f165d950772faa878160146520525))\n\n### Added\n- A daily 03:00 orphaned-resource scan on production that emails `MAILER_EMAIL` when orphans change. ([c31d28f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc31d28f929bd78ee5670a72c511aeb42e2415eb3), [#101](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F101))\n\n### Fixed\n- The orphan scan's email reaches a `MAILER_EMAIL` in the `Name \u003Caddress>` form (bundle 2.0.0-alpha.7). ([6bf6b7c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F6bf6b7c2cfd1a657b8d64ac00bfd1d325d326029))\n- The orphan-scan CronJob no longer receives `RESET_DATABASE`, which would have dropped the schema nightly. ([70b9802](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F70b9802d879f29e3e6fd3121e893a328ec85bf2e), [#103](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F103))\n- An empty `MAILER_EMAIL` no longer crashes the orphan scan. ([70b9802](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F70b9802d879f29e3e6fd3121e893a328ec85bf2e))\n- A deploy without `MAILER_DSN` no longer fails the helm render, and GitHub deploys pass the mail and orphan-scan variables. ([70b9802](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F70b9802d879f29e3e6fd3121e893a328ec85bf2e), [#102](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F102))\n\n### Security\n- `TRUSTED_HOSTS` defaults anchor every alternative, so no default trusts an attacker's hostname. Projects should check their own `TRUSTED_HOSTS` CI variable the same way. ([b43fd09](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fb43fd0925b7f130d1362562ec8d0001285c05644), [#97](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F97))\n\nBuilt against `api-components-bundle` 2.0.0-alpha.7 and `@cwa\u002Fnuxt` 2.0.0-alpha.3. `create-cwa@2.0.0-alpha.3` installs this release.\n",{"part":777,"tag":741,"name":741,"publishedAt":993,"url":994,"body":995},"2026-09-26T10:34:22Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.7","### Fixed\n- `scan-orphaned` (and `clean-orphaned`) no longer fails with a `TypeError` when the notification recipients resolve to null, e.g. from an empty `%env(default::MAILER_EMAIL)%`: null or empty recipients mean no email ([#366](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F366))\n- `orphaned_resources.notify.recipients` accepts `Name \u003Caddress>` as well as a bare address, in a list or a comma-separated string (quote a name containing a comma), so the template's default `MAILER_EMAIL` no longer silently stops the orphan alert ([#363](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F363))\n\n### Tooling\n- The Behat suite runs as concurrent shards (`bin\u002Fbehat-parallel`) with coverage merged into one report, and the test app hashes passwords at bcrypt cost 4: about 9x faster locally ([#360](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F360))\n- Infection on a pull request mutates only the lines it changes in `src\u002F`, gated at 80; `main` still runs the full set and is the only run that sends the Stryker badge score ([#362](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F362))\n",{"part":777,"tag":997,"name":997,"publishedAt":998,"url":999,"body":1000},"2.0.0-alpha.6","2026-09-26T07:58:58Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.6","### Breaking\n- Upgrade step: the orphaned resource report is now stored in a new table, `_acb_orphaned_resource_report`. Generate and run a migration (`doctrine:migrations:diff`). Reports previously held in `cache.app` are not migrated; `GET \u002F_\u002Forphaned_resources` is 404 until the next scan ([#354](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F354))\n- `silverback:api-components:clean-orphaned` no longer deletes anything: it is now an alias of `scan-orphaned`, which only scans. Delete orphans through the API (`DELETE` per IRI, or `POST \u002F_\u002Forphaned_resources\u002Fdelete`) ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F353))\n\n### Added\n- `silverback:api-components:scan-orphaned` emails `silverback_api_components.orphaned_resources.notify.recipients` when the orphaned resources differ from the last alert, with the counts, what is new since then and a link to the admin page (`notify.admin_page_path`, default `\u002F_cwa\u002Forphaned`, on `user.email_links.default_origin`). `--no-notify` skips it; the HTTP scan never emails ([#354](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F354))\n- `POST \u002F_\u002Forphaned_resources\u002Fdelete` (admin) deletes selected orphans (`iris`) or all of them (`all: true`), re-checked against a fresh scan, and returns what was deleted and what was rejected ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F353))\n- Fixture builder: `PageBuilder::withoutRoute()` keeps a non-template page without a route; `generate-fixtures --namespace` sets the generated class's namespace ([#347](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F347))\n\n### Changed\n- The orphaned resource report is stored in the database instead of `cache.app`, so it is shared between pods and survives deploys, and its `generatedAt` has microsecond precision ([#354](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F354))\n- `silverback:api-components:scan-orphaned` (alias `clean-orphaned`) runs the same scan as `POST \u002F_\u002Forphaned_resources\u002Fscan` synchronously, stores the report `GET \u002F_\u002Forphaned_resources` returns, and prints the counts per kind (IRIs with `-v`) ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F353))\n\n### Fixed\n- The orphan scan reports what is orphaned only through an orphaned parent: the positions in an orphaned group, the components only those positions hold, the groups those components own, to any depth, and cycles nothing live reaches. A resource still reached from a live page, layout, component or page data is not listed, and `POST \u002F_\u002Forphaned_resources\u002Fdelete` accepts the newly listed IRIs ([#358](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F358))\n- An admin's `POST \u002F_\u002Forphaned_resources\u002Fscan`, or the refresh after `POST \u002F_\u002Forphaned_resources\u002Fdelete`, no longer absorbs an orphan change: `scan-orphaned` compares with the orphans at the last alert, kept in the same table, instead of with the last stored report. A failed send is retried on the next run ([#356](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F356))\n- A delete leaves nothing for the next orphan scan: a component removed by a cascade takes the groups it owns and its unused draft with it. An explicit `DELETE` of a published component still keeps its draft ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F353))\n- A fixture `redirect()` whose `name:` belongs to a route created in the same load throws even when its path already exists, instead of re-pointing that name at the existing route ([#352](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F352))\n- A component held by a page data property typed as a parent class (such as `AbstractComponent`) counts as used, so `clean-orphaned` no longer deletes it; it is public only when the page data is on a live route, and editing it purges and publishes the page data ([#349](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F349))\n- The new email address and change password forms and `\u002Fverify-email` look the user up through `UserRepositoryInterface::loadUserByIdentifier()` instead of the undeclared `find()`\u002F`findOneBy()` ([#346](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F346))\n- A failed user email send returns 503 (or is logged after a write) whether or not a logger is configured, instead of a 500 ([#346](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F346))\n- `generate-fixtures` names the class after the `--output` file instead of always `GeneratedScaffold` ([#347](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F347))\n- `generate-fixtures` emits `withoutRoute()` for a non-template page with no route, so it no longer gains a route on reload ([#347](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F347))\n- A fixture `redirect()` whose derived route name is taken gets a suffixed name instead of failing on the unique constraint ([#347](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F347))\n- Two scaffold classes in one `doctrine:fixtures:load` no longer fail on detached entities: each scaffold starts a new builder load and `getRoute()` finds existing routes by name ([#347](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F347))\n",{"part":814,"tag":987,"name":987,"publishedAt":1002,"url":1003,"body":1004},"2026-09-26T07:22:51Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.3","- Times from the API with more than millisecond precision, such as the orphan report's `generatedAt`, display correctly in every browser ([d1695b8d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fd1695b8d))\n- Orphaned resources are deleted in one request to `POST \u002F_\u002Forphaned_resources\u002Fdelete`, which checks each one again first; the page re-reads the report afterwards and lists what was deleted, including cascaded items, and what was kept and why. Needs an api-components-bundle release containing #353 ([#358](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F358), [519ef3e5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F519ef3e5))\n- Review orphaned component groups, positions and components at `\u002F_cwa\u002Forphaned`, view and delete them, and scan from site settings, which says when a scan has found orphans ([api-components-bundle#190](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fissues\u002F190), [dca1fb13](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fdca1fb13))\n- An `undefined` or non-string `allowedComponents` entry warns and syncs nothing instead of throwing ([#354](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F354), [825cf38c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F825cf38c))\n- `useResendVerifyEmail` resets `success` when a new request starts, so a throttled or failed resend no longer shows alongside the earlier success; Back to Login after a password reset no longer sends a second reset request ([#355](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F355), [28901c51](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F28901c51))\n- An email the API refuses to send (400) asks the visitor to contact the site administrator, and warns in the console about `user.email_links.allowed_origins` ([#356](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F356), [28901c51](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F28901c51))\n",{"part":783,"tag":1006,"name":1007,"publishedAt":1008,"url":1009,"body":1010},"v2.0.0-alpha.2","2.0.0-alpha.2","2026-09-25T22:21:40Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.2","Since [v2.0.0-alpha.1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.1).\n\n### Upgrade notes\n- Bundle alpha.5: throttled email requests now return 429, and an application with its own `RouteGeneratorInterface` must add `generatePath()`. ([867c412](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F867c4128d66553744a1fb6c6269e1b1a4452a667))\n- **Staging no longer has a fixture job** (it shares production's database). `FIXTURES_PURGE` empties the database before a fixture load: `\"true\"` on review apps, `\"force\"` for production. ([!11](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F11))\n- **Rename `NUXT_PUBLIC_CWA_API_URL` to `NUXT_CWA_API_URL`** in the same deploy as the module update. The old name still works, but it publishes the internal URL and logs a deprecation warning. ([66e9365](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F66e93658cb394b52e39e5664e54153a21c74e99d))\n- **Configure `user.email_links.default_origin`.** Without it, bundle alpha.4 refuses password-reset and verification emails with a 400. ([e23956d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe23956dceafbbb7caaa2a9875be25e3325985742))\n- `compose.prod.yaml` now needs every secret set. ([1456c7c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1456c7cd1002d3da18f8b7191b69983c819c65fc))\n\n### Changed\n- `api-components-bundle` 2.0.0-alpha.5 and `@cwa\u002Fnuxt` 2.0.0-alpha.2: fixture loads with `--append` are idempotent, and drafts can be scheduled to publish. ([867c412](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F867c4128d66553744a1fb6c6269e1b1a4452a667))\n- Every fixture load ends by flushing the whole HTTP cache, so reloaded content isn't served stale. ([!11](https:\u002F\u002Fgitlab.com\u002Fsilverback-web-apps\u002Fcwa\u002Fcomponents-web-app\u002F-\u002Fmerge_requests\u002F11))\n- `CLAUDE.md` trimmed from 1,992 to 351 lines of current guidance. ([d13dbe1](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd13dbe1aa2df04b0b004ce8c9cd3a4e56e08b296))\n- Depend on `@cwa\u002Fnuxt` `^2.0.0-alpha.1`, its first tagged release, instead of an edge build. ([b3602d5](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fb3602d5455581f47b62c280bb82f96d0f87577eb))\n- `@cwa\u002Fnuxt` edge `5cc17ad`: readiness route, purgeable sitemap, private server API URL, first-run and API-down error pages. ([66e9365](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F66e93658cb394b52e39e5664e54153a21c74e99d))\n- `api-components-bundle` 2.0.0-alpha.4: uncached `\u002F_api\u002F_\u002Fhealth`, a failed purge no longer fails a saved write, and email links use a configured origin. ([e23956d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe23956dceafbbb7caaa2a9875be25e3325985742))\n- `create-cwa` downloads the template tag matching its own version, with `--ref` to choose another. ([c3a0984](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fc3a09842bbbc3f524bb7fefabdabf734c34120d7))\n- A branch without a review namespace shows orange (passed with warnings), not red. ([5b6a953](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5b6a953ee17221d8612648d4594f2b7f40608155))\n\n### Added\n- `CHANGELOG.md`, kept from now on and used as each release's notes. ([7d04051](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F7d040513a14cbaaae5c167ff41a2cb8789db3ffc))\n- Dev JWT keys are generated on first boot. ([1456c7c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F1456c7cd1002d3da18f8b7191b69983c819c65fc))\n- `.gitguardian.yaml` for the required-secret placeholders in `compose.prod.yaml`. ([64f8d58](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F64f8d58d36703aed3b3433c969002ebaf1576c9f))\n- Release process documented in `CLAUDE.md`. ([5fc5e64](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F5fc5e64fb843db67ee9c810c38fba189e69d558d))\n\n### Fixed\n- CORS exposes `Retry-After`, so a cross-origin site can count down a throttled email request. ([ba0ea40](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fba0ea409338e653e25b44afcfb545e0006809014), [#100](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F100))\n- `compose.prod.yaml` gives the app service its API URL and warm origin. ([ba0ea40](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fba0ea409338e653e25b44afcfb545e0006809014), [#98](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F98))\n- Review fixtures, warm, audit and stop jobs stop with a reason when the review job didn't deploy. ([ba0ea40](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fba0ea409338e653e25b44afcfb545e0006809014), [#99](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F99))\n\n### Security\n- Local JWT keys, secrets and uploads are kept out of the API image. ([176fa9d](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F176fa9d708e892f75acc367a05997f2c00a8633a))\n- Caddy's admin API listens only on the php container's loopback, and port 2019 is no longer published. ([0170978](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F0170978196ef9dd3092a15f761b5edf024c4d174))\n- Removed a hardcoded, unused `COMPOSER_PACKAGIST_TOKEN` from the Behat job. ([cf6591e](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fcf6591e9586df420834bdcab713b65efa4be7889))\n\nBuilt against `api-components-bundle` 2.0.0-alpha.5 and `@cwa\u002Fnuxt` 2.0.0-alpha.2. `create-cwa@2.0.0-alpha.2` installs this release.\n",{"part":777,"tag":1012,"name":1012,"publishedAt":1013,"url":1014,"body":1015},"2.0.0-alpha.5","2026-09-25T22:04:12Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.5","### Breaking\n- `RouteGeneratorInterface` gains `generatePath()`: an application with its own route generator must implement it ([#335](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F335))\n- A throttled password reset, email verification or new email confirmation request returns 429 with `Retry-After`, where it returned 200 ([#332](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F332))\n- Registration and a new email address request whose email link is refused return 201, where they returned 400 for a user that had been saved ([#327](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F327))\n\n### Added\n- Fixture builder: `allow:` on page groups, `liveAt()` on pages and page data, `redirect()` and `afterRoutes()` ([#322](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F322))\n- Orphaned resource report: `POST \u002F_\u002Forphaned_resources\u002Fscan` and `GET \u002F_\u002Forphaned_resources` (admin) list unowned groups, empty positions and unused components ([#329](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F329))\n- `doctrine:fixtures:load --append` with a `CwaFixtureBuilder` scaffold creates only what is missing and prints a created\u002Fkept\u002Fskipped summary; adds `PageDataBuilder::withoutRoute()` and `RouteGeneratorInterface::generatePath()` ([#335](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F335))\n\n### Fixed\n- The current-password check (`UserPasswordValidator`) reloads the user through `UserRepositoryInterface::loadUserByIdentifier()` instead of the undeclared `find()`, so it works with an application's own user repository ([#337](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F337))\n- `generate-fixtures` writes a scaffold that loads and gives back the same site: published state and drafts, uploaded files, component-owned and shared groups, relations, dates, inherited and non-public fields, meta descriptions, scheduled routes and redirects. It lists anything it cannot reproduce ([#322](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F322))\n- An application's `#[ORM\\MappedSuperclass]` between `AbstractComponent` or `AbstractPageData` and its entities no longer breaks queries with a missing table ([#328](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F328))\n- A refused email link no longer leaves a half-done change: a password reset request is a 400 that keeps the existing reset token, and an email after a completed user write (welcome, account enabled, email change and the rest) is logged at error instead of turning the saved write into a 400 ([#327](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F327))\n- A throttled password reset, email verification or new email confirmation request is a 429 with `Retry-After` instead of a 200 that sends nothing. Each flow has its own throttle: new `user.new_email_confirmation.repeat_ttl_seconds` and `user.email_verification.repeat_ttl_seconds` (default 300), with `password_reset.repeat_ttl_seconds` now governing only password resets. The throttle starts only once the email is sent ([#332](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F332))\n- The `UserPassword` constraint no longer fails with an undefined-method error for a signed-in user that is not the bundle's user, such as an in-memory admin ([#334](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F334))\n\n### Tooling\n- `CHANGELOG.md` is kept for every change, and pushing a tag publishes its section as the GitHub release notes ([#320](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F320))\n- PHPStan: all 23 `method.notFound` baseline entries resolved; the baseline is down from 98 findings to 69 ([#334](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Fpull\u002F334))\n",{"part":814,"tag":1006,"name":1006,"publishedAt":1017,"url":1018,"body":1019},"2026-09-25T22:01:35Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.2","- Throttled email requests (429) say when another can be sent; the admin resend link counts down until then ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F353), [6e0fd593](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F6e0fd593))\n- A failed email send (503) says the email couldn't be sent, rather than implying one is on its way ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F353), [5467165f](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F5467165f))\n- Cancel a pending email change from the admin user page ([#353](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F353), [6e0fd593](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F6e0fd593), [a5b34725](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fa5b34725))\n- `CwaUiDatePicker`, `CwaUiCalendar` and `CwaUiInputDate`, built on `reka-ui` and ported from Nuxt UI; the Publish tab and a route's go-live date use the picker ([#320](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F320), [b57599bf](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fb57599bf))\n- Schedule a draft to publish at a future time from the Publish tab, or publish it now ([#320](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F320), [f909013a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Ff909013a), [d654ed96](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fd654ed96))\n- Ship type declarations for `ResourceModalTabs.vue`, which were empty in 2.0.0-alpha.1 ([#349](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F349), [314c6708](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F314c6708))\n- `allowedComponents` accepts component names, with an auto-imported `CwaComponentNames` constant and `CwaComponentName` type ([#352](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F352), [93ca8a0a](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F93ca8a0a))\n- Sync `allowedComponents` to groups with no stored list; an empty list is sent as `null` ([#351](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F351), [a995f757](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002Fa995f757))\n",{"part":814,"tag":1021,"name":1021,"publishedAt":1022,"url":1023,"body":1024},"v2.0.0-alpha.1","2026-09-25T15:18:34Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.1","- First tagged release of `@cwa\u002Fnuxt`; releases are now published from `v*` tags ([7c61b7c](https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fcommit\u002F7c61b7ce331ec4a0cb8e76951a5de2ff806ed07a))\n",{"part":777,"tag":737,"name":737,"publishedAt":1026,"url":1027,"body":1028},"2026-09-24T15:15:42Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.4","**Upgrade step required: configure the origin of links in user emails** (#316). Links in password reset, email verification, new-email confirmation, welcome and account-enabled emails now use the request's `Origin`\u002F`Referer` only when it matches `silverback_api_components.user.email_links.allowed_origins` (anchored regexes), and otherwise `email_links.default_origin`. With neither set, those emails are not sent and the request is a 400:\n\n```yaml\nsilverback_api_components:\n    user:\n        email_links:\n            default_origin: '%env(default::EMAIL_LINK_DEFAULT_ORIGIN)%'\n```\n\nA path from `redirect_path_query` must now be a plain relative path; anything else falls back to the configured redirect path. `RefererUrlResolver::getAbsoluteUrl()` now takes a `RelativeUrlPath`.\n\n**New:** `GET \u002F_\u002Fhealth` (`\u002F_api\u002F_\u002Fhealth` in the template), an uncached readiness check: `200 {\"status\":\"ok\"}` after a dummy query on the default connection, or 503 (#312).\n\n**Fixed:** a failed HTTP cache purge after a write no longer turns the saved write into a 500. It is logged at error level and Mercure still publishes. `silverback:api-components:purge-rendered-html` exits 1 on failure, and both purge endpoints return 502 (#311).\n\n**Pinned:** the exact cache tags a Route write purges under the API route prefix, e.g. `\u002F_api\u002F_\u002Froutes`, identical over HTTP and CLI (#313).\n\nStill an alpha: the bundle and `@cwa\u002Fnuxt` are changing together with the projects that use them.\n\n**Requires:** PHP 8.5; Symfony ^7.4 or ^8.1; API Platform ^4.4 or ^5.0; Doctrine ORM 3.\n",{"part":783,"tag":1021,"name":1030,"publishedAt":1031,"url":1032,"body":1033},"2.0.0-alpha.1","2026-09-24T11:49:40Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.1","The first tagged release of the CWA template: the demo app and the starting point for new projects.\n\n```bash\npnpm create cwa my-project\n```\n\n`create-cwa` now has the same version as the template: `create-cwa@2.0.0-alpha.1` installs this release. Add `--ref main` to get the latest unreleased template instead.\n\n**Still an alpha.** The template, `@cwa\u002Fnuxt` and `api-components-bundle` are changing together with the projects that use them.\n\n### Built against\n- **api-components-bundle `2.0.0-alpha.3`**, required as `^2.0@alpha`\n- **`@cwa\u002Fnuxt` edge `0.0.0-29837288.9a15df5`**. The module has no tagged release yet, so the edge build is pinned exactly.\n- Nuxt 4.5.2 (Vite 8), `@nuxt\u002Fui` 4.11, Vue 3.5, TypeScript 6.0.3 (TypeScript 7 isn't supported by `vue-tsc` yet)\n- API Platform 5.0, Symfony 8.1, Doctrine ORM 3, PHP 8.5, FrankenPHP with Souin and Mercure\n\n### What's in it\n- **Page and API caching in Souin.** Pages are purged by surrogate key when content changes, and flushed after each front-end deploy. The cache is warmed from the sitemap after a production deploy. Tracking parameters are stripped from the cache key.\n- **Offline support.** A service worker caches the app shell and public API content. Updates apply on the next page change, and the caches are cleared when a session ends.\n- **Kubernetes.** A Helm chart with autoscaling for the front end and eviction protection. A certificate is issued before any hostname change takes effect, so the site keeps a valid certificate throughout.\n- **CI for GitLab and GitHub Actions.** Review apps, staging, canary and production deploys, plus an optional Lighthouse audit you start by hand.\n- **Demo content.** Fixtures cover a blog, nested pages and a form that uses every Symfony field type.\n- **Load testing.** A k6 harness in `bin\u002Fload-test\u002F`.\n\n`CLAUDE.md` records the reasoning and measurements behind these decisions.\n",{"part":777,"tag":988,"name":988,"publishedAt":1035,"url":1036,"body":1037},"2026-09-24T11:38:59Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.3","**Manifest change:** `GET \u002F_\u002Fresource_manifest\u002F{id}` now lists a component's own component groups, their positions and their components at the component's depth, to any depth (#309). Before, each level of component nesting cost another round trip. A component's own response is unchanged.\n\nStill an alpha: the bundle and `@cwa\u002Fnuxt` are changing together with the projects that use them.\n\n**Requires:** PHP 8.5; Symfony ^7.4 or ^8.1; API Platform ^4.4 or ^5.0; Doctrine ORM 3.",{"part":777,"tag":1007,"name":1007,"publishedAt":1039,"url":1040,"body":1041},"2026-09-24T11:17:08Z","https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.2","The first 2.x release since `2.0.0-alpha.1` in 2020. It reflects the current `main`.\n\nThis is still an **alpha**. The bundle and `@cwa\u002Fnuxt` are changing together with the projects that use them, and breaking changes are still expected before 2.0.0, possibly including the namespace.\n\n**Requires:** PHP 8.5; Symfony ^7.4 or ^8.1; API Platform ^4.4 or ^5.0; Doctrine ORM 3.\n\n`dev-main` is now aliased to `2.x-dev`.",1791855266283]