@elytracms/next 0.0.90 → 0.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +189 -225
- package/dist/index.js +131 -109
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components.ts","../src/canvas-renderer.tsx","../src/source.ts","../src/snapshot.ts","../src/route-core.ts","../src/cache.ts","../src/metadata.ts","../src/route.tsx","../src/sitemap.ts","../src/revalidate-core.ts","../src/revalidate.ts","../src/image.tsx","../src/full-bleed.ts","../src/host-env.ts","../src/host-content.ts","../src/host-globals.ts","../src/preview-core.ts","../src/preview.ts","../src/host-kit.ts","../src/index.ts"],"names":["record","items","documentBodyCompositionNodes","cacheTags","jsx","outcome","str","draftMode","redirect"],"mappings":";;;;;;;;;;;;;;;;;;;AA+EO,SAAS,qBACd,UAAA,GAA4C,EAAC,EAC7C,OAAA,GAAuC,EAAC,EACxB;AAChB,EAAA,MAAM,WAAA,GAAc,QAAQ,qBAAA,KAA0B,KAAA;AACtD,EAAA,MAAM,IAAA,GAAO,WAAA,GAAc,cAAA,EAAe,GAAI,EAAE,WAAW,EAAC,EAAG,eAAA,EAAiB,EAAC,EAAE;AAEnF,EAAA,MAAM,QAAA,GAAW,IAAI,iBAAA,CAAkB,CAAC,GAAG,IAAA,CAAK,SAAA,EAAW,GAAG,UAAA,CAAW,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAC,CAAC,CAAA;AAEhG,EAAA,MAAM,kBAA0E,EAAC;AACjF,EAAA,KAAA,MAAW,QAAA,IAAY,KAAK,SAAA,EAAW;AACrC,IAAA,MAAM,cAAA,GAAiB,iBAAA,CAAkB,IAAA,CAAK,eAAA,EAAiB,SAAS,EAAE,CAAA;AAC1E,IAAA,IAAI,cAAA,EAAgB,eAAA,CAAgB,QAAA,CAAS,EAAE,CAAA,GAAI,cAAA;AAAA,EACrD;AAGA,EAAA,MAAM,IAAA,GAAO,wBAAwB,UAAU,CAAA;AAC/C,EAAA,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,IAAA,CAAK,eAAe,CAAA;AAEnD,EAAA,OAAO,EAAE,QAAA,EAAU,eAAA,EAAiB,MAAA,EAAQ,CAAC,GAAG,QAAA,CAAS,MAAA,EAAQ,GAAG,IAAA,CAAK,MAAM,CAAA,EAAE;AACnF;ACPA,SAAS,iBAAiB,SAAA,EAA4D;AACpF,EAAA,OAAO,CAAC,GAAA,KAAQ,SAAA,CAAU,CAAA,EAAG,GAAA,CAAI,UAAU,CAAA,CAAA,EAAI,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA,IAAK,IAAA;AAC9D;AAGA,SAAS,aAAa,KAAA,EAA4C;AAChE,EAAA,OAAO,CAAC,GAAA,KAAQ,KAAA,CAAM,CAAA,EAAG,GAAA,CAAI,UAAU,CAAA,CAAA,EAAI,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA,IAAK,IAAA;AAC1D;AAGA,SAAS,gBAAgB,QAAA,EAA4D;AACnF,EAAA,OAAO,CAAC,KAAA,KAAU,QAAA,CAAS,qBAAA,CAAsB,KAAK,CAAC,CAAA,IAAK,IAAA;AAC9D;AAGA,SAAS,cAAc,MAAA,EAAsE;AAC3F,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,EAAA,EAAI,KAAK,CAAC,CAAC,CAAA;AAC7D,EAAA,OAAO,CAAC,EAAA,KAAO;AACb,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA;AACzB,IAAA,OAAO,KAAA,GACH;AAAA,MACE,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,YAAY,KAAA,CAAM,UAAA;AAAA;AAAA,MAElB,MAAM,KAAA,CAAM,IAAA;AAAA,MACZ,MAAM,KAAA,CAAM;AAAA,KACd,GACA,IAAA;AAAA,EACN,CAAA;AACF;AAGA,SAAS,uBAAuB,KAAA,EAAsC;AACpE,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,kBAAA,EAAiB,iBAAA,EAAkB,gBAAA,EAAgB,MAAM,MAAA,EAC3D,QAAA,EAAA,CAAA,4CAAA,EAA+C,KAAA,CAAM,MAAM,CAAA,EAAA,CAAA,EAC9D,CAAA;AAEJ;AAGA,SAAS,uBAAA,GAAqC;AAC5C,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,kBAAA,EAAiB,kBAAA,EACnB,QAAA,EAAA,CAAA,sDAAA,CAAA,EACH,CAAA;AAEJ;AASA,SAAS,wBAAA,CAAyB,OAAsB,QAAA,EAAgC;AACtF,EAAA,IAAI,cAAA,CAAe,QAAQ,CAAA,IAAK,QAAA,CAAS,SAAS,mBAAA,EAAqB;AACrE,IAAA,OAAQ,QAAA,CAAS,MAAmC,QAAA,IAAY,IAAA;AAAA,EAClE;AACA,EAAA,OAAO,QAAA;AACT;AAOA,eAAsB,eAAe,KAAA,EAAgD;AACnF,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,KAAA;AAE/B,EAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAG1B,IAAA,uBAAO,GAAA,CAAC,sBAAA,EAAA,EAAuB,MAAA,EAAQ,MAAA,CAAO,MAAA,EAAQ,CAAA;AAAA,EACxD;AAEA,EAAA,MAAM,GAAA,GAAqB;AAAA,IACzB,UAAU,UAAA,CAAW,QAAA;AAAA,IACrB,iBAAiB,UAAA,CAAW,eAAA;AAAA,IAC5B,YAAA,EAAc,aAAA,CAAc,KAAA,CAAM,MAAA,IAAU,EAAE,CAAA;AAAA;AAAA;AAAA,IAG9C,GAAI,KAAA,CAAM,SAAA,GAAY,EAAE,eAAA,EAAiB,iBAAiB,KAAA,CAAM,SAAS,CAAA,EAAE,GAAI,EAAC;AAAA;AAAA;AAAA,IAGhF,GAAI,KAAA,CAAM,QAAA,GAAW,EAAE,cAAA,EAAgB,gBAAgB,KAAA,CAAM,QAAQ,CAAA,EAAE,GAAI,EAAC;AAAA;AAAA;AAAA;AAAA,IAI5E,GAAI,KAAA,CAAM,KAAA,GAAQ,EAAE,WAAA,EAAa,aAAa,KAAA,CAAM,KAAK,CAAA,EAAE,GAAI,EAAC;AAAA,IAChE,YAAA,EAAc;AAAA,GAChB;AAEA,EAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AAUxB,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBAAO,GAAA,CAAA,QAAA,EAAA,EAAG,kCAAwB,WAAA,CAAY,QAAA,EAAU,MAAM,YAAY,CAAA,EAAG,GAAG,CAAA,EAAE,CAAA;AAAA,EACpF;AAEA,EAAA,2BAAQ,uBAAA,EAAA,EAAwB,CAAA;AAClC;AAUO,SAAS,WAAA,CACd,UACA,YAAA,EACoB;AACpB,EAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAC;AACvB,EAAA,MAAM,MAAA,GAAS,YAAA,EAAc,IAAA,IAAQ,EAAC;AACtC,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,OAAO,MAAA,CAAO,OAAA,CAAQ,CAAC,IAAA,KAAS,iBAAA,CAAkB,QAAA,CAAS,IAAI,CAAC,CAAC,CAAA;AACxF,EAAA,OAAO,iBAAA,CAAkB,aAAA,CAAc,QAAQ,CAAC,CAAA;AAClD;AA2BA,SAAS,cAAc,QAAA,EAA4C;AACjE,EAAA,MAAM,IAAA,GAAO,SAAS,MAAM,CAAA;AAM5B,EAAA,IAAI,wBAAA,CAAyB,IAAI,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,IAAK,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,IAAA;AAChG,EAAA,MAAM,OAAA,GAAU,SAAS,SAAS,CAAA;AAClC,EAAA,IAAI,wBAAA,CAAyB,OAAO,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,oBAAA,CAAqB,OAAO,CAAA,EAAG,OAAO,OAAA;AAGzG,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,EAAG;AAC3C,IAAA,IAAI,wBAAA,CAAyB,KAAK,CAAA,EAAG,OAAO,KAAA;AAC5C,IAAA,IAAI,oBAAA,CAAqB,KAAK,CAAA,EAAG,OAAO,KAAA;AACxC,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,oBAAoB,CAAA,EAAG,OAAO,KAAA;AAAA,EAC5F;AACA,EAAA,OAAO,MAAA;AACT;AAeO,SAAS,kBAAA,CACd,UACA,YAAA,EACiB;AACjB,EAAA,OAAO,4BAAA,CAA6B,WAAA,CAAY,QAAA,EAAU,YAAY,CAAC,CAAA;AACzE;AAGA,SAAS,yBAAyB,KAAA,EAAyB;AACzD,EAAA,OACE,yBAAA,CAA0B,KAAK,CAAA,IAC/B,KAAA,CAAM,OAAA,CAAQ,IAAA;AAAA,IACZ,CAAC,SACC,OAAO,IAAA,KAAS,YAChB,IAAA,KAAS,IAAA,IACR,KAA4B,IAAA,KAAS;AAAA,GAC1C;AAEJ;AAGA,SAAS,qBAAqB,KAAA,EAAyB;AACrD,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,OAAQ,KAAA,CAA2B,EAAA,KAAO,QAAA,IAC1C,OAAQ,KAAA,CAAoC,WAAA,KAAgB,QAAA;AAEhE;ACxPO,SAAS,cAAA,CAAe,UAAqB,IAAA,EAAuC;AACzF,EAAA,MAAM,GAAA,GAAM,CAAC,KAAA,EAAO,GAAG,IAAI,CAAA;AAC3B,EAAA,OAAO,SAAA,CAAU;AAAA,IACf,MAAM,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AAAA,IAC/B,aAAa,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,WAAW,CAAA;AAAA,IAC7C,QAAQ,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACnC,QAAQ,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACnC,QAAQ,KAAA,CAAM;AAAA,GACf,CAAA;AACH;ACoCO,SAAS,0BAA0B,IAAA,EAA0C;AAKlF,EAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,IAAA,CAAK,OAAO,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,IAAU,EAAC;AAC/B,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,IAAa,EAAC;AACrC,EAAA,MAAM,WACJ,IAAA,CAAK,QAAA,KAAa,CAAC,KAAA,KAAuB,CAAA,mBAAA,EAAsB,MAAM,UAAU,CAAA,CAAA,CAAA;AAElF,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAA2B;AACnD,EAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,IAAe,IAAI,WAAA,CAAY,GAAA,CAAI,UAAA,CAAW,EAAA,EAAI,UAAU,CAAA;AAE1F,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAmC;AACzD,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAqC;AAC9D,EAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG;AACzC,IAAA,MAAM,GAAA,GAAM,iBAAA,CAAkB,MAAM,CAAA,GAAI,OAAO,KAAA,GAAQ,MAAA;AACvD,IAAA,SAAA,CAAU,GAAA,CAAI,WAAA,CAAY,GAAG,CAAA,EAAG,MAAM,CAAA;AACtC,IAAA,MAAM,OAAO,YAAA,CAAa,GAAA,CAAI,GAAA,CAAI,UAAU,KAAK,EAAC;AAClD,IAAA,IAAA,CAAK,KAAK,MAAM,CAAA;AAChB,IAAA,YAAA,CAAa,GAAA,CAAI,GAAA,CAAI,UAAA,EAAY,IAAI,CAAA;AAAA,EACvC;AAEA,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAyB;AAC5C,EAAA,KAAA,MAAWA,OAAAA,IAAU,KAAK,MAAA,IAAU,IAAI,MAAA,CAAO,GAAA,CAAIA,OAAAA,CAAO,EAAA,EAAIA,OAAM,CAAA;AAEpE,EAAA,MAAM,SAAA,GAAY,CAAC,YAAA,MAA8C;AAAA,IAC/D,UAAA,EAAY,CAAC,EAAA,KAAO,WAAA,CAAY,IAAI,EAAE,CAAA;AAAA,IACtC,UAAU,CAAC,GAAA,KAAqB,UAAU,GAAA,CAAI,WAAA,CAAY,GAAG,CAAC,CAAA;AAAA,IAC9D,WAAW,CAAC,YAAA,KAAiB,aAAa,GAAA,CAAI,YAAY,KAAK,EAAC;AAAA,IAChE,KAAA,EAAO,CAAC,EAAA,KAAO,MAAA,CAAO,IAAI,EAAE,CAAA;AAAA,IAC5B,UAAU,CAAC,KAAA,KAAU,KAAA,CAAM,GAAA,IAAO,SAAS,KAAK;AAAA,GAClD,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KACd,mBAAA,CAAoB;AAAA,IAClB,MAAA,EAAQ,SAAA,CAAU,IAAA,CAAK,WAAW,CAAA;AAAA,IAClC,SAAS,oBAAA,CAAqB;AAAA,MAC5B,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,OAAA,CAAQ,OAAA;AAAA,MAC/B,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB;AAAA,KACD,CAAA;AAAA,IACD,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAEH,EAAA,OAAO;AAAA,IACL,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,OAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,QAAQ,MACN,CAAC,GAAG,MAAA,CAAO,MAAA,EAAQ,CAAA,CAAE,GAAA,CAAI,CAACA,OAAAA,KAAW,cAAcA,OAAAA,EAAQA,OAAAA,CAAO,OAAO,QAAA,CAASA,OAAM,CAAC,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAK5F,GAAI,KAAK,WAAA,GAAc,EAAE,aAAa,IAAA,CAAK,WAAA,KAAgB;AAAC,GAC9D;AACF;AAQA,eAAsB,qBAAA,CACpB,OAAA,EACA,SAAA,EACA,OAAA,EAC0B;AAE1B,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,GAAA,CAAI,WAAW,SAAS,CAAA;AACrD,EAAA,MAAM,eAAA,GAAkB,MAAM,OAAA,CAAQ,GAAA,CAAI,cAAc,SAAS,CAAA;AACjE,EAAA,MAAM,cAAA,GAAiB,MAAM,OAAA,CAAQ,GAAA,CAAI,wBAAwB,SAAS,CAAA;AAC1E,EAAA,MAAM,YAAA,GAAe,MAAM,OAAA,CAAQ,MAAA,CAAO,WAAW,SAAS,CAAA;AAI9D,EAAA,MAAM,eAAA,GAAkB,MAAM,OAAA,CAAQ,SAAA,CAAU,cAAc,SAAS,CAAA;AACvE,EAAA,MAAM,SAAA,GAA8B,eAAA,CACjC,MAAA,CAAO,CAACA,OAAAA,KAAWA,QAAO,OAAO,CAAA,CACjC,GAAA,CAAI,CAACA,OAAAA,MAAY;AAAA,IAChB,IAAIA,OAAAA,CAAO,EAAA;AAAA,IACX,MAAMA,OAAAA,CAAO,IAAA;AAAA,IACb,IAAIA,OAAAA,CAAO,EAAA;AAAA,IACX,WAAWA,OAAAA,CAAO;AAAA,GACpB,CAAE,CAAA;AAMJ,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAsC;AAChE,EAAA,KAAA,MAAWA,WAAU,cAAA,EAAgB;AACnC,IAAA,MAAM,MAAM,CAAA,EAAGA,OAAAA,CAAO,UAAU,CAAA,CAAA,EAAIA,QAAO,KAAK,CAAA,CAAA;AAChD,IAAA,IAAI,SAAA,GAAY,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,SAAA,uBAAgB,GAAA,EAAI;AACpB,MAAA,aAAA,CAAc,GAAA,CAAI,KAAK,SAAS,CAAA;AAAA,IAClC;AACA,IAAA,SAAA,CAAU,GAAA,CAAIA,OAAAA,CAAO,OAAA,EAASA,OAAAA,CAAO,QAAQ,CAAA;AAAA,EAC/C;AACA,EAAA,MAAM,SAAA,GAAqC,eAAA,CAAgB,GAAA,CAAI,CAACA,OAAAA,KAAW;AACzE,IAAA,IAAIA,OAAAA,CAAO,gBAAA,KAAqB,MAAA,EAAW,OAAOA,OAAAA,CAAO,QAAA;AAEzD,IAAA,MAAM,SAAA,GAAY,aAAA,CACf,GAAA,CAAI,CAAA,EAAGA,QAAO,QAAA,CAAS,UAAU,CAAA,CAAA,EAAIA,OAAAA,CAAO,SAAS,EAAE,CAAA,CAAE,CAAA,EACxD,GAAA,CAAIA,QAAO,gBAAgB,CAAA;AAC/B,IAAA,OAAO,EAAE,KAAA,EAAOA,OAAAA,CAAO,UAAU,SAAA,EAAW,QAAA,EAAU,EAAC,EAAE;AAAA,EAC3D,CAAC,CAAA;AAID,EAAA,OAAO,yBAAA,CAA0B;AAAA,IAC/B,SAAA;AAAA,IACA,SAAS,OAAA,CAAQ,OAAA;AAAA,IACjB,GAAI,QAAQ,MAAA,GAAS,EAAE,QAAQ,OAAA,CAAQ,MAAA,KAAW,EAAC;AAAA,IACnD,WAAW,OAAA,CAAQ,SAAA,GAAY,CAAC,GAAG,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA;AAAA,IACxD,GAAI,MAAA,GAAS,EAAE,aAAa,MAAA,CAAO,WAAA,KAAgB,EAAC;AAAA,IACpD,SAAA;AAAA,IACA,MAAA,EAAQ,YAAA;AAAA,IACR,UAAU,CAAC,KAAA,KAAU,QAAQ,KAAA,CAAM,GAAA,CAAI,MAAM,UAAU;AAAA,GACxD,CAAA;AACH;AC1JO,SAAS,iBAAA,CACd,QACA,IAAA,EACmB;AACnB,EAAA,MAAM,UAAA,GAAa,OAAO,QAAA,EAAU,UAAA;AACpC,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe,EAAC;AAAA,IACjD,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB;AAAA,GACF;AACF;AA2DO,SAAS,aAAa,IAAA,EAA6C;AACxE,EAAA,OAAO,GAAA,GAAA,CAAO,IAAA,IAAQ,EAAC,EAAG,KAAK,GAAG,CAAA;AACpC;AA4BA,SAAS,iBAAA,CACP,SAAA,EACA,QAAA,EACA,MAAA,EACA,MACA,eAAA,EAC0B;AAC1B,EAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AACnC,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAK5B,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,IAAA,yBAAA,CAA0B,IAAA,EAAM,QAAA,EAAU,GAAA,EAAK,eAAA,EAAiB,IAAI,CAAA;AAAA,EACtE;AAMA,EAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,IAAA,0BAAA,CAA2B,KAAA,EAAO,EAAE,OAAA,EAAS,CAAC,OAAO,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,EAAG,CAAA;AAAA,EACpE;AAMA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,QAAA,GAAW,YAAY,CAAA;AAC9C,EAAA,MAAM,SAAA,GACJ,OAAO,OAAA,KAAY,QAAA,GACf,UACA,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,IAAY,OAAQ,OAAA,CAA6B,EAAA,KAAO,QAAA,GACnF,QAA2B,EAAA,GAC5B,IAAA;AACR,EAAA,IAAI,SAAA,EAAW,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA;AAChC,EAAA,OAAO,GAAA,CAAI,IAAA,KAAS,CAAA,GAAI,EAAC,GAAI,SAAA,CAAU,MAAA,CAAO,CAAC,KAAA,KAAU,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,EAAE,CAAC,CAAA;AAC5E;AAqBA,SAAS,UAAA,CAAW,QAA2B,YAAA,EAA+C;AAC5F,EAAA,MAAMC,MAAAA,GAAQ,WAAA,CAAY,MAAA,CAAO,QAAA,EAAU,YAAY,CAAA;AACvD,EAAA,MAAM,SAAA,GAAY,2BAA2BA,MAAK,CAAA;AAClD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO;AAAA,MACL,GAAGC,6BAA6BD,MAAK,CAAA;AAAA,MACrC,GAAG,SAAA,CAAU,OAAA,CAAQ,CAAC,KAAA,KAAU,qBAAA,CAAsB,KAAK,CAAC;AAAA,KAC9D;AAAA,IACA;AAAA,GACF;AACF;AAYA,SAAS,eAAA,CACP,QACA,MAAA,EACkC;AAClC,EAAA,MAAM,YAAA,GAAe,OAAO,QAAA,EAAU,UAAA;AACtC,EAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,MAAA,CAAO,aAAa,OAAO,MAAA;AACjD,EAAA,MAAM,UAAA,GAAa,OAAO,WAAA,CAAY,IAAA,CAAK,CAAC,SAAA,KAAc,SAAA,CAAU,OAAO,YAAY,CAAA;AACvF,EAAA,IAAI,CAAC,UAAA,EAAY,YAAA,EAAc,OAAO,MAAA;AACtC,EAAA,MAAM,IAAA,GAAO,uBAAuB,UAAU,CAAA;AAC9C,EAAA,OAAO,IAAA,CAAK,SAAS,CAAA,GAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA,EAAE,GAAI,MAAA;AACjD;AAgBA,SAAS,qBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACkE;AAClE,EAAA,IAAI,CAAC,UAAU,OAAO,EAAE,WAAW,EAAC,EAAG,OAAA,EAAS,EAAC,EAAE;AACnD,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,EAAA,MAAM,SAAA,GAAY,0BAAA;AAAA,IAChB,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG,CAAA;AAAA,IACnC,EAAC;AAAA,IACD;AAAA,GACF;AACA,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,CAAC,GAAG,OAAO,CAAA,EAAE;AAC5C;AAaA,SAAS,oBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACmE;AACnE,EAAA,IAAI,CAAC,UAAU,OAAO,EAAE,UAAU,EAAC,EAAG,IAAA,EAAM,EAAC,EAAE;AAC/C,EAAA,MAAM,OAAoB,EAAC;AAC3B,EAAA,MAAM,QAAA,GAAW,yBAAA;AAAA,IACf,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,KAAA,KAAwB;AACvB,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,aAAA,CAAc,KAAA,CAAM,UAAA,EAAY;AAAA,QACpD,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,GAAI,MAAM,IAAA,GAAO,EAAE,MAAM,KAAA,CAAM,IAAA,KAAS,EAAC;AAAA,QACzC,GAAI,MAAM,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAM,GAAI;AAAC,OAC3D,CAAA;AACD,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,CAAA;AACrB,MAAA,OAAO,MAAA,CAAO,EAAA,GAAM,MAAA,CAAO,SAAA,GAAiC,EAAC;AAAA,IAC/D,CAAA;AAAA;AAAA;AAAA;AAAA,IAIA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GACrC;AACA,EAAA,OAAO,EAAE,UAAU,IAAA,EAAK;AAC1B;AAiBA,SAAS,iBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACsD;AACtD,EAAA,IAAI,CAAC,UAAU,OAAO,EAAE,OAAO,EAAC,EAAG,OAAA,EAAS,EAAC,EAAE;AAC/C,EAAA,MAAM,IAAA,GAAO,8BAAA;AAAA,IACX,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GACrC;AAIA,EAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,IAAA,0BAAA,CAA2B,KAAA,EAAO;AAAA,MAChC,MAAA,EAAQ,CAAC,GAAA,KAAQ;AACf,QAAA,IAAA,CAAK,GAAG,GAAA,CAAI,UAAU,IAAI,GAAA,CAAI,EAAE,EAAE,CAAA,GAAI,GAAA;AAAA,MACxC;AAAA,KACD,CAAA;AAAA,EACH;AACA,EAAA,MAAM,QAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,GAAG,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC7C,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,WAAA,CAAY,GAAG,CAAA;AAClC,IAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,KAAA,CAAM,GAAG,CAAA,GAAI,GAAA;AAAA,EACjC;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAE;AAC7C;AAQA,eAAsB,kBACpB,MAAA,EACA,GAAA,EACA,WAAA,EACA,OAAA,GAAoC,EAAC,EACT;AAC5B,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,KAAS,eAAA,CAAgB,GAAA,EAAK,OAAO,OAAO,CAAA;AAC5D,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,EAAE,WAAA,EAAa,QAAQ,CAAA;AACpD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA;AAEtC,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,UAAA,IAAc,MAAA,CAAO,QAAA,EAAU;AACnD,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,UAAA;AAAA,MACN,MAAA,EAAQ,OAAO,QAAA,CAAS,MAAA;AAAA,MACxB,SAAA,EAAW,OAAO,QAAA,CAAS,SAAA;AAAA,MAC3B,MAAM,MAAA,CAAO;AAAA,KACf;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAC1B,IAAA,OAAO,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,EAChD;AAKA,EAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,MAAA,EAAQ,MAAM,CAAA;AACnD,EAAA,MAAM,IAAA,GAAO,UAAA,CAAW,MAAA,EAAQ,YAAY,CAAA;AAE5C,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAA,IAAS,IAAK,EAAC;AAGxC,EAAA,MAAM,WAAA,GAAc,QAAQ,QAAA,GACxB,iBAAA;AAAA,IAAkB,SAAA;AAAA,IAAW,OAAA,CAAQ,QAAA;AAAA,IAAU,MAAA;AAAA,IAAQ,IAAA;AAAA,IAAM,CAAC,GAAA,KAC5D,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GAC5B,GACA,SAAA;AAKJ,EAAA,MAAM,YAAYE,SAAAA,CAAU;AAAA,IAC1B,QAAQ,WAAA,CAAY,GAAA,CAAI,CAAC,KAAA,KAAU,MAAM,EAAE,CAAA;AAAA,IAC3C,MAAA,EAAQ,OAAO,IAAA,CAAK;AAAA,GACrB,CAAA;AAKD,EAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,eAAA,EAAgB,GAAI,qBAAA;AAAA,IAC9C,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA,CAAQ;AAAA,GACV;AAGA,EAAA,MAAM,YAAA,GAAeA,UAAU,EAAE,IAAA,EAAM,iBAAiB,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,CAAA;AAKpF,EAAA,MAAM,EAAE,QAAA,EAAU,IAAA,EAAM,WAAA,EAAY,GAAI,oBAAA;AAAA,IACtC,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA,CAAQ;AAAA,GACV;AAIA,EAAA,MAAM,EAAE,OAAO,OAAA,EAAS,WAAA,KAAgB,iBAAA,CAAkB,IAAA,EAAM,MAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAA;AACxF,EAAA,MAAM,QAAA,GAAWA,UAAU,EAAE,IAAA,EAAM,aAAa,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,CAAA;AAC5E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,MAAA;AAAA,IACA,MAAA,EAAQ,WAAA;AAAA,IACR,SAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,GAAI,YAAA,GAAe,EAAE,YAAA,KAAiB,EAAC;AAAA,IACvC,IAAA,EAAM,cAAA;AAAA,MACJ,MAAA,CAAO,IAAA;AAAA,MACP,GAAG,WAAA;AAAA,MACH,SAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA;AACF,GACF;AACF;;;AC/aO,SAAS,aAAA,CAAc,KAAa,KAAA,EAAwB;AACjE,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,CAAM,MAAA,KAAW,IAAI,GAAA,GAAM,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAC5E;AAMO,SAAS,WAAA,CAAY,QAAgB,KAAA,EAAwB;AAClE,EAAA,OAAO,aAAA,CAAc,CAAA,IAAA,EAAO,MAAM,CAAA,CAAA,EAAI,KAAK,CAAA;AAC7C;AAGO,SAAS,kBAAA,CAAmB,UAAA,EAAoB,MAAA,EAAgB,KAAA,EAAwB;AAC7F,EAAA,OAAO,cAAc,CAAA,WAAA,EAAc,UAAU,CAAA,CAAA,EAAI,MAAM,IAAI,KAAK,CAAA;AAClE;AAGO,SAAS,aAAA,CAAc,OAAA,EAAiB,MAAA,EAAgB,KAAA,EAAwB;AACrF,EAAA,OAAO,cAAc,CAAA,MAAA,EAAS,OAAO,CAAA,CAAA,EAAI,MAAM,IAAI,KAAK,CAAA;AAC1D;AAOO,SAAS,aAAA,CAAc,SAAiB,KAAA,EAAwB;AACrE,EAAA,OAAO,aAAA,CAAc,CAAA,MAAA,EAAS,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA;AAChD;AAMO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,aAAA,CAAc,WAAW,KAAK,CAAA;AACvC;AAOO,SAAS,aAAA,CAAc,MAAiB,KAAA,EAA0B;AACvE,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,EAAA,GAAA,CAAI,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC9B,EAAA,KAAA,MAAW,MAAA,IAAU,KAAK,IAAA,EAAM,GAAA,CAAI,IAAI,WAAA,CAAY,MAAA,EAAQ,KAAK,CAAC,CAAA;AAClE,EAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,EAAa;AACzC,IAAA,GAAA,CAAI,IAAI,kBAAA,CAAmB,UAAA,EAAY,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAC,CAAA;AAAA,EAC5D;AACA,EAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,cAAc,KAAA,EAAO,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAC,CAAA;AACjF,EAAA,KAAA,MAAW,OAAA,IAAW,KAAK,MAAA,EAAQ,GAAA,CAAI,IAAI,aAAA,CAAc,OAAA,EAAS,KAAK,CAAC,CAAA;AACxE,EAAA,OAAO,CAAC,GAAG,GAAG,CAAA,CAAE,IAAA,EAAK;AACvB;AAoCO,SAAS,gBAAA,CAAiB,OAA0B,OAAA,EAAwC;AACjG,EAAA,MAAM,EAAE,OAAM,GAAI,OAAA;AAClB,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,SAAA;AAAA,IACL,KAAK,WAAA;AAAA,IACL,KAAK,QAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,CAAA,EAAG,KAAA,CAAM,UAAU,IAAI,KAAA,CAAM,EAAE,CAAA,CAAA,EAAI,KAAK,CAAC,CAAA;AAC9D,MAAA,KAAA,MAAW,MAAA,IAAU,OAAA,CAAQ,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,mBAAmB,KAAA,CAAM,UAAA,EAAY,MAAA,EAAQ,KAAK,CAAC,CAAA;AAClG,MAAA,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC/B,MAAA;AAAA,IACF,KAAK,WAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC/B,MAAA;AAAA,IACF,KAAK,OAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,aAAA,CAAc,KAAA,CAAM,EAAA,EAAI,KAAK,CAAC,CAAA;AACvC,MAAA,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC/B,MAAA;AAEA;AAEJ,EAAA,OAAO,CAAC,GAAG,IAAI,CAAA,CAAE,IAAA,EAAK;AACxB;AAmCO,SAAS,cAAA,CAAe,EAAE,KAAA,EAAO,IAAA,EAAM,KAAI,EAAkC;AAClF,EAAA,OAAO,CAAC,cAAA,EAAgB,KAAA,IAAS,EAAA,EAAI,IAAA,IAAQ,IAAI,GAAG,CAAA;AACtD;AAiCA,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EACrC,WAAA,GAAc;AACZ,IAAA,KAAA,CAAM,kDAAkD,CAAA;AACxD,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AAAA,EACd;AACF,CAAA;AASA,SAAS,YAAe,IAAA,EAA0C;AAChE,EAAA,OAAO,YAAY,IAAA,EAAK;AAC1B;AAgGA,eAAsB,eAAA,CACpB,KAAA,EACA,QAAA,EACA,YAAA,EACA,IAAA,EACyB;AACzB,EAAA,IAAI,KAAA;AAEJ,EAAA,MAAM,KAAA,GAAQ,KAAA;AAAA,IACZ,YAAY,YAAqC;AAC/C,MAAA,KAAA,GAAQ,MAAM,IAAA,EAAK;AACnB,MAAA,MAAM,IAAI,kBAAA,EAAmB;AAAA,IAC/B,CAAC,CAAA;AAAA,IACD,QAAA;AAAA,IACA,EAAE,IAAA,EAAM,CAAC,GAAG,YAAY,CAAA,EAAG,YAAY,KAAA;AAAM,GAC/C;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,KAAA,EAAM;AAAA,EACrB,SAAS,KAAA,EAAO;AAGd,IAAA,IAAI,EAAE,KAAA,YAAiB,kBAAA,CAAA,EAAqB,MAAM,KAAA;AAAA,EACpD;AAGA,EAAA,MAAM,KAAA,GAAQ,KAAA;AACd,EAAA,MAAM,KAAA,GAAQ,KAAA;AAAA,IACZ,WAAA,CAAY,YAAY,KAAK,CAAA;AAAA,IAC7B,QAAA;AAAA,IACA,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,YAAY,KAAA;AAAM,GACxC;AACA,EAAA,OAAO,KAAA,EAAM;AACf;;;AC9UA,SAAS,IAAI,KAAA,EAA+B;AAC1C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,IAAA;AACjE;AAGA,SAAS,aAAa,KAAA,EAAwB;AAC5C,EAAA,OAAO,kBAAkB,IAAA,CAAK,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,GAAG,CAAA;AAC9D;AAOA,SAAS,cAAA,CAAe,OAAgB,MAAA,EAAiD;AAMvF,EAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACtC,IAAA,MAAM,MAAO,KAAA,CAA4B,GAAA;AACzC,IAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,GAAS,IAAI,GAAA,GAAM,IAAA;AAAA,EAC3D;AACA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AAC5D,EAAA,IAAI,YAAA,CAAa,KAAK,CAAA,EAAG,OAAO,KAAA;AAChC,EAAA,OAAO,MAAA,CAAO,KAAK,CAAC,KAAA,KAAU,MAAM,EAAA,KAAO,KAAK,GAAG,GAAA,IAAO,IAAA;AAC5D;AAYO,SAAS,gBAAgB,MAAA,EAA+B;AAC7D,EAAA,OAAO,MAAA,CAAO,SAAS,GAAG,CAAA,GAAI,OAAO,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,GAAI,IAAA;AAC5D;AAEO,SAAS,kBAAA,CACd,MAAA,EACA,MAAA,GAAmC,EAAC,EAC1B;AACV,EAAA,MAAM,MAAM,MAAA,CAAO,QAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,GAAM,UAAU,CAAC,CAAA;AACtC,EAAA,MAAM,cAAA,GAAiB,GAAA,CAAI,GAAA,GAAM,gBAAgB,CAAC,CAAA;AAClD,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,GAAA,GAAM,YAAY,CAAC,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,GAAA,GAAM,YAAY,GAAG,MAAM,CAAA;AAC7D,EAAA,MAAM,UAAA,GAAa,GAAA,GAAM,YAAY,CAAA,KAAM,IAAA;AAC3C,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,GAAA,GAAM,cAAc,CAAC,CAAA;AAG9C,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,GAAM,MAAM,CAAC,CAAA,IAAK,GAAA,CAAI,GAAA,GAAM,OAAO,CAAC,CAAA;AACzD,EAAA,MAAM,QAAQ,QAAA,IAAY,QAAA;AAE1B,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,IAAI,KAAA,KAAU,IAAA,EAAM,QAAA,CAAS,KAAA,GAAQ,KAAA;AACrC,EAAA,IAAI,cAAA,WAAyB,WAAA,GAAc,cAAA;AAE3C,EAAA,MAAM,UAAU,UAAA,IAAc,KAAA;AAC9B,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA;AACnD,IAAA,QAAA,CAAS,SAAA,GAAY;AAAA,MACnB,GAAI,OAAA,KAAY,IAAA,GAAO,EAAE,KAAA,EAAO,OAAA,KAAY,EAAC;AAAA,MAC7C,GAAI,cAAA,GAAiB,EAAE,WAAA,EAAa,cAAA,KAAmB,EAAC;AAAA,MACxD,GAAI,UAAA,GAAa,EAAE,MAAA,EAAQ,CAAC,EAAE,GAAA,EAAK,UAAA,EAAY,CAAA,EAAE,GAAI,EAAC;AAAA,MACtD,GAAI,QAAA,KAAa,IAAA,GAAO,EAAE,MAAA,EAAQ,QAAA,KAAa;AAAC,KAClD;AAAA,EACF;AAEA,EAAA,IAAI,UAAA,EAAY,QAAA,CAAS,MAAA,GAAS,EAAE,OAAO,KAAA,EAAM;AAIjD,EAAA,IAAI,YAAA,EAAc,QAAA,CAAS,UAAA,GAAa,EAAE,WAAW,YAAA,EAAa;AAElE,EAAA,OAAO,QAAA;AACT;AAyBO,SAAS,uBAAA,CACd,QACA,IAAA,EACU;AACV,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,MAAM,MAAA,GAAkC,EAAE,GAAG,IAAA,EAAK;AAClD,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,SAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AACnD,IAAA,MAAM,WAAA,GAAe,OAAmC,GAAG,CAAA;AAC3D,IAAA,IAAI,aAAA,CAAc,SAAS,CAAA,IAAK,aAAA,CAAc,WAAW,CAAA,EAAG;AAC1D,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,EAAE,GAAG,WAAA,EAAa,GAAG,SAAA,EAAU;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;ACPA,SAAS,eAAA,CAAgB,QAA2B,MAAA,EAA4C;AAC9F,EAAA,OAAO,kBAAA,CAAmB,QAAQ,MAAM,CAAA;AAC1C;AAEA,eAAe,kBAAA,GAA2C;AACxD,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,SAAA,EAAU;AACtC,EAAA,OAAO,YAAY,OAAA,GAAU,WAAA;AAC/B;AAGA,IAAM,gBAAA,GAAiC,CAAC,EAAA,EAAI,QAAA,EAAU,YACpD,cAAA,CAAe,EAAA,EAAI,QAAA,EAAU,EAAE,MAAM,OAAA,CAAQ,IAAA,EAAM,UAAA,EAAY,OAAA,CAAQ,YAAY,CAAA;AAG9E,SAAS,kBAAkB,OAAA,EAA0C;AAM1E,EAAA,MAAM,WAAA,GAAc,OAAO,GAAA,EAAa,WAAA,KAAyD;AAC/F,IAAA,MAAM,eAAe,YAAwC;AAC3D,MAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,EAAE,aAAa,CAAA;AACxD,MAAA,OAAO,iBAAA,CAAkB,MAAA,EAAQ,GAAA,EAAK,WAAA,EAAa;AAAA;AAAA,QAEjD,QAAA,EAAU,QAAQ,UAAA,CAAW;AAAA,OAC9B,CAAA;AAAA,IACH,CAAA;AAWA,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA;AAC3C,IAAA,IAAI,CAAC,OAAA,CAAQ,KAAA,IAAS,gBAAgB,WAAA,IAAe,SAAA,SAAkB,YAAA,EAAa;AAEpF,IAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,CAAM,KAAA;AAC5B,IAAA,MAAM,QAAQ,MAAM,eAAA;AAAA,MAClB,gBAAA;AAAA,MACA,cAAA,CAAe,EAAE,KAAA,EAAO,IAAA,EAAM,QAAQ,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,MACvD,CAAC,eAAA,CAAgB,KAAK,CAAC,CAAA;AAAA,MACvB,YAAY;AACV,QAAA,MAAM,OAAA,GAAU,MAAM,YAAA,EAAa;AACnC,QAAA,OAAO,EAAE,OAAO,OAAA,EAAS,IAAA,EAAM,cAAc,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA,EAAE;AAAA,MACpE;AAAA,KACF;AACA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,IAAA,GAAO,OAAO,KAAA,KAAgD;AAClE,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,KAAA,CAAM,MAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,aAAa,IAAI,CAAA;AAC7B,IAAA,MAAM,WAAA,GAAc,MAAM,kBAAA,EAAmB;AAE7C,IAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,GAAA,EAAK,WAAW,CAAA;AAClD,IAAA,OAAA,CAAQ,SAAS,OAAA,CAAQ,IAAA,EAAM,EAAE,GAAA,EAAK,aAAa,CAAA;AAEnD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,UAAA;AAEH,QAAA,OAAO,OAAA,CAAQ,YAAY,iBAAA,CAAkB,OAAA,CAAQ,MAAM,CAAA,GAAI,QAAA,CAAS,QAAQ,MAAM,CAAA;AAAA,MACxF,KAAK,WAAA;AACH,QAAA,OAAO,QAAA,EAAS;AAAA,MAClB,KAAK,QAAA,EAAU;AACb,QAAA,MAAM,2BACJC,GAAAA;AAAA,UAAC,cAAA;AAAA,UAAA;AAAA,YACC,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,YAAY,OAAA,CAAQ,UAAA;AAAA,YACpB,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,WAAW,OAAA,CAAQ,SAAA;AAAA,YACnB,UAAU,OAAA,CAAQ,QAAA;AAAA,YAClB,OAAO,OAAA,CAAQ,KAAA;AAAA,YAIf,cAAc,OAAA,CAAQ;AAAA;AAAA,SACxB;AAKF,QAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,EAAM,OAAO,QAAA;AAC1B,QAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,iBAAA,CAAkB,OAAA,CAAQ,QAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAC,CAAA;AAAA,MACvF;AAAA;AACF,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,OAAO,KAAA,KAA+C;AACzE,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,KAAA,CAAM,MAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,aAAa,IAAI,CAAA;AAC7B,IAAA,MAAM,WAAA,GAAc,MAAM,kBAAA,EAAmB;AAM7C,IAAA,IAAI,OAAA,CAAQ,KAAA,IAAS,WAAA,KAAgB,WAAA,EAAa;AAChD,MAAA,MAAMC,QAAAA,GAAU,MAAM,WAAA,CAAY,GAAA,EAAK,WAAW,CAAA;AAClD,MAAA,IAAIA,QAAAA,CAAQ,IAAA,KAAS,QAAA,EAAU,OAAO,EAAC;AACvC,MAAA,OAAO,OAAA,CAAQ,QAAA,GACX,OAAA,CAAQ,QAAA,CAASA,SAAQ,MAAA,EAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAA,GACrD,eAAA,CAAgBA,QAAAA,CAAQ,MAAA,EAAQA,SAAQ,MAAM,CAAA;AAAA,IACpD;AAEA,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,EAAE,aAAa,CAAA;AACxD,IAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,CAAkB,MAAA,EAAQ,KAAK,WAAW,CAAA;AAChE,IAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,QAAA,EAAU,OAAO,EAAC;AACvC,IAAA,OAAO,OAAA,CAAQ,QAAA,GACX,OAAA,CAAQ,QAAA,CAAS,QAAQ,MAAA,EAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAA,GACrD,eAAA,CAAgB,OAAA,CAAQ,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAAA,EACpD,CAAA;AAMA,EAAA,MAAM,gBAAA,GAAmB,OACvB,KAAA,EACA,MAAA,KACsB;AACtB,IAAA,MAAM,CAAC,IAAA,EAAM,cAAc,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,YAAA,CAAa,KAAK,CAAA,EAAG,MAAM,CAAC,CAAA;AAC9E,IAAA,OAAO,uBAAA,CAAwB,gBAAgB,IAAI,CAAA;AAAA,EACrD,CAAA;AAEA,EAAA,OAAO,EAAE,MAAM,gBAAA,EAAiB;AAClC;ACtMA,SAAS,aAAa,OAAA,EAA2B;AAC/C,EAAA,OAAO,UAAU,OAAO,CAAA,CACrB,MAAA,CAAO,CAAC,YAAY,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAC,EAC3C,GAAA,CAAI,CAAC,YAAY,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC,CAAA;AACtC;AAGA,SAAS,WAAA,CAAY,SAAiB,MAAA,EAA+C;AACnF,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,OAAA,IAAW,SAAA,CAAU,OAAO,CAAA,EAAG;AACxC,IAAA,IAAI,CAAC,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC5B,MAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC,CAAA;AACrC,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AACtD,IAAA,QAAA,CAAS,IAAA,CAAK,kBAAA,CAAmB,KAAK,CAAC,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAChC;AAGA,SAAS,SAAS,OAAA,EAAyB;AACzC,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACnC;AAOO,SAAS,oBAAA,CACd,QACA,OAAA,EACqB;AACrB,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,MAAA,CAAO,UAAU,EAAC;AACnD,EAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AACvB,EAAA,MAAM,UAAiC,EAAC;AACxC,EAAA,MAAM,IAAA,GAAO,CAAC,KAAA,EAAoB,MAAA,KAAyB;AACzD,IAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,KAAA,CAAM,EAAA,IAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,EAAG;AACvE,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,OAAA,EAAS,KAAA,CAAM,IAAI,OAAA,EAAS,KAAA,CAAM,OAAA,EAAS,MAAA,EAAQ,CAAA;AAAA,IACpE;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,OAAA,uBAAc,GAAA,EAA2B;AAC/C,EAAA,MAAM,SAAA,GAAY,CAAC,MAAA,KAAkC;AACnD,IAAA,IAAI,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AAC/B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAA,GAAS,OAAO,MAAA,CAAO,EAAE,WAAA,EAAa,WAAA,EAAa,QAAQ,CAAA;AAC3D,MAAA,OAAA,CAAQ,GAAA,CAAI,QAAQ,MAAM,CAAA;AAAA,IAC5B;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AAgBA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA+C;AACjE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAiC;AAErD,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAI7C,IAAA,MAAM,KAAA,GAAQ,eAAe,KAAK,CAAA;AAClC,IAAA,MAAM,eAAe,KAAA,CAAM,MAAA,GAAS,CAAC,KAAA,CAAM,MAAM,IAAI,OAAA,CAAQ,OAAA;AAE7D,IAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,MAAA,MAAM,MAAA,GAAS,UAAU,MAAM,CAAA;AAE/B,MAAA,IAAI,KAAA;AACJ,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,aAAA,CAAc,KAAA,CAAM,UAAU,CAAA;AACpD,QAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,UAAA,IAAA,CAAK,KAAA,EAAO,CAAA,sBAAA,EAAyB,KAAA,CAAM,UAAU,CAAA,qBAAA,CAAuB,CAAA;AAC5E,UAAA;AAAA,QACF;AAKA,QAAA,MAAM,IAAA,GAAsB,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,UACzD,YAAY,KAAA,CAAM,UAAA;AAAA,UAClB,IAAI,GAAA,CAAI,EAAA;AAAA,UACR,MAAA,EAAQ,EAAE,IAAA,EAAM,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,EAAQ,GAAA,CAAI,QAAQ,CAAA;AAAE,SACrD,CAAE,CAAA;AACF,QAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,CAAC,GAAA,CAAI,EAAA,EAAI,GAAG,CAAC,CAAC,CAAA;AACrD,QAAA,KAAA,GAAQ,EAAC;AACT,QAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AAGtB,UAAA,IAAI,OAAO,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA,KAAM,YAAY,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA,KAAM,EAAA,EAAI;AACzE,UAAA,MAAM,QAAA,GAAW,YAAY,GAAA,EAAK,CAAC,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAC,CAAA;AACtD,UAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAClB,UAAA,KAAA,CAAM,IAAA,CAAK,GAAA,GAAM,CAAC,GAAG,KAAA,CAAM,MAAA,EAAQ,GAAG,QAAA,CAAS,QAAQ,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,QACpE;AAAA,MACF,CAAA,MAAA,IAAW,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG;AAClC,QAAA,KAAA,GAAQ,CAAC,MAAM,OAAO,CAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,MAAM,YAAY,OAAA,CAAQ,MAAA,GAAS,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAQ,CAAA;AAC5D,QAAA,IAAI,aAAa,IAAA,EAAM;AACrB,UAAA,IAAA,CAAK,OAAO,yDAAyD,CAAA;AACrE,UAAA;AAAA,QACF;AACA,QAAA,KAAA,GAAQ,EAAC;AACT,QAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,UAAA,MAAM,IAAA,GAAO,WAAA,CAAY,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAA;AAChD,UAAA,IAAI,SAAS,IAAA,EAAM;AACjB,YAAA,IAAA,CAAK,OAAO,CAAA,6BAAA,EAAgC,UAAA,CAAW,IAAA,CAAK,KAAK,CAAC,CAAA,CAAE,CAAA;AACpE,YAAA;AAAA,UACF;AACA,UAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,QACjB;AAAA,MACF;AAMA,MAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,MAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AAMxB,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,IAAA,EAAM,MAAM,CAAA;AAK9C,QAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAE1B,UAAA,IAAI,MAAA,CAAO,MAAA,KAAW,UAAA,EAAY,UAAA,IAAc,CAAA;AAChD,UAAA;AAAA,QACF;AACA,QAAA,IAAI,MAAA,CAAO,QAAA,GAAW,YAAY,CAAA,KAAM,IAAA,EAAM;AAM9C,QAAA,MAAM,EAAE,OAAA,EAAS,WAAA,KAAgB,aAAA,CAAc,IAAA,EAAM,QAAQ,OAAO,CAAA;AACpE,QAAA,IAAI,CAAC,WAAA,EAAa;AAChB,UAAA,IAAA;AAAA,YACE,KAAA;AAAA,YACA,CAAA,CAAA,EAAI,IAAI,CAAA,WAAA,EAAc,MAAM,CAAA,6EAAA;AAAA,WAC9B;AACA,UAAA;AAAA,QACF;AACA,QAAA,MAAM,QAAA,GAAW,QAAA,CAAS,OAAA,CAAQ,OAAO,CAAA,GAAI,OAAA;AAO7C,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,GACjB,CAAA,EAAG,KAAA,CAAM,EAAE,CAAA,EAAG,uBAAuB,CAAA,GAAA,EAAM,uBAAuB,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA,EAAG,uBAAuB,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,EAAE,CAAA,CAAA,GAC9I,CAAA,EAAG,KAAA,CAAM,EAAE,CAAA,EAAG,uBAAuB,CAAA,IAAA,EAAO,uBAAuB,GAAG,IAAI,CAAA,CAAA;AAC9E,QAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA,EAAG,KAAA,CAAM,GAAA,CAAI,QAAA,EAAU,EAAE,KAAA,EAAO,MAAA,EAAQ,CAAA;AAC/D,QAAA,MAAM,YAAY,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA,wBAAS,GAAA,EAAoB;AAChE,QAAA,IAAI,CAAC,UAAU,GAAA,CAAI,MAAM,GAAG,SAAA,CAAU,GAAA,CAAI,QAAQ,QAAQ,CAAA;AAC1D,QAAA,OAAA,CAAQ,GAAA,CAAI,OAAO,SAAS,CAAA;AAAA,MAC9B;AAEA,MAAA,IAAI,SAAS,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,UAAA,KAAe,MAAM,MAAA,EAAQ;AAC5D,QAAA,IAAA;AAAA,UACE,KAAA;AAAA,UACA,yBAAyB,KAAA,CAAM,UAAU,cAAc,KAAA,CAAM,MAAM,uBAAuB,MAAM,CAAA,oCAAA;AAAA,SAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,UAAiC,EAAC;AACxC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,EAAE,KAAA,EAAO,KAAK,KAAA,EAAO;AACpC,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA;AAGnC,IAAA,MAAM,SAAA,GAAY,aAAa,SAAA,CAAU,IAAA,GAAO,IAAI,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA,GAAI,MAAA;AACpF,IAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,GAAA,EAAK,GAAI,SAAA,GAAY,EAAE,UAAA,EAAY,EAAE,SAAA,EAAU,EAAE,GAAI,IAAK,CAAA;AAAA,EAC3E;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAO,EAAE,GAAA,GAAM,CAAA,CAAE,GAAA,GAAM,EAAA,GAAK,CAAA,CAAE,GAAA,GAAM,CAAA,CAAE,GAAA,GAAM,IAAI,CAAE,CAAA;AAEnE,EAAA,OAAO,EAAE,SAAS,OAAA,EAAQ;AAC5B;AAaO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,OAAO,YAAY;AACjB,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,YAAY,EAAE,WAAA,EAAa,aAAa,CAAA;AACrE,IAAA,OAAO,oBAAA,CAAqB,MAAA,EAAQ,OAAO,CAAA,CAAE,OAAA;AAAA,EAC/C,CAAA;AACF;AC1RA,eAAsB,yBAAA,CACpB,OAAA,EACA,SAAA,EACA,MAAA,EAC+B;AAC/B,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAC/C,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,yCAAA,EAA0C;AAAA,EACtF;AACA,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,CAAU,WAAW,CAAA,EAAG;AAC3D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,yCAAA,EAA0C;AAAA,EACtF;AACA,EAAA,IAAI,CAAE,MAAM,2BAAA,CAA4B,OAAA,EAAS,SAAA,EAAW,MAAM,CAAA,EAAI;AACpE,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,4BAAA,EAA6B;AAAA,EACzE;AACA,EAAA,MAAM,IAAA,GAAO,qBAAqB,OAAO,CAAA;AACzC,EAAA,IAAI,CAAC,IAAA,CAAK,EAAA,EAAI,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,IAAA,CAAK,OAAA,EAAQ;AACrE,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,KAAK,KAAA,EAAM;AACvC;ACIO,SAAS,sBAAsB,OAAA,EAA0D;AAC9F,EAAA,MAAM,OAAA,GAA+B,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,SAAS,CAAC,GAAG,OAAA,CAAQ,OAAO,CAAA,EAAE;AAC3F,EAAA,OAAO;AAAA,IACL,MAAM,KAAK,OAAA,EAAqC;AAC9C,MAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,IAAA,EAAK;AACnC,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,2BAA2B,CAAA;AACjE,MAAA,MAAM,aAAa,MAAM,yBAAA,CAA0B,OAAA,EAAS,SAAA,EAAW,QAAQ,MAAM,CAAA;AACrF,MAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,QAAA,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,KAAA,EAAO,UAAA,CAAW,OAAA,EAAQ,EAAG,EAAE,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AAAA,MACnF;AACA,MAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,UAAA,CAAW,KAAA,EAAO,OAAO,CAAA;AA4BvD,MAAA,KAAA,MAAW,OAAO,IAAA,EAAM,aAAA,CAAc,KAAK,EAAE,MAAA,EAAQ,GAAG,CAAA;AACxD,MAAA,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,WAAA,EAAa,MAAM,CAAA;AAAA,IAC5C;AAAA,GACF;AACF;ACRA,IAAM,aAAA,GAAgB,EAAA;AAYtB,SAAS,eAAA,CAAgB,OAAe,MAAA,EAAmD;AACzF,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,MAAM,CAAC,CAAA;AACjE,EAAA,OAAO,EAAE,OAAO,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAK,CAAC,GAAG,MAAA,EAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,MAAM,MAAA,GAAS,KAAK,CAAC,CAAA,EAAE;AAC1G;AAsBO,SAAS,cAAA,CAAe,IAAA,EAAc,KAAA,EAAe,MAAA,EAAwB;AAClF,EAAA,MAAM,EAAE,OAAO,KAAA,EAAO,MAAA,EAAQ,OAAM,GAAI,eAAA,CAAgB,OAAO,MAAM,CAAA;AACrE,EAAA,MAAM,GAAA,GAAM,wDAAwD,KAAA,GAAQ,EAAE,IAAI,KAAA,GAAQ,EAAE,kbAAkb,IAAI,CAAA,SAAA,CAAA;AAClhB,EAAA,OAAO,CAAA,0BAAA,EAA6B,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAC/C;AAcO,SAAS,YAAY,KAAA,EAAoC;AAC9D,EAAA,MAAM,EAAE,OAAO,KAAA,EAAO,QAAA,EAAU,SAAS,SAAA,EAAW,MAAA,EAAQ,SAAQ,GAAI,KAAA;AACxE,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,GAAA,CAAI,MAAA,KAAW,GAAG,OAAO,IAAA;AAC7C,EAAA,MAAM,GAAA,GAAM,MAAM,GAAA,IAAO,EAAA;AAIzB,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,UAAA,GACzB,CAAA,EAAG,KAAA,CAAM,UAAA,CAAW,CAAA,GAAI,GAAG,CAAA,EAAA,EAAK,KAAA,CAAM,UAAA,CAAW,CAAA,GAAI,GAAG,CAAA,CAAA,CAAA,GACxD,MAAA;AACJ,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,IAAA,IAAQ,KAAA,CAAM,WAAW,IAAA,EAAM;AAKjD,IAAA,uBACED,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,eAAA,EAAc,UAAA;AAAA,QACd,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,GAAA;AAAA,QACA,SAAA;AAAA,QACC,GAAI,iBAAiB,EAAE,KAAA,EAAO,EAAE,cAAA,EAAe,KAAM;AAAC;AAAA,KACzD;AAAA,EAEJ;AAQA,EAAA,MAAM,cACJ,KAAA,CAAM,IAAA,IAAQ,OACV,IAAA,GACA,KAAA,CAAM,KAAK,UAAA,CAAW,gBAAgB,CAAA,GACpC,KAAA,CAAM,OACN,cAAA,CAAe,KAAA,CAAM,MAAM,KAAA,CAAM,KAAA,EAAO,MAAM,MAAM,CAAA;AAC5D,EAAA,MAAM,OAAO,WAAA,GAAc,EAAE,aAAa,MAAA,EAAiB,WAAA,KAAgB,EAAC;AAI5E,EAAA,MAAM,WAAA,GAAc,QAAA,IAAY,OAAA,KAAY,MAAA,GAAS,MAAA,GAAY,OAAA;AACjE,EAAA,uBACEA,GAAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,eAAA,EAAc,MAAA;AAAA,MACd,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,GAAA;AAAA,MACA,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,QAAQ,KAAA,CAAM,MAAA;AAAA,MACb,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,MACvC,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA,MAC7C,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,OAAA,EAAS,WAAA,KAAgB,EAAC;AAAA,MAC5D,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,MAC3C,GAAI,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MAC/C,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA,MACzC,GAAG,IAAA;AAAA,MACH,GAAI,iBAAiB,EAAE,KAAA,EAAO,EAAE,cAAA,EAAe,KAAM;AAAC;AAAA,GACzD;AAEJ;AAqBO,SAAS,sBAAA,CAAuB,OAAA,GAAmC,EAAC,EAAgB;AACzF,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,GAAA;AACzC,EAAA,MAAM,YAAA,GAAe,QAAQ,YAAA,IAAgB,GAAA;AAC7C,EAAA,MAAM,cAAA,GAAiB,QAAQ,cAAA,IAAkB,EAAA;AACjD,EAAA,OAAO,CAAC,EAAE,GAAA,EAAK,KAAA,EAAO,SAAQ,KAAM;AAClC,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC5C,IAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,SAAS,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,YAAY,CAAA,CAAA,EAAI,OAAA,IAAW,cAAc,CAAA,CAAA;AAAA,EAC9F,CAAA;AACF;AAmCA,SAAS,yBAAyB,OAAA,EAAoC;AACpE,EAAA,OAAO,SAAS,mBAAmB,KAAA,EAA2C;AAC5E,IAAA,MAAM,EAAE,KAAK,KAAA,EAAO,MAAA,EAAQ,KAAK,UAAA,EAAY,IAAA,EAAK,GAAI,oBAAA,CAAqB,KAAK,CAAA;AAChF,IAAA,IAAI,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAC7B,IAAA,uBACEA,GAAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,EAAE,GAAA,EAAK,KAAA,EAAO,KAAA,IAAS,IAAA,EAAM,MAAA,EAAQ,MAAA,IAAU,IAAA,EAAM,GAAA,EAAK,UAAA,EAAY,IAAA,EAAK;AAAA,QACjF,GAAI,MAAM,QAAA,KAAa,IAAA,GAAO,EAAE,QAAA,EAAU,IAAA,KAAS,EAAC;AAAA,QACpD,GAAI,QAAQ,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,EAAC;AAAA,QACjE,GAAI,QAAQ,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,GAAI,EAAC;AAAA,QAC9D,GAAI,QAAQ,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI;AAAC;AAAA,KACvE;AAAA,EAEJ,CAAA;AACF;AAwBO,SAAS,kBAAA,CAAmB,OAAA,GAAqC,EAAC,EAAkB;AACzF,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,aAAA;AAAA,IACV,cAAA,EAAgB,yBAAyB,OAAO;AAAA,GAClD;AACF;AA0BO,SAAS,eAAA,CAAgB,OAAA,GAAkC,EAAC,EAAsB;AAIvF,EAAA,OAAO,CAAC,EAAE,KAAA,EAAO,GAAA,EAAK,WAAW,QAAA,EAAU,KAAA,EAAO,OAAA,EAAQ,qBACxDA,GAAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,EAAE,GAAG,KAAA,EAAO,GAAA,EAAI;AAAA,MACtB,GAAI,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MAC/C,GAAI,QAAA,GAAW,EAAE,QAAA,EAAU,IAAA,KAAS,EAAC;AAAA,MACrC,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,MACvC,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,MAC3C,GAAI,QAAQ,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,EAAC;AAAA,MACjE,GAAI,QAAQ,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI;AAAC;AAAA,GACvE;AAEJ;;;ACzTO,SAAS,mBAAA,CACd,QAAA,EACA,QAAA,EACA,YAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,QAAA,EAAU,YAAY,EAAE,CAAC,CAAA;AAC1D,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,OAAO,SAAS,WAAA,CAAY,KAAA,CAAM,WAAW,CAAA,EAAG,cAAc,SAAA,KAAc,IAAA;AAC9E;;;ACpBO,IAAM,oBAAA,GAAuB;AAAA,EAClC,SAAA,EAAW,mBAAA;AAAA,EACX,SAAA,EAAW,mBAAA;AAAA,EACX,UAAA,EAAY,qBAAA;AAAA,EACZ,gBAAA,EAAkB;AACpB;AASA,SAAS,QAAQ,KAAA,EAA+C;AAC9D,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,EAAK;AAC5B,EAAA,OAAO,OAAA,KAAY,MAAA,IAAa,OAAA,CAAQ,MAAA,GAAS,IAAI,OAAA,GAAU,MAAA;AACjE;AAQO,SAAS,eAAe,GAAA,EAAwD;AACrF,EAAA,MAAM,WAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,oBAAoB,CAAA,EAAG;AAC9D,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AAC/B,IAAA,IAAI,KAAA,KAAU,MAAA,EAAW,QAAA,CAAS,GAA0B,CAAA,GAAI,KAAA;AAAA,EAClE;AACA,EAAA,OAAO,QAAA;AACT;ACoBA,eAAe,cAAA,CACb,SAAA,EACA,SAAA,EACA,IAAA,EACA,IAAA,EACkB;AAClB,EAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,CAAA,EAAG,UAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,UAAA,CAAA,EAAc;AAAA,IAC5E,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,IAC9C,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,MAAM,IAAA,EAAM,MAAA,EAAQ,QAAQ,CAAA;AAAA,IACnD,KAAA,EAAO;AAAA,GACR,CAAA;AACD,EAAA,MAAM,OAAQ,MAAM,QAAA,CAAS,MAAK,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAGzD,EAAA,IAAI,CAAC,QAAA,CAAS,EAAA,IAAM,IAAA,EAAM,WAAW,SAAA,EAAW;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,cAAA,EAAiB,IAAI,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,MAC/C,IAAA,EAAM,YAAA,IAAgB,IAAA,EAAM,OAAA,IAAW,kBACzC,CAAA;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,IAAA,CAAK,KAAA;AACd;AAGA,SAAS,eAAe,IAAA,EAAgC;AACtD,EAAA,MAAM,YAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,SAAA,CAAU,GAAG,CAAA;AAC3C,IAAA,IAAI,MAAA,CAAO,OAAA,EAAS,SAAA,CAAU,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,SAAA;AACT;AAGA,SAAS,eAAe,IAAA,EAAmC;AACzD,EAAA,MAAM,YAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,MAAA,GAAS,oBAAA,CAAqB,SAAA,CAAU,GAAG,CAAA;AACjD,IAAA,IAAI,MAAA,CAAO,OAAA,EAAS,SAAA,CAAU,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,SAAA;AACT;AAgBO,SAAS,kBAAA,CACd,OAAA,EACA,OAAA,EACA,WAAA,EACe;AAGf,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,MAAM,SAAA,GAAY,cAAA,CAAe,OAAA,CAAQ,SAAS,CAAA;AAIlD,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,CAAC,GAAG,OAAA,CAAQ,MAAM,CAAA,EAAG,kBAAA,CAAmB,SAAA,EAAW,OAAO,CAAC,CAAA;AAc5F,EAAA,MAAM,kBACJ,WAAA,KAAgB,WAAA,GACZ,SAAA,CAAU,GAAA,CAAI,CAAC,GAAA,MAAS,EAAE,KAAA,EAAO,GAAA,EAAK,WAAW,GAAA,EAAK,QAAA,EAAU,EAAC,GAAI,CAAA,GACrE,SAAA;AAEN,EAAA,OAAO,yBAAA,CAA0B;AAAA,IAC/B,WAAW,OAAA,CAAQ,SAAA;AAAA,IACnB,OAAA;AAAA,IACA,MAAA;AAAA;AAAA;AAAA;AAAA,IAIA,SAAA,EAAW,cAAA,CAAe,OAAA,CAAQ,SAAA,IAAa,EAAE,CAAA;AAAA,IACjD,WAAA,EAAa,CAAC,GAAG,OAAA,CAAQ,WAAW,CAAA;AAAA,IACpC,SAAA,EAAW,eAAA;AAAA,IACX,QAAQ,OAAA,CAAQ;AAAA,GACjB,CAAA;AACH;AAGA,eAAsB,eAAA,CACpB,SACA,WAAA,EACwB;AACxB,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAC9C,EAAA,MAAM,IAAA,GAAgC,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAU;AAErE,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,gBAAgB,OAAA,EAAS;AAC3B,IAAA,IAAI,CAAC,QAAQ,UAAA,EAAY;AAGvB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,GAAW,MAAM,cAAA,CAAe,SAAA,EAAW,OAAA,CAAQ,WAAW,wBAAA,EAA0B;AAAA,MACtF,GAAG,IAAA;AAAA,MACH,OAAO,OAAA,CAAQ;AAAA,KAChB,CAAA;AAAA,EACH,CAAA,MAAO;AACL,IAAA,OAAA,GAAW,MAAM,cAAA;AAAA,MACf,SAAA;AAAA,MACA,OAAA,CAAQ,SAAA;AAAA,MACR,mBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,kBAAA,CAAmB,OAAA,EAAS,OAAA,EAAS,WAAW,CAAA;AACzD;;;ACjHA,IAAM,oBAAA,GAAuB,CAAC,OAAA,EAAS,OAAO,CAAA;AAE9C,SAAS,OAAO,KAAA,EAAyC;AACvD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GACrE,KAAA,GACD,EAAC;AACP;AAEA,SAASE,KAAI,KAAA,EAAwB;AACnC,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAC7C;AAEA,SAAS,MAAM,KAAA,EAA2C;AACxD,EAAA,OAAO,KAAA,CAAM,QAAQ,KAAK,CAAA,GAAI,MAAM,GAAA,CAAI,MAAM,IAAI,EAAC;AACrD;AAOO,SAAS,cAAA,CACd,MAAA,EACA,OAAA,GAAiC,EAAC,EACrB;AACb,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,WAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,EAAC;AAC5C,EAAA,MAAM,kBAAA,GAAqB,YAAY,kBAAA,IAAsB,UAAA;AAC7D,EAAA,MAAM,oBAAA,GAAuB,YAAY,oBAAA,IAAwB,YAAA;AACjE,EAAA,MAAM,WAAA,GAAc,YAAY,qBAAA,IAAyB,oBAAA;AAEzD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,EAAE,aAAa,GAAI,OAAA,CAAQ,MAAA,GAAS,EAAE,QAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,IAAK,CAAA;AACnG,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,IAAS,IAAK,EAAC;AACrC,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,EAAA,EAAI,KAAK,CAAU,CAAC,CAAA;AAE3E,EAAA,MAAM,QAAA,GAAW,CAAC,UAAA,KAA2C;AAC3D,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,aAAA,CAAc,UAAU,CAAA;AAC9C,IAAA,OAAO,MAAA,CAAO,EAAA,GAAK,MAAA,CAAO,SAAA,GAAY,EAAC;AAAA,EACzC,CAAA;AAGA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,kBAAkB,CAAA,CAAE,CAAC,CAAA,IAAK,IAAA;AAIpD,EAAA,MAAM,OAAA,GAAU,SAAS,oBAAoB,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAa,CAAC,IAAA,KAClB,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAA,KAAS,GAAA,CAAgC,KAAA,KAAU,IAAI,CAAA,IAAK,IAAA;AAG5E,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAuC;AACvD,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACjC,MAAA,IAAI,KAAA,SAAc,KAAA,CAAM,GAAA;AAExB,MAAA,OAAO,KAAA,CAAM,WAAW,MAAM,CAAA,IAAK,MAAM,UAAA,CAAW,GAAG,IAAI,KAAA,GAAQ,MAAA;AAAA,IACrE;AACA,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,KAAK,CAAA,CAAE,GAAA;AAC1B,IAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,GAAS,IAAI,GAAA,GAAM,MAAA;AAAA,EAC3D,CAAA;AAGA,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAuC;AACpD,IAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,IAAA,OAAO,OAAO,GAAA,CAAI,UAAA,KAAe,QAAA,IAAY,OAAO,GAAA,CAAI,EAAA,KAAO,QAAA,GAC3D,EAAE,YAAY,GAAA,CAAI,UAAA,EAAY,EAAA,EAAI,GAAA,CAAI,IAAG,GACzC,IAAA;AAAA,EACN,CAAA;AAGA,EAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAAwC;AAC3D,IAAA,MAAM,IAAA,GAAO,OAAO,KAAK,CAAA;AACzB,IAAA,MAAM,YAAA,GAAe,KAAK,YAAA,KAAiB,IAAA;AAC3C,IAAA,MAAM,OAAOA,IAAAA,CAAI,IAAA,CAAK,IAAI,CAAA,IAAKA,IAAAA,CAAI,KAAK,GAAG,CAAA;AAC3C,IAAA,MAAM,WAAWA,IAAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,KAAM,OAAO,MAAA,GAAS,EAAA,CAAA;AAExD,IAAA,IAAI,QAAA,KAAa,MAAA,IAAU,QAAA,KAAa,UAAA,EAAY;AAClD,MAAA,OAAO,IAAA,GAAO,EAAE,IAAA,EAAM,YAAA,EAAa,GAAI,IAAA;AAAA,IACzC;AACA,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AAC9B,MAAA,OAAO,GAAA,GAAM,EAAE,IAAA,EAAM,GAAA,EAAK,cAAa,GAAI,IAAA;AAAA,IAC7C;AACA,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAGlB,MAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,MAAM,IAAA,GAAO,MAAA,CAAO,WAAA,CAAY,GAAG,CAAA;AACnC,QAAA,IAAI,SAAS,IAAA,EAAM,OAAO,EAAE,IAAA,EAAM,MAAM,YAAA,EAAa;AAAA,MACvD;AAEA,MAAA,MAAM,MAAA,GAAS,OAAO,IAAI,CAAA;AAC1B,MAAA,IAAI,OAAO,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAClE,QAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,YAAA,EAAa;AAAA,MAC3C;AACA,MAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AAC5B,MAAA,IAAI,MAAM,OAAO,EAAE,MAAM,CAAA,CAAA,EAAI,IAAI,IAAI,YAAA,EAAa;AAClD,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA2D;AAC5E,IAAA,MAAM,KAAA,GAAQA,IAAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAG7B,IAAA,MAAM,WAAW,WAAA,CAAY,MAAA,IAAU,KAAA,GAAQ,KAAA,CAAM,OAAO,KAAK,CAAA;AACjE,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,QAAA,EAAU,OAAO,IAAA;AAChC,IAAA,MAAM,WAAA,GAAcA,IAAAA,CAAI,KAAA,CAAM,WAAW,CAAA;AACzC,IAAA,OAAO,EAAE,KAAA,EAAO,GAAG,QAAA,EAAU,GAAI,cAAc,EAAE,WAAA,EAAY,GAAI,EAAC,EAAG;AAAA,EACvE,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAA4D;AAC9E,IAAA,MAAM,KAAA,GAAQA,IAAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,IAAA,MAAM,WAAW,WAAA,CAAY,MAAA,IAAU,KAAA,GAAQ,KAAA,CAAM,OAAO,KAAK,CAAA;AACjE,IAAA,MAAM,WAAA,GAAcA,IAAAA,CAAI,KAAA,CAAM,WAAW,CAAA;AACzC,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,QAAQ,CAAA,CAClC,GAAA,CAAI,SAAS,CAAA,CACb,MAAA,CAAO,CAAC,KAAA,KAAoC,KAAA,KAAU,IAAI,CAAA;AAG7D,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,MAAA,KAAW,GAAG,OAAO,IAAA;AAC/C,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,GAAI,QAAA,GAAW,EAAE,MAAM,QAAA,CAAS,IAAA,KAAS,EAAC;AAAA,MAC1C,YAAA,EAAc,UAAU,YAAA,IAAgB,KAAA;AAAA,MACxC,GAAI,WAAA,GAAc,EAAE,WAAA,KAAgB,EAAC;AAAA,MACrC;AAAA,KACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,iBAAA,GAAoB,CAAC,IAAA,KAAqC;AAC9D,IAAA,MAAM,GAAA,GAAM,WAAW,IAAI,CAAA;AAC3B,IAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAC;AAClB,IAAA,MAAM,MAAA,GAAS,GAAA;AACf,IAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,CAAC,IAAA,KAAS,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAC,CAAC,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA,CACvB,GAAA,CAAI,UAAU,CAAA,CACd,MAAA,CAAO,CAAC,KAAA,KAAqC,KAAA,KAAU,IAAI,CAAA;AAAA,EAChE,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,UAAA;AAAA,IACA,iBAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AACF;;;AC3OA,IAAM,iBAAA,GAAoB,iCAAA;AAG1B,SAAS,gBAAgB,KAAA,EAA2B;AAClD,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,IAAI,GAAA,CAAI,KAAA,EAAO,iBAAiB,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,QAAA,CAAS,MAAA,KAAW,iBAAA,GAAoB,QAAA,GAAW,IAAA;AAC5D;AAGA,SAAS,oBAAoB,KAAA,EAAwB;AACnD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,IAAQ,EAAA,IAAQ,IAAA,KAAS,GAAA,EAAM,OAAO,IAAA;AAAA,EAC5C;AACA,EAAA,OAAO,KAAA;AACT;AAuBO,SAAS,kBAAA,CACd,KAAA,EACA,QAAA,GAAW,GAAA,EACI;AACf,EAAA,IAAI,UAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAI,OAAO,QAAA;AAClE,EAAA,IAAI,CAAC,MAAM,UAAA,CAAW,GAAG,KAAK,mBAAA,CAAoB,KAAK,GAAG,OAAO,IAAA;AACjE,EAAA,MAAM,QAAA,GAAW,gBAAgB,KAAK,CAAA;AACtC,EAAA,IAAI,QAAA,KAAa,MAAM,OAAO,IAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,QAAA,GAAW,QAAA,CAAS,SAAS,QAAA,CAAS,IAAA;AAC9D,EAAA,OAAO,eAAA,CAAgB,MAAM,CAAA,KAAM,IAAA,GAAO,IAAA,GAAO,MAAA;AACnD;AAWO,SAAS,sBAAA,CACd,YACA,aAAA,EACmB;AACnB,EAAA,IAAI,aAAA,KAAkB,MAAA,IAAa,aAAA,CAAc,MAAA,KAAW,CAAA,EAAG;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,kCAAA,EAAmC;AAAA,EAC/E;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,OAAO,UAAA,KAAe,QAAA,GAAW,IAAI,GAAA,CAAI,UAAA,EAAY,kBAAkB,CAAA,GAAI,UAAA;AAAA,EACnF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,gCAAA,EAAiC;AAAA,EAC7E;AAEA,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAC1C,EAAA,IAAI,UAAU,aAAA,EAAe;AAC3B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,wBAAA,EAAyB;AAAA,EACrE;AAEA,EAAA,MAAM,aAAa,kBAAA,CAAmB,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAU,CAAC,CAAA;AACtE,EAAA,IAAI,eAAe,IAAA,EAAM;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,mDAAA,EAAoD;AAAA,EAChG;AAEA,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,UAAA,EAAW;AAChC;AASO,SAAS,qBAAqB,UAAA,EAAkC;AACrE,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,OAAO,UAAA,KAAe,QAAA,GAAW,IAAI,GAAA,CAAI,UAAA,EAAY,kBAAkB,CAAA,GAAI,UAAA;AAAA,EACnF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,mBAAmB,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAU,CAAC,CAAA,IAAK,GAAA;AACjE;;;ACxFO,SAAS,mBAAmB,OAAA,EAAoD;AACrF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,OAAA,EAAqC;AAC7C,MAAA,MAAM,UAAA,GAAa,sBAAA,CAAuB,OAAA,CAAQ,GAAA,EAAK,QAAQ,KAAK,CAAA;AACpE,MAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,QAAA,OAAO,IAAI,SAAS,UAAA,CAAW,OAAA,EAAS,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAAA,MACvE;AACA,MAAA,MAAM,KAAA,GAAQ,MAAMC,SAAAA,EAAU;AAC9B,MAAA,KAAA,CAAM,MAAA,EAAO;AACb,MAAAC,QAAAA,CAAS,WAAW,UAAU,CAAA;AAAA,IAChC;AAAA,GACF;AACF;AASO,SAAS,yBAAA,GAAkD;AAChE,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,OAAA,EAAqC;AAC7C,MAAA,MAAM,KAAA,GAAQ,MAAMD,SAAAA,EAAU;AAC9B,MAAA,KAAA,CAAM,OAAA,EAAQ;AACd,MAAAC,QAAAA,CAAS,oBAAA,CAAqB,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,IAC5C;AAAA,GACF;AACF;;;ACsOA,SAAS,iBAAA,CACP,QACA,WAAA,EACe;AACf,EAAA,OAAO,OAAO,MAAA,KAAW,UAAA,GAAa,MAAA,CAAO,CAAC,GAAG,WAAW,CAAC,CAAA,GAAI,CAAC,GAAG,MAAM,CAAA;AAC7E;AAGO,SAAS,iBAAiB,OAAA,EAAwC;AACvE,EAAA,MAAM,GAAA,GAAM,cAAA,CAAe,OAAA,CAAQ,GAAA,IAAO,QAAQ,GAAG,CAAA;AACrD,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,OAAA,CAAQ,MAAA,EAAQ,QAAQ,WAAW,CAAA;AACpE,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,SAAA,KAAc,MAAA,IAAa,IAAI,SAAA,KAAc,MAAA;AAQ9D,EAAA,IAAI,CAAC,IAAA,KAAS,GAAA,CAAI,cAAc,MAAA,IAAa,GAAA,CAAI,cAAc,MAAA,CAAA,EAAY;AACzE,IAAA,MAAM,CAAC,OAAA,EAAS,OAAO,CAAA,GACrB,GAAA,CAAI,cAAc,MAAA,GACd,CAAC,oBAAA,CAAqB,SAAA,EAAW,qBAAqB,SAAS,CAAA,GAC/D,CAAC,oBAAA,CAAqB,SAAA,EAAW,qBAAqB,SAAS,CAAA;AACrE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,yBAAyB,OAAO,CAAA,YAAA,EAAe,OAAO,CAAA,oHAAA,EAE7C,OAAO,yBAAyB,OAAO,CAAA,2CAAA;AAAA,KAClD;AAAA,EACF;AAEA,EAAA,MAAM,WAAA,GAAiC,IAAA,GACnC,MAAA,GACA,OAAA,CAAQ,kBACN,UAAA,GACA,OAAA;AACN,EAAA,MAAM,YAAA,GAAe,IAAA,GAAQ,GAAA,CAAI,SAAA,GAAuB,OAAA,CAAQ,IAAA;AAEhE,EAAA,MAAM,aAAa,oBAAA,CAAqB;AAAA;AAAA;AAAA;AAAA,IAItC,GAAI,OAAA,CAAQ,KAAA,KAAU,KAAA,GAAQ,EAAC,GAAI,CAAC,kBAAA,CAAmB,OAAA,CAAQ,KAAA,IAAS,EAAE,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAK3E,GAAI,OAAA,CAAQ,UAAA,IAAc;AAAC,GAC5B,CAAA;AAID,EAAA,MAAM,cAA6B,yBAAA,CAA0B;AAAA,IAC3D,SAAA,EAAW,YAAA;AAAA,IACX,SAAS,OAAA,CAAQ,OAAA;AAAA,IACjB,MAAA;AAAA;AAAA,IAEA,WAAW,EAAC;AAAA,IACZ,WAAA,EAAa,CAAC,GAAG,OAAA,CAAQ,WAAW,CAAA;AAAA,IACpC,WAAW,EAAC;AAAA,IACZ,QAAQ;AAAC,GACV,CAAA;AAED,EAAA,MAAM,WAAA,GAAc,CAAC,OAAA,KAA0D;AAC7E,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,OAAO,eAAA;AAAA,QACL;AAAA,UACE,WAAW,GAAA,CAAI,SAAA;AAAA,UACf,WAAW,GAAA,CAAI,SAAA;AAAA,UACf,SAAS,OAAA,CAAQ,OAAA;AAAA,UACjB,aAAa,OAAA,CAAQ,WAAA;AAAA,UACrB,MAAA;AAAA,UACA,GAAI,IAAI,UAAA,GAAa,EAAE,YAAY,GAAA,CAAI,UAAA,KAAe,EAAC;AAAA,UACvD,GAAI,QAAQ,KAAA,GAAQ,EAAE,OAAO,OAAA,CAAQ,KAAA,KAAU;AAAC,SAClD;AAAA,QACA,OAAA,CAAQ;AAAA,OACV;AAAA,IACF;AACA,IAAA,IAAI,OAAA,CAAQ,eAAA,EAAiB,OAAO,OAAA,CAAQ,gBAAgB,OAAO,CAAA;AACnE,IAAA,OAAO,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACpC,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,OAAO,cAAA,GAAqC,EAAC,KAA4B;AAC3F,IAAA,MAAM,WAAA,GAAc,eAAe,WAAA,IAAe,WAAA;AAClD,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,EAAE,aAAa,CAAA;AAChD,IAAA,OAAO,eAAe,MAAA,EAAQ;AAAA,MAC5B,WAAA;AAAA,MACA,GAAI,QAAQ,OAAA,GAAU,EAAE,aAAa,OAAA,CAAQ,OAAA,KAAY;AAAC,KAC3D,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,YAAA,GAAuC,EAAC,KAAmB;AAC9E,IAAA,MAAM,EAAE,KAAA,EAAO,GAAG,IAAA,EAAK,GAAI,YAAA;AAC3B,IAAA,OAAO,iBAAA,CAAkB;AAAA,MACvB,WAAA;AAAA,MACA,UAAA;AAAA;AAAA;AAAA;AAAA,MAIA,GAAI,KAAA,KAAU,KAAA,GAAQ,EAAC,GAAI,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,YAAA,EAAa,EAAE;AAAA,MAC/E,GAAG;AAAA,KACJ,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,eAAA,GAAkB,CACtB,SAAA,GAA6D,OAE7D,qBAAA,CAAsB;AAAA;AAAA;AAAA,IAGpB,MAAA,EAAQ,QAAA,IAAY,SAAA,GAAY,SAAA,CAAU,SAAS,GAAA,CAAI,gBAAA;AAAA;AAAA;AAAA;AAAA,IAIvD,KAAA,EAAO,YAAA;AAAA,IACP,OAAA,EAAS,QAAQ,OAAA,CAAQ;AAAA,GAC1B,CAAA;AAIH,EAAA,MAAM,eAAe,MAA4B,kBAAA,CAAmB,EAAE,KAAA,EAAO,GAAA,CAAI,YAAY,CAAA;AAC7F,EAAA,MAAM,mBAAA,GAAsB,MAA4B,yBAAA,EAA0B;AAElF,EAAA,MAAM,kBAAA,GAAqB,OAAO,IAAA,KAA0D;AAC1F,IAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAMD,SAAAA,EAAU;AACtC,IAAA,MAAM,WAAA,GAA2B,YAAY,OAAA,GAAU,WAAA;AACvD,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,EAAE,aAAa,CAAA;AAChD,IAAA,MAAM,EAAE,QAAQ,IAAA,EAAK,GAAI,gBAAgB,YAAA,CAAa,IAAI,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA;AAC3E,IAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,EAAE,WAAA,EAAa,QAAQ,CAAA;AACpD,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA;AACxC,IAAA,OAAO,mBAAA,CAAoB,QAAA,CAAS,QAAA,EAAU,UAAA,CAAW,QAAQ,CAAA;AAAA,EACnE,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,YAAA;AAAA,IACA,WAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,GAAI,QAAQ,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,EAAa,OAAA,CAAQ,WAAA,EAAY,GAAI,EAAC;AAAA,IAChF,WAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACF;AACF;ACjbO,IAAM,OAAA,GAAU","file":"index.js","sourcesContent":["import type { ComponentType } from 'react'\nimport { ComponentRegistry } from '@elytracms/core/component-registry'\nimport type { ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry'\nimport { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations } from '@elytracms/core/runtime-renderer'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\n\n/**\n * One host-repo component: the manifest the builder edits against plus the\n * real React implementation that renders it (vision AD-2 — project components\n * are real code in the host repo, never generated).\n *\n * This is the STRICT shape — the one a hand-authored component module should\n * export (`export const heroComponent: HostComponent = …`), so the compiler\n * checks the implementation where it is written. It is assignable to the\n * config's {@link HostComponentEntry}, which is what {@link defineHostComponents}\n * accepts, so both hand-built lists and the config's own list register through\n * the one signature (EC-541).\n */\nexport interface HostComponent {\n manifest: ComponentManifest\n implementation: ComponentType<Record<string, unknown>>\n}\n\n/**\n * The registered component surface a host app hands to `<CanvasRenderer />`\n * and the catch-all route helper: a manifest registry plus the implementation\n * map, with registration problems surfaced as structured issues (never\n * thrown — an unknown component renders the EC-015 fallback).\n */\nexport interface HostComponents {\n registry: ComponentRegistry\n implementations: ComponentImplementations\n /**\n * Structural registration issues: the registry's own (duplicate ids, invalid\n * namespaces, …) plus `invalid-implementation` for an entry whose\n * `implementation` is not a React component (EC-541).\n */\n issues: readonly ComponentIssue[]\n}\n\nexport interface DefineHostComponentsOptions {\n /**\n * Include the platform primitives (`base.*`) that ship with\n * `@elytracms/core/runtime-renderer`. Defaults to `true` — graphs authored in the\n * builder assume them.\n */\n includeBasePrimitives?: boolean\n}\n\n/**\n * Component registration API for the embedded runtime (EC-144). The host repo\n * supplies real implementations + manifests; base primitives are included by\n * default. A host implementation registered under a primitive id replaces the\n * default implementation (the manifest of the first registration wins, which\n * is the primitive's — by design, so prop schemas stay canonical).\n *\n * Accepts the CONFIG's entry shape (`implementation: unknown`) rather than the\n * strict {@link HostComponent}: the config declares implementations opaquely,\n * and EC-539 made `createElytraHost({ ...site })` the contract, so a\n * compile-time check at this seam was already gone for the recommended path.\n * The check is RUNTIME instead (EC-541), through core's one registration step\n * `registerImplementations` (EC-555 — the studio registry and the preview\n * harness call the same one): a value that is not a component is\n * recorded as an `invalid-implementation` issue naming the component id and\n * the received kind, and is NOT put into `implementations` — so the renderer\n * takes its missing-component path (a visible EC-015 fallback) instead of\n * crashing the server render on `createElement` (\"Element type is invalid\")\n * or, for a string, silently rendering an HTML tag. The manifest still\n * registers, so the registry's own issues (duplicate id, invalid id) for that\n * entry are reported alongside.\n *\n * One consequence of the `unknown` parameter: an INLINE\n * `implementation: (props) => …` literal no longer gets `props` contextually\n * typed (it is `noImplicitAny` under strict). Annotate the parameter, or\n * author the entry as a {@link HostComponent} — which is the documented shape\n * anyway. (An overload pair was tried and does not restore it: TypeScript\n * resolves the context-sensitive literal against the wider overload.)\n */\nexport function defineHostComponents(\n components: readonly HostComponentEntry[] = [],\n options: DefineHostComponentsOptions = {},\n): HostComponents {\n const includeBase = options.includeBasePrimitives !== false\n const base = includeBase ? basePrimitives() : { manifests: [], implementations: {} }\n\n const registry = new ComponentRegistry([...base.manifests, ...components.map((c) => c.manifest)])\n\n const implementations: Record<string, ComponentType<Record<string, unknown>>> = {}\n for (const manifest of base.manifests) {\n const implementation = getImplementation(base.implementations, manifest.id)\n if (implementation) implementations[manifest.id] = implementation\n }\n // Host entries layer OVER the primitives: a valid host implementation under a\n // `base.*` id replaces the default; a withheld one leaves the default in place.\n const host = registerImplementations(components)\n Object.assign(implementations, host.implementations)\n\n return { registry, implementations, issues: [...registry.issues, ...host.issues] }\n}\n","import { isValidElement } from 'react'\nimport type { ReactNode } from 'react'\nimport type { ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { isBlockSequenceValueShape } from '@elytracms/core/rich-text'\nimport {\n RenderErrorBoundary,\n documentBodyCompositionNodes,\n documentBodyItems,\n renderDocumentBodyItems,\n serializeListingQuery,\n} from '@elytracms/core/runtime-renderer'\nimport type {\n DocumentBodyItem,\n RelationTarget,\n RenderAsset,\n RenderContext,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { HostComponents } from './components'\n\n/**\n * `<CanvasRenderer />` (EC-144, vision AD-1): an async React Server Component\n * that renders a resolved page (from `resolvePage`, EC-143) through\n * `@elytracms/core/runtime-renderer` with the host app's component registry. EC-015\n * fallback behavior is identical to the builder: a missing component, invalid\n * prop, or missing required slot renders a visible fallback — a page never\n * crashes and never silently omits a node.\n */\nexport interface CanvasRendererProps {\n /** The `resolvePage` result for the current URL. */\n result: ResolvePageResult\n /** Host component surface from `defineHostComponents`. */\n components: HostComponents\n /**\n * Delivery-shaped assets the page may reference (EC-195, from the render\n * outcome). An `asset`-typed Image prop resolves through these to a url +\n * intrinsic dimensions; an unknown id degrades to the visible fallback.\n */\n assets?: readonly ResolvedAsset[]\n /**\n * Populated composition-node `relation` props, keyed `collection:id` (EC-254,\n * from the render outcome). A block's `relation` prop (e.g. a ProductGrid's\n * picked products) resolves through these to the populated target; an unknown\n * key degrades to the block's own empty state. Absent → relation props pass\n * through unresolved (raw ref), the same degradation as a missing asset list.\n */\n relations?: Record<string, RelationTarget>\n /**\n * Documents each `listing` block resolved to, keyed by serialized query (EC-255,\n * from the render outcome). A ProductGrid keyed to a picked `category` reads its\n * products through this; an unknown key (or absent map) degrades to the block's\n * own empty state — the same degradation as a missing relation.\n */\n listings?: Record<string, RelationTarget[]>\n /**\n * URL paths for the documents this page's rich-text props LINK to, keyed\n * `collection:id` (EC-291, from the render outcome). A self-typed internal link\n * (`attrs.ref = {type:'reference',…}`) resolves through this to a real `href`; an\n * unknown key (or absent map) degrades to the renderer's visible `unresolved-ref`\n * issue (EC-290), never a silent `#`.\n */\n links?: Record<string, string>\n /**\n * The resolved document's collection-declared delivery presentation (EC-499,\n * from the render outcome): which field(s) hold the body. Absent → the EC-458\n * shape picker selects the body by VALUE, exactly as before.\n */\n presentation?: DeliveryPresentation\n}\n\n/**\n * A collection's `presentation` declaration (EC-499), resolved for delivery: the\n * body field list normalized from `string | string[]`. Plain data — it rides the\n * (serializable) render outcome and the RSC boundary like the baked\n * assets/relations/links do.\n *\n * EC-524: this renderer applies no CHROME. It carried a `wrapper` component id\n * briefly (never released); a detail wrapper is a page template, templates are\n * code, and the host now supplies one from its own route file via\n * `createCanvasRoute`'s `wrap` option. What stays here is content selection —\n * which is exactly what makes a canvas body and a rich-text body render\n * agnostically through the one entry point.\n */\nexport interface DeliveryPresentation {\n /** Field names whose values form the body, in render order. */\n body?: readonly string[]\n}\n\n/** Build the renderer's relation resolver from the baked, populated targets (EC-254). */\nfunction relationResolver(relations: Record<string, RelationTarget>): ResolveRelation {\n return (ref) => relations[`${ref.collection}:${ref.id}`] ?? null\n}\n\n/** Build the renderer's link resolver from the baked, doc-keyed URL paths (EC-291). */\nfunction linkResolver(links: Record<string, string>): ResolveLink {\n return (ref) => links[`${ref.collection}:${ref.id}`] ?? null\n}\n\n/** Build the renderer's listing resolver from the baked, query-keyed documents (EC-255). */\nfunction listingResolver(listings: Record<string, RelationTarget[]>): ResolveListing {\n return (query) => listings[serializeListingQuery(query)] ?? null\n}\n\n/** Build the renderer's asset resolver from the baked delivery assets (EC-195). */\nfunction assetResolver(assets: readonly ResolvedAsset[]): (id: string) => RenderAsset | null {\n const byId = new Map(assets.map((asset) => [asset.id, asset]))\n return (id) => {\n const asset = byId.get(id)\n return asset\n ? {\n url: asset.url,\n width: asset.width,\n height: asset.height,\n alt: asset.alt,\n focalPoint: asset.focalPoint,\n // EC-420: carry the LQIP so a placed base.Image blurs up on delivery.\n lqip: asset.lqip,\n meta: asset.meta,\n }\n : null\n }\n}\n\n/** Visible fallback when the renderer is handed a non-`ok` resolution. */\nfunction UnresolvedPageFallback(props: { status: string }): ReactNode {\n return (\n <div data-ec-fallback=\"unresolved-page\" data-ec-status={props.status}>\n {`This URL did not resolve to a page (status: ${props.status}).`}\n </div>\n )\n}\n\n/** Visible fallback when an `ok` resolution carries no renderable document. */\nfunction MissingDocumentFallback(): ReactNode {\n return (\n <div data-ec-fallback=\"missing-document\">\n {`Route resolved but carried no page document to render.`}\n </div>\n )\n}\n\n/**\n * Server-side per-node decorator: unwrap the renderer's client error\n * boundary. React Server Components cannot render class components, and a\n * boundary cannot recover during server rendering anyway (the renderer\n * already converts resolution errors into visible fallbacks before this\n * point). The wrapped element keeps its key, so output is unchanged.\n */\nfunction stripServerErrorBoundary(_node: ComponentNode, rendered: ReactNode): ReactNode {\n if (isValidElement(rendered) && rendered.type === RenderErrorBoundary) {\n return (rendered.props as { children?: ReactNode }).children ?? null\n }\n return rendered\n}\n\n/**\n * Render a resolved page server-side. Async so hosts can compose it like any\n * other RSC; resolution itself is synchronous (the content was prefetched by\n * the `ContentSource`).\n */\nexport async function CanvasRenderer(props: CanvasRendererProps): Promise<ReactNode> {\n const { result, components } = props\n\n if (result.status !== 'ok') {\n // The route helper answers redirect/notFound before rendering; direct\n // users of <CanvasRenderer /> still get a visible state, never a crash.\n return <UnresolvedPageFallback status={result.status} />\n }\n\n const ctx: RenderContext = {\n registry: components.registry,\n implementations: components.implementations,\n resolveAsset: assetResolver(props.assets ?? []),\n // EC-254: wire relation population only when the host baked the populated\n // targets, so a host that did not opt in keeps the raw-ref passthrough.\n ...(props.relations ? { resolveRelation: relationResolver(props.relations) } : {}),\n // EC-255: wire the listing resolver only when the host baked the query results,\n // so a host that did not opt in keeps listing props empty (the block's own state).\n ...(props.listings ? { resolveListing: listingResolver(props.listings) } : {}),\n // EC-291: wire the rich-text link resolver only when the host baked the link map,\n // so a host that did not opt in degrades a reference link to the visible\n // `unresolved-ref` issue (EC-290) rather than a wrong href.\n ...(props.links ? { resolveLink: linkResolver(props.links) } : {}),\n decorateNode: stripServerErrorBoundary,\n }\n\n const document = result.document\n\n // EC-187/EC-287: a routed document delivers its body directly — there is no\n // graph or layout indirection; shared chrome is composed in the host's\n // `app/layout.tsx`, and per-collection detail chrome in the host's own route\n // (`createCanvasRoute`'s `wrap`, EC-524). EC-499: WHICH field is the body, and\n // what shape it holds, is a declaration (`presentation.body`) with the EC-458\n // value-shape picker as the fallback — and one entry point renders a\n // composition body and a rich-text body alike. With no document, a visible\n // fallback.\n if (document) {\n return <>{renderDocumentBodyItems(bodyItemsOf(document, props.presentation), ctx)}</>\n }\n\n return <MissingDocumentFallback />\n}\n\n/**\n * The ordered body items of a resolved document (EC-499): the values of the\n * collection's declared `presentation.body` fields concatenated in declaration\n * order, or — when the collection declares nothing — the EC-458 value-shape\n * pick, unchanged. A declared field the document has no value for contributes\n * nothing (an empty body is legitimate); a declared field holding something that\n * is neither composition nor rich text contributes a visible fallback.\n */\nexport function bodyItemsOf(\n document: Record<string, unknown> | null | undefined,\n presentation?: DeliveryPresentation,\n): DocumentBodyItem[] {\n if (!document) return []\n const fields = presentation?.body ?? []\n if (fields.length > 0) return fields.flatMap((name) => documentBodyItems(document[name]))\n return documentBodyItems(compositionOf(document))\n}\n\n/**\n * Select the delivery composition from a resolved document (EC-187) — the\n * FALLBACK, used only when the collection declares no `presentation.body`\n * (EC-499). Kept exactly as it was so every undeclared collection renders\n * byte-for-byte what it rendered before: declaration beats shape-guessing, but\n * shape-guessing keeps working where nothing was declared.\n *\n * Its known limit is why the declaration exists: it can only ever select a\n * COMPOSITION, so a document whose body is a `richText` field was unrenderable\n * through the catch-all and needed a hand-written Next route.\n *\n * The\n * composition field can be named anything the host's collection schema chooses:\n * the conventional `body` (a `page`) or `content` (a routed content collection\n * like a `post`), but ALSO a host-specific name such as Konditorei's\n * `pageBuilder` (migrated faithfully from Sanity, EC-263). So the field is chosen\n * by VALUE — a composition is a `ComponentNode` or an array of them — NEVER by\n * name: a `body ?? content` name heuristic both picks a post's rich-text `body`\n * string over its `content` composition AND misses a `pageBuilder` entirely,\n * rendering nothing.\n *\n * `body` then `content` keep priority (so a content document's unrelated rich-text\n * `body` never shadows the conventional `content` composition); any other\n * composition-valued field is the generalized fallback.\n */\nfunction compositionOf(document: Record<string, unknown>): unknown {\n const body = document['body']\n // EC-547: a v2 value qualifies for the shape-pick only when it CARRIES\n // component roots — a prose-only sequence is renderable through a declared\n // `presentation.body`, but the undeclared fallback keeps its EC-458 promise\n // byte-identical: a document's prose `body` never shadows its conventional\n // `content` composition.\n if (isCompositionishSequence(body) || Array.isArray(body) || isComponentNodeValue(body)) return body\n const content = document['content']\n if (isCompositionishSequence(content) || Array.isArray(content) || isComponentNodeValue(content)) return content\n // Generalized (EC-263): the first field whose value is a composition — a node or\n // a non-empty array of nodes. `body`/`content` already took priority above.\n for (const value of Object.values(document)) {\n if (isCompositionishSequence(value)) return value\n if (isComponentNodeValue(value)) return value\n if (Array.isArray(value) && value.length > 0 && value.every(isComponentNodeValue)) return value\n }\n return undefined\n}\n\n/**\n * The routed document's COMPOSITION nodes as a `ComponentNode[]` (EC-205): the\n * same body selection the renderer uses, normalized to an array so a delivery\n * host can walk it (e.g. to page-scope baked assets, or to read the first\n * block's `presentation.fullBleed` hint). Non-node values are dropped.\n * `null`/no-document yields an empty array.\n *\n * EC-499: takes the collection's declared presentation when there is one, and\n * still returns COMPOSITION nodes only — a rich-text body yields `[]`, which is\n * the right answer for both of its readers (nothing to full-bleed; nothing that\n * `collectReferencedAssetIds` could walk). The rich-text half of a body is baked\n * separately in `route-core`.\n */\nexport function compositionNodesOf(\n document: Record<string, unknown> | null | undefined,\n presentation?: DeliveryPresentation,\n): ComponentNode[] {\n return documentBodyCompositionNodes(bodyItemsOf(document, presentation))\n}\n\n/** A v2 sequence that carries at least one component root (the shape-pick bar). */\nfunction isCompositionishSequence(value: unknown): boolean {\n return (\n isBlockSequenceValueShape(value) &&\n value.content.some(\n (node) =>\n typeof node === 'object' &&\n node !== null &&\n (node as { type?: unknown }).type === 'elytra/component',\n )\n )\n}\n\n/** Structural `ComponentNode` guard — a resolved field value is opaque at the type level. */\nfunction isComponentNodeValue(value: unknown): boolean {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { id?: unknown }).id === 'string' &&\n typeof (value as { componentId?: unknown }).componentId === 'string'\n )\n}\n","import type { CollectionDef, Locale, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport { cacheTags } from '@elytracms/core/content'\nimport type { CacheTags, ContentClient, Perspective, ResolvedAsset } from '@elytracms/core/content'\n\n/**\n * The content seam of the embedded runtime (EC-144, vision AD-1/AD-4): the\n * catch-all route helper consumes this interface, so where the content comes\n * from is swappable. EC-144 ships the in-memory snapshot bridge\n * (`createContentSnapshot` over a `PersistenceAdapter`); EC-145/146 plug a\n * Convex-backed source into the exact same seam.\n *\n * Accessors are synchronous over a prefetched `ContentLookup` (vision AD-4);\n * any fetching happens when the source is loaded, before clients are minted.\n *\n * EC-574: the `CanvasDataSource` shape and the `sources` member (the data-binding\n * subsystem's per-section query definitions, EC-166) are gone with that subsystem.\n */\nexport interface ContentSource {\n /** The project's locale configuration (default + supported set, EC-018). */\n readonly locales: LocaleConfig\n /**\n * Mint a typed accessor client (EC-143) bound to a perspective. The locale\n * defaults to the project default; the route helper overrides it per\n * request from the URL's locale prefix.\n */\n client(init: { perspective: Perspective; locale?: Locale }): ContentClient\n /**\n * The route records the source's clients resolve against (EC-171, additive).\n * Lets enumerating consumers — the sitemap helper — walk the same route\n * table `resolvePage` uses, instead of keeping a second list. Optional so\n * existing custom sources stay valid; without it the sitemap helper needs\n * explicit routes.\n */\n readonly routes?: readonly RouteRecord[]\n /**\n * The project's collection definitions (EC-499, additive) — config-owned\n * (AD-11), already prefetched by every source that reads a schema. The route\n * helper reads the resolved document's collection from here to honour its\n * declared `presentation` (which field(s) hold the delivery body, and the\n * optional detail wrapper). Optional so existing custom sources stay valid;\n * without it every collection is treated as undeclared and the EC-458 shape\n * picker decides, exactly as before.\n */\n readonly collections?: readonly CollectionDef[]\n /**\n * The project's delivery-shaped assets (EC-195, additive). Lets the route\n * helper bake the assets a page may reference into the (serializable) render\n * outcome, so the renderer can resolve an `asset`-typed Image prop to a usable\n * url + intrinsic dimensions — and a cached page keeps its images during a\n * backend outage. Optional so existing custom sources stay valid; without it,\n * asset-typed props fall back to their raw value (the renderer's visible\n * degradation), never a crash.\n */\n assets?(): readonly ResolvedAsset[]\n}\n\n/**\n * Merge accessor cache tags into one deterministic set (sorted, deduplicated)\n * — used by the route helper to combine the `resolvePage` tags with tags\n * added by the host's payload factory (e.g. a `listDocuments` call). The\n * merged set is what the EC-146 `onTags` hook receives.\n */\nexport function mergeCacheTags(first: CacheTags, ...rest: readonly CacheTags[]): CacheTags {\n const all = [first, ...rest]\n return cacheTags({\n docs: all.flatMap((t) => t.docs),\n collections: all.flatMap((t) => t.collections),\n routes: all.flatMap((t) => t.routes),\n assets: all.flatMap((t) => t.assets),\n locale: first.locale,\n })\n}\n","import { documentKey, mergeRouteRecords, parseLocaleConfig } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfigInput,\n RedirectRecord,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport {\n createContentClient,\n createContentContext,\n isDocumentHistory,\n resolvedAsset,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n Perspective,\n} from '@elytracms/core/content'\nimport type { AssetRecord, PersistenceAdapter } from '@elytracms/core/persistence'\nimport type { ContentSource } from './source'\n\n/**\n * Bridge from a `PersistenceAdapter` to the synchronous `ContentLookup` the\n * accessors (EC-143) read through: prefetch the project's collections,\n * documents, assets, and graphs ONCE (async), then serve every accessor call\n * synchronously from memory. This is how `apps/example-site` reads the seeded\n * in-memory fixture project; a Convex-backed `ContentSource` (EC-145/146)\n * replaces this bridge without touching the route helper.\n *\n * The assembly half is exposed separately as {@link createStaticContentSource}\n * (EC-156): a host that already holds the prefetched data as plain values —\n * e.g. a delivery-snapshot payload fetched over Convex's public HTTP query\n * API — assembles the exact same `ContentSource` without faking an adapter.\n */\n\nexport interface ContentSnapshotOptions {\n /** Route records served by `resolvePage` (EC-019). */\n routes?: readonly RouteRecord[]\n /** Redirect records served by `resolvePage` (EC-019/020). */\n redirects?: readonly RedirectRecord[]\n /**\n * Locale configuration — **required**, never defaulted (EC-530). The default\n * locale is the storage key every localized value is written under, so a\n * fabricated `en` is a data migration to undo, not a config edit: content\n * authored under one locale and served under another, silently (EC-512\n * measured exactly that). EC-490 made a config declaring no locales an error\n * and EC-519 collapsed the two declarations into one; this was the last place\n * in the chain still inventing the value, and it sits in the package that\n * resolves the URLs. Hand it the project's `LocaleConfig` — the one the\n * config declares. Parsed here (EC-580): a `default` outside `locales` throws.\n */\n locales: LocaleConfigInput\n}\n\nexport interface ContentSnapshot extends ContentSource {\n readonly projectId: string\n}\n\n/**\n * Plain prefetched data for {@link createStaticContentSource} (EC-156): the\n * same inputs `createContentSnapshot` reads from a `PersistenceAdapter`, as\n * already-loaded values. Everything content-bearing is optional — an absent\n * piece simply resolves to \"not visible\", with the runtime's explicit\n * fallbacks (never a crash).\n */\nexport interface StaticContentData {\n projectId: string\n /** Locale configuration — **required**, never defaulted (see EC-530 above); parsed (EC-580). */\n locales: LocaleConfigInput\n /** Route records served by `resolvePage` (EC-019). */\n routes?: readonly RouteRecord[]\n /** Redirect records served by `resolvePage` (EC-019/020). */\n redirects?: readonly RedirectRecord[]\n collections?: readonly CollectionDef[]\n /**\n * Stored document sources. Either a bare `CmsDocument` (gated by its own\n * `state`) or a `DocumentHistory` carrying the live draft + the pinned\n * published snapshot (EC-224). Perspective gating happens in the pure\n * accessors (`selectPerspective`, EC-142): a `published` client sees a\n * history's pinned snapshot (or nothing), and never a bare `draft` row.\n */\n documents?: readonly ContentDocumentSource[]\n assets?: readonly AssetRecord[]\n /**\n * Resolve a serve URL for assets without a stored `url`. Defaults to an\n * explicit unresolved-placeholder scheme (never a silent empty string).\n */\n assetUrl?: (asset: AssetRecord) => string\n}\n\n// `mergeRouteRecords` moved to `@elytracms/core/cms-core` (EC-384) so the studio's\n// document-URL preview can merge stored routes with the home fallback through the\n// exact same rule the delivery resolver below uses — re-exported here (imported\n// above) to keep this module's/`@elytracms/next`'s existing public surface.\nexport { mergeRouteRecords }\n\n/**\n * Assemble a `ContentSource` from plain prefetched data — the pure half of\n * {@link createContentSnapshot}, shared with hosts that fetched the data\n * themselves (e.g. the EC-156 live delivery snapshot). Accessor semantics are\n * byte-identical across both paths: the same `ContentLookup` feeds the same\n * pure `@elytracms/core/content` client.\n */\nexport function createStaticContentSource(data: StaticContentData): ContentSnapshot {\n // EC-580: THE delivery parse of the declared locale config into the branded\n // `LocaleConfig` every reader below holds — every source (empty, seeded, live)\n // is assembled here, so a `default` outside `locales` cannot reach a client. It\n // throws: a host builds its empty source at boot, so that is where it fails.\n const locales = parseLocaleConfig(data.locales)\n const routes = data.routes ?? []\n const redirects = data.redirects ?? []\n const assetUrl =\n data.assetUrl ?? ((asset: AssetRecord) => `asset://unresolved/${asset.storageKey}`)\n\n const collections = new Map<string, CollectionDef>()\n for (const collection of data.collections ?? []) collections.set(collection.id, collection)\n\n const documents = new Map<string, ContentDocumentSource>()\n const byCollection = new Map<string, ContentDocumentSource[]>()\n for (const source of data.documents ?? []) {\n const doc = isDocumentHistory(source) ? source.draft : source\n documents.set(documentKey(doc), source)\n const list = byCollection.get(doc.collection) ?? []\n list.push(source)\n byCollection.set(doc.collection, list)\n }\n\n const assets = new Map<string, AssetRecord>()\n for (const record of data.assets ?? []) assets.set(record.id, record)\n\n const lookupFor = (_perspective: Perspective): ContentLookup => ({\n collection: (id) => collections.get(id),\n document: (ref: DocumentRef) => documents.get(documentKey(ref)),\n documents: (collectionId) => byCollection.get(collectionId) ?? [],\n asset: (id) => assets.get(id),\n assetUrl: (asset) => asset.url ?? assetUrl(asset),\n })\n\n const client = (init: { perspective: Perspective; locale?: Locale }): ContentClient =>\n createContentClient({\n lookup: lookupFor(init.perspective),\n context: createContentContext({\n locale: init.locale ?? locales.default,\n perspective: init.perspective,\n locales,\n }),\n routes,\n redirects,\n })\n\n return {\n projectId: data.projectId,\n locales,\n client,\n routes,\n // EC-195: the delivery-shaped assets, so the renderer can resolve an\n // `asset`-typed Image prop (an asset id) to a usable url + intrinsic\n // dimensions. Baked into the (serializable) render outcome by the route\n // helper, so a cached page keeps its images during a backend outage.\n assets: () =>\n [...assets.values()].map((record) => resolvedAsset(record, record.url ?? assetUrl(record))),\n // EC-499: the schema the route helper reads a collection's declared\n // `presentation` from (which field is the delivery body, which component\n // wraps it). Already prefetched for the lookup above — exposing it costs\n // nothing and keeps the declaration reachable without a second wiring seam.\n ...(data.collections ? { collections: data.collections } : {}),\n }\n}\n\n/**\n * Prefetch a project's content from a `PersistenceAdapter` into an in-memory\n * `ContentSource`. The snapshot is a point-in-time view: load once per\n * request (or memoize across requests for fixture-backed sites) — accessors\n * never reach back to the adapter.\n */\nexport async function createContentSnapshot(\n adapter: PersistenceAdapter,\n projectId: string,\n options: ContentSnapshotOptions,\n): Promise<ContentSnapshot> {\n // --- Prefetch (the only async part) ------------------------------------\n const schema = await adapter.cms.findSchema(projectId)\n const documentRecords = await adapter.cms.listDocuments(projectId)\n const versionRecords = await adapter.cms.listAllDocumentVersions(projectId)\n const assetRecords = await adapter.assets.listAssets(projectId)\n // Redirects are editorial DATA (EC-303): read the ENABLED ones from the adapter's\n // redirects rail (projected to the routing `RedirectRecord`), like documents/assets\n // — NOT from config. An explicit `options.redirects` still overrides (fixtures/tests).\n const redirectRecords = await adapter.redirects.listRedirects(projectId)\n const redirects: RedirectRecord[] = redirectRecords\n .filter((record) => record.enabled)\n .map((record) => ({\n id: record.id,\n from: record.from,\n to: record.to,\n permanent: record.permanent,\n }))\n\n // Hybrid sources (EC-224): a `publishedVersion` pointer yields a\n // `DocumentHistory` (draft = the live row, published = the pinned snapshot);\n // an unpinned record stays a bare `CmsDocument` gated by its `state`.\n // Backward-compatible — pre-pointer data resolves exactly as before.\n const versionsByKey = new Map<string, Map<number, CmsDocument>>()\n for (const record of versionRecords) {\n const key = `${record.collection}:${record.docId}`\n let byVersion = versionsByKey.get(key)\n if (!byVersion) {\n byVersion = new Map()\n versionsByKey.set(key, byVersion)\n }\n byVersion.set(record.version, record.snapshot)\n }\n const documents: ContentDocumentSource[] = documentRecords.map((record) => {\n if (record.publishedVersion === undefined) return record.document\n // A dangling pin under-serves to `null` for `published` — never the draft.\n const published = versionsByKey\n .get(`${record.document.collection}:${record.document.id}`)\n ?.get(record.publishedVersion)\n return { draft: record.document, published, versions: [] }\n })\n\n // --- Assemble via the shared pure path ----------------------------------\n // Captured once: `BlobStorageAdapter.url` is synchronous by contract.\n return createStaticContentSource({\n projectId,\n locales: options.locales,\n ...(options.routes ? { routes: options.routes } : {}),\n redirects: options.redirects ? [...options.redirects] : redirects,\n ...(schema ? { collections: schema.collections } : {}),\n documents,\n assets: assetRecords,\n assetUrl: (asset) => adapter.blobs.url(asset.storageKey),\n })\n}\n","import { cacheTags } from '@elytracms/core/content'\nimport { splitLocalePath } from './locale-url'\nimport type {\n CacheTags,\n ContentClient,\n Perspective,\n ResolvedAsset,\n ResolvedDocument,\n ResolvePageResult,\n} from '@elytracms/core/content'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport {\n collectReferencedAssetIds,\n collectReferencedListings,\n collectReferencedRelations,\n collectReferencedRichTextLinks,\n collectRichTextRefsInValue,\n documentBodyCompositionNodes,\n documentBodyRichTextValues,\n richTextEmbedsInValue,\n} from '@elytracms/core/runtime-renderer'\nimport type { ListingQuery, RelationTarget, ResolveRelation } from '@elytracms/core/runtime-renderer'\nimport { presentationBodyFields } from '@elytracms/core/cms-core'\nimport { mergeCacheTags } from './source'\nimport type { ContentSource } from './source'\nimport { bodyItemsOf } from './canvas-renderer'\nimport type { DeliveryPresentation } from './canvas-renderer'\n\n/**\n * The pure half of the catch-all route helper (EC-144): URL → outcome, with\n * no Next.js imports — so redirects, 404s, locale handling, asset/relation/\n * listing/link baking, and tag merging are all unit-testable as plain\n * functions. `route.tsx` maps\n * each outcome onto `redirect()` / `permanentRedirect()` / `notFound()` /\n * `<CanvasRenderer />`.\n */\n\n/** What the route helper tells a host callback about the request it is serving. */\nexport interface CanvasRequestInfo {\n /** The raw request path (including any locale prefix). */\n url: string\n perspective: Perspective\n}\n\n/**\n * What a host's `wrap` function is told about the request it is wrapping\n * (EC-524 — see `CanvasRouteOptions.wrap`).\n *\n * Every field is something the route already had in hand at the wrap point:\n * nothing is gathered for this seam, and nothing was invented for it.\n */\nexport interface CanvasWrapContext extends CanvasRequestInfo {\n /**\n * The collection of the document this URL resolved to — **the branch key**.\n * `undefined` when the resolution carried no document, so a host branching on\n * a collection id naturally wraps nothing there (which is what the removed\n * `presentation.wrapper` declaration did too: it only applied with a document).\n */\n collection?: string\n /**\n * The resolved document. Verbatim the `document` prop the removed wrapper\n * component received, so a wrapper ported to this seam needs no other source\n * for its field-level chrome (the title, a byline, a date).\n */\n document: ResolvedDocument | null\n /**\n * The whole resolution — the same value the `metadata` callback already\n * receives, so this is the established host-callback argument on this route\n * rather than a new surface. It carries `dynamicParams`, `route`,\n * `localeFallback` and `issues` for a wrapper that needs more than the\n * document (EC-499 recorded wanting exactly that and could not have it across\n * a serializable declaration).\n */\n result: ResolvePageResult\n}\n\n/**\n * PURE core (EC-524): build the wrap context from a render outcome's resolution\n * and the request info. Extracted from `route.tsx` so the derivation — above all\n * \"no document ⇒ no collection ⇒ a collection-branching host wraps nothing\" — is\n * testable without Next's request APIs.\n */\nexport function canvasWrapContext(\n result: ResolvePageResult,\n info: CanvasRequestInfo,\n): CanvasWrapContext {\n const collection = result.document?.collection\n return {\n ...info,\n ...(collection !== undefined ? { collection } : {}),\n document: result.document,\n result,\n }\n}\n\nexport type CanvasPageOutcome =\n | { kind: 'redirect'; target: string; permanent: boolean; tags: CacheTags }\n | { kind: 'not-found'; tags: CacheTags }\n | {\n kind: 'render'\n result: ResolvePageResult\n /**\n * The delivery-shaped assets the page may reference (EC-195). Baked into\n * the outcome (serializable) so the renderer resolves `asset`-typed Image\n * props to a url + intrinsic dimensions, and a cached page keeps its\n * images during a backend outage.\n */\n assets: readonly ResolvedAsset[]\n /**\n * The composition-node `relation` props this page references, populated to\n * depth-1 delivery shape and keyed `collection:id` (EC-254). Baked into the\n * outcome (serializable) so `<CanvasRenderer />` rebuilds\n * `RenderContext.resolveRelation` from it inside the RSC — a function (the\n * content client) cannot ride the data cache or cross the RSC boundary, so\n * the data is baked exactly as the page-scoped `assets` list is.\n */\n relations: Record<string, RelationTarget>\n /**\n * The documents each `listing` block on this page resolved to (EC-255),\n * keyed by serialized query. Baked into the outcome (serializable) so\n * `<CanvasRenderer />` rebuilds `RenderContext.resolveListing` from it inside\n * the RSC — like `relations`, because the content client that runs the query\n * cannot ride the data cache or cross the RSC boundary. A listing depends on\n * collection MEMBERSHIP (a product joining/leaving the category), so its\n * invalidation rides the per-query `listDocuments` tags merged into `tags`.\n */\n listings: Record<string, RelationTarget[]>\n /**\n * The public URL path of every document a rich-text prop on this page LINKS\n * to (EC-291), keyed `collection:id`. Baked into the outcome (serializable) so\n * `<CanvasRenderer />` rebuilds `RenderContext.resolveLink` from it inside the\n * RSC — like `relations`, because the content client that derives the URL\n * (`resolveLink` → `urlForDocument`) cannot ride the data cache or cross the\n * RSC boundary. A link depends on the target's IDENTITY (its slug/parent →\n * path), so its invalidation rides the per-link doc tags merged into `tags`:\n * renaming, re-parenting, or first-publishing a linked document re-bakes the\n * embedding page's href.\n */\n links: Record<string, string>\n /**\n * The resolved document's collection-declared delivery presentation\n * (EC-499): which field(s) are the render body. Plain, serializable data —\n * handed to `<CanvasRenderer />` so ONE catch-all renders every\n * collection's body, whatever its shape. Absent when the collection\n * declares nothing (or the source exposes no collections), and the EC-458\n * value-shape picker then decides exactly as before.\n */\n presentation?: DeliveryPresentation\n tags: CacheTags\n }\n\n/** Build the request path from a Next catch-all `slug` param. */\nexport function pathFromSlug(slug: readonly string[] | undefined): string {\n return '/' + (slug ?? []).join('/')\n}\n\n/**\n * Derive the request locale from a leading URL segment (`/de/about` → locale\n * `de`, path `/about`). EC-486 moved the implementation to `./locale-url`, so\n * this direction and the sitemap's inverse are one definition rather than two\n * that can drift; re-exported here because it is a published API surface.\n */\nexport { splitLocalePath }\n\nexport interface ResolveCanvasPageOptions {\n /**\n * Component registry (EC-205): when provided, the baked {@link CanvasPageOutcome}\n * `assets` are PAGE-SCOPED to exactly the asset ids this page's composition +\n * layout resolve through `resolveAsset` — not the whole project asset list,\n * which otherwise rides in every page's cache entry + RSC payload. Omitted →\n * all source assets (back-compat). Scoping mirrors the renderer's asset\n * resolution exactly, so no referenced image is ever dropped.\n */\n registry?: ComponentRegistry\n}\n\n/**\n * Page-scope the baked assets to those the page actually resolves (EC-205): the\n * static `asset`-typed props of its composition + selected layout tree. Document\n * asset FIELDS resolve into the document separately and do not use this list, so\n * they do not widen it.\n */\nfunction scopeAssetsToPage(\n allAssets: readonly ResolvedAsset[],\n registry: ComponentRegistry,\n result: ResolvePageResult,\n body: PageBody,\n resolveRelation: ResolveRelation,\n): readonly ResolvedAsset[] {\n if (allAssets.length === 0) return allAssets\n const ids = new Set<string>()\n // EC-270: pass `resolveRelation` so the collector follows a Reusable block's\n // `element` pick into the referenced document and page-scopes the asset ids\n // inside its transcluded `content` — otherwise a transcluded image is baked\n // nowhere and renders empty on the host (the relation bake already transcludes).\n const seen = new Set<string>()\n for (const node of body.nodes) {\n collectReferencedAssetIds(node, registry, ids, resolveRelation, seen)\n }\n // EC-499: a rich-text BODY (not just a component's rich-text prop) pins assets\n // too — an inline image carries `attrs.ref = { type:'asset', id }`. Its\n // component-node siblings are already in `body.nodes`; this collects the pins\n // that sit directly in the prose, so the host bakes them instead of the page\n // rendering an unresolved-ref placeholder where the image should be.\n for (const value of body.richTexts) {\n collectRichTextRefsInValue(value, { onAsset: (id) => ids.add(id) })\n }\n // EC-202: the OG image is an asset the page uses (for its `og:image` meta), so\n // it joins the page-scoped set — (EC-227) tagged for invalidation when that\n // asset record is edited. EC-266: a `type:'asset'` seoOgImage field is delivered\n // as a ResolvedAsset OBJECT ({ id, url }); a string is a raw id (or a passthrough\n // URL, which has no asset id to tag). Take the id from whichever shape.\n const ogImage = result.document?.['seoOgImage']\n const ogImageId =\n typeof ogImage === 'string'\n ? ogImage\n : ogImage && typeof ogImage === 'object' && typeof (ogImage as { id?: unknown }).id === 'string'\n ? (ogImage as { id: string }).id\n : null\n if (ogImageId) ids.add(ogImageId)\n return ids.size === 0 ? [] : allAssets.filter((asset) => ids.has(asset.id))\n}\n\n/**\n * One request's resolved delivery body (EC-499), split into what each baker\n * needs: the composition NODES to walk (EC-254 relation scan, EC-205 asset\n * scoping, EC-255 listings, EC-291 links) and the rich-text REGIONS whose own\n * refs must be baked alongside them.\n */\ninterface PageBody {\n /**\n * Every composition node the page renders: the body's own component nodes AND\n * the components EMBEDDED in its rich-text regions (EC-189/EC-434 — the\n * ambient embed renderer renders those on delivery, so their relations,\n * listings and assets have to be baked or they render empty).\n */\n nodes: ComponentNode[]\n /** The body's rich-text regions, for the pinned-asset / internal-link bake. */\n richTexts: unknown[]\n}\n\n/** Split a resolved document's declared body into the bakers' two views (EC-499). */\nfunction pageBodyOf(result: ResolvePageResult, presentation?: DeliveryPresentation): PageBody {\n const items = bodyItemsOf(result.document, presentation)\n const richTexts = documentBodyRichTextValues(items)\n return {\n nodes: [\n ...documentBodyCompositionNodes(items),\n ...richTexts.flatMap((value) => richTextEmbedsInValue(value)),\n ],\n richTexts,\n }\n}\n\n/**\n * The collection-declared delivery presentation for the document this URL\n * resolved to (EC-499), normalized for the render outcome — `undefined` when the\n * source exposes no collections, the collection is unknown, or it declares\n * nothing (the EC-458 shape picker then decides, unchanged).\n *\n * EC-524: body fields only. The detail wrapper this used to carry alongside them\n * is host code now, applied by `createCanvasRoute`'s `wrap` around the whole\n * renderer, so nothing chrome-shaped crosses the outcome.\n */\nfunction presentationFor(\n source: ContentSource,\n result: ResolvePageResult,\n): DeliveryPresentation | undefined {\n const collectionId = result.document?.collection\n if (!collectionId || !source.collections) return undefined\n const collection = source.collections.find((candidate) => candidate.id === collectionId)\n if (!collection?.presentation) return undefined\n const body = presentationBodyFields(collection)\n return body.length > 0 ? { body: [...body] } : undefined\n}\n\n/**\n * Populate every composition-node `relation` prop the page references to depth-1,\n * keyed `collection:id` (EC-254). Walks the page composition + layout (and, through\n * `collectReferencedRelations`, transcluded reusable content) and resolves each ref\n * through the request's client. Empty when no registry is supplied (the collector\n * needs manifests to know which props are relations) — relation props then pass\n * through raw, the graceful degradation a host without a registry already gets.\n *\n * `docKeys` carries the `collection:id` of every referenced pick (resolved or not),\n * so the caller cache-tags the page by them (EC-146): editing/publishing a picked\n * document — including a previously-unpublished one that should now appear — drops\n * exactly the pages that embed it. A fixed pick depends on the document's identity,\n * not on its collection's membership, so no coarse collection tag is added.\n */\nfunction populatePageRelations(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n): { relations: Record<string, RelationTarget>; docKeys: string[] } {\n if (!registry) return { relations: {}, docKeys: [] }\n const refKeys = new Set<string>()\n const relations = collectReferencedRelations(\n body.nodes,\n registry,\n (ref) => client.resolveRelation(ref),\n {},\n refKeys,\n )\n return { relations, docKeys: [...refKeys] }\n}\n\n/**\n * Resolve every `listing` block on the page to its matching documents (EC-255),\n * keyed by serialized query for `<CanvasRenderer />` to rebuild `resolveListing`.\n * Runs each listing's query through the request's client (so perspective + locale\n * match the rest of the resolution — published-only on a live page, per AD-5), and\n * returns the per-query `listDocuments` `CacheTags` so the caller folds them into\n * the page's tag set: those carry both the COLLECTION tag (membership — a product\n * joining/leaving the category re-renders the grid) and the listed documents' own\n * tags (a member's content edit re-renders it). Empty when no registry is supplied\n * (the collector needs manifests to know which blocks declare a listing).\n */\nfunction populatePageListings(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n): { listings: Record<string, RelationTarget[]>; tags: CacheTags[] } {\n if (!registry) return { listings: {}, tags: [] }\n const tags: CacheTags[] = []\n const listings = collectReferencedListings(\n body.nodes,\n registry,\n (query: ListingQuery) => {\n const listed = client.listDocuments(query.collection, {\n where: query.where,\n ...(query.sort ? { sort: query.sort } : {}),\n ...(query.limit !== undefined ? { limit: query.limit } : {}),\n })\n tags.push(listed.tags)\n return listed.ok ? (listed.documents as RelationTarget[]) : []\n },\n // EC-255: reach listings nested inside transcluded reusable content (the same\n // depth-1 populate the relation bake uses), so a grid inside a reusable bakes —\n // and its membership tag is folded in — instead of rendering empty on the host.\n (ref) => client.resolveRelation(ref),\n )\n return { listings, tags }\n}\n\n/**\n * Resolve every internal rich-text LINK on the page to its target document's URL\n * path (EC-291), keyed `collection:id` for `<CanvasRenderer />` to rebuild\n * `resolveLink`. Collects the referenced doc refs from the page's rich-text props\n * (transclusion-aware) and derives each URL through the request's client\n * (`resolveLink` → `urlForDocument`), so the path matches what the same routes serve.\n * A ref that resolves to no live URL (unpublished target / no addressing route) is\n * simply omitted — the renderer then shows the EC-290 `unresolved-ref` issue.\n *\n * `docKeys` carries EVERY referenced target (resolved or not), so the caller cache-\n * tags the page by them (EC-146): publishing or renaming a linked document — including\n * one not yet published when the page was baked — drops exactly the embedding pages.\n * Empty when no registry is supplied (the collector needs no manifest, but the bake\n * stays consistent with its relation/listing siblings, which do).\n */\nfunction populatePageLinks(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n): { links: Record<string, string>; docKeys: string[] } {\n if (!registry) return { links: {}, docKeys: [] }\n const refs = collectReferencedRichTextLinks(\n body.nodes,\n registry,\n (ref) => client.resolveRelation(ref),\n )\n // EC-499: a rich-text BODY carries link marks directly, not only inside a\n // component's rich-text prop — collect those too, so an internal link in a\n // post's prose resolves to a real href instead of the `unresolved-ref` fallback.\n for (const value of body.richTexts) {\n collectRichTextRefsInValue(value, {\n onLink: (ref) => {\n refs[`${ref.collection}:${ref.id}`] = ref\n },\n })\n }\n const links: Record<string, string> = {}\n for (const [key, ref] of Object.entries(refs)) {\n const url = client.resolveLink(ref)\n if (url !== null) links[key] = url\n }\n return { links, docKeys: Object.keys(refs) }\n}\n\n/**\n * Resolve one request URL against a content source: split the locale, run\n * `resolvePage` in the requested perspective, assemble binding payloads, and\n * merge every accessor's `CacheTags` into one deterministic set. Pure — all\n * data was prefetched by the source.\n */\nexport async function resolveCanvasPage(\n source: ContentSource,\n url: string,\n perspective: Perspective,\n options: ResolveCanvasPageOptions = {},\n): Promise<CanvasPageOutcome> {\n const { locale, path } = splitLocalePath(url, source.locales)\n const client = source.client({ perspective, locale })\n const result = client.resolvePage(path)\n\n if (result.status === 'redirect' && result.redirect) {\n return {\n kind: 'redirect',\n target: result.redirect.target,\n permanent: result.redirect.permanent,\n tags: result.tags,\n }\n }\n\n if (result.status !== 'ok') {\n return { kind: 'not-found', tags: result.tags }\n }\n\n // EC-499: read the collection's declared delivery presentation ONCE, and split\n // the resolved body by it — every baker below and the renderer then agree on\n // exactly which fields are the body and which of its items is which kind.\n const presentation = presentationFor(source, result)\n const body = pageBodyOf(result, presentation)\n\n const allAssets = source.assets?.() ?? []\n // EC-205: page-scope the baked assets when a registry is supplied (the route\n // helper passes it); else keep the full list (back-compat).\n const bakedAssets = options.registry\n ? scopeAssetsToPage(allAssets, options.registry, result, body, (ref) =>\n client.resolveRelation(ref),\n )\n : allAssets\n // EC-227: tag the cache entry with every asset it baked, so editing an asset\n // record (alt/dimensions/re-upload) drops exactly the pages carrying it. The\n // locale is irrelevant for asset tags (an asset record is locale-agnostic) —\n // `cacheTags` just needs one; `result.tags.locale` is the page's.\n const assetTags = cacheTags({\n assets: bakedAssets.map((asset) => asset.id),\n locale: result.tags.locale,\n })\n // EC-254: populate the relation props of the nodes inside the page's blocks\n // (e.g. a ProductGrid's picked products), keyed for `<CanvasRenderer />` to\n // rebuild `resolveRelation`. Reuses the request's client, so perspective +\n // locale match the rest of the resolution.\n const { relations, docKeys: relationDocKeys } = populatePageRelations(\n body,\n client,\n options.registry,\n )\n // EC-146: tag the page by every picked document so editing/publishing one drops\n // the embedding page (the locale is the page's; doc tags are locale-agnostic).\n const relationTags = cacheTags({ docs: relationDocKeys, locale: result.tags.locale })\n // EC-255: resolve every listing block's query (e.g. a ProductGrid keyed to a\n // category) and bake the result keyed by serialized query. The per-query\n // `listDocuments` tags (collection membership + listed docs) join the merged set,\n // so adding/removing a member or editing one invalidates the embedding page.\n const { listings, tags: listingTags } = populatePageListings(\n body,\n client,\n options.registry,\n )\n // EC-291: resolve every internal rich-text link to its target's URL path and bake\n // it for `<CanvasRenderer />` to rebuild `resolveLink`. Tag the page by each linked\n // document so renaming/re-parenting/first-publishing a target re-bakes the href.\n const { links, docKeys: linkDocKeys } = populatePageLinks(body, client, options.registry)\n const linkTags = cacheTags({ docs: linkDocKeys, locale: result.tags.locale })\n return {\n kind: 'render',\n result,\n assets: bakedAssets,\n relations,\n listings,\n links,\n ...(presentation ? { presentation } : {}),\n tags: mergeCacheTags(\n result.tags,\n ...listingTags,\n assetTags,\n relationTags,\n linkTags,\n ),\n }\n}\n","import type { CacheTags } from '@elytracms/core/content'\nimport type { RevalidationEvent } from '@elytracms/core/revalidation'\n\n/**\n * Cache-tag pipeline (EC-146, vision AD-5) — the pure half.\n *\n * This module defines (a) the **canonical Next.js tag string format** mapping\n * the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, (b) the\n * **tag derivation for a revalidation event** ({@link revalidationTags}, EC-579 —\n * the receiver's half of the one emitter), and (c) the **tag-discovery caching\n * logic** the catch-all route helper runs inside `unstable_cache`. No Next.js\n * imports — everything here is unit-testable; `route.tsx` injects the real\n * `unstable_cache` via {@link CacheWrapper}.\n *\n * This is the ONE tag module: the entries are tagged here and the invalidations\n * are derived here, from the same formatters and the same scope. Before EC-579 the\n * format was mirrored twice more (the studio's browser dispatcher and an inline\n * literal in the Convex emitter), and each emitter brought its own scope.\n *\n * ## Tag format\n *\n * - `doc:<collection>:<id>` — one per document whose content was actually in\n * the response (fetch-time tagging: includes depth-1 populated relation\n * targets, and documents inside filtered/dynamic lists *after* the filter\n * ran).\n * - `collection:<name>:<locale>` — the coarse membership tag: every response\n * that read from a collection carries it, so create/delete/newly-matching\n * documents sweep cached lists without read-set tracking.\n * - `route:<pattern>:<locale>` — the resolved URL path plus every hop of a\n * redirect chain.\n * - `asset:<id>` — one per asset whose delivery shape the page *baked*\n * (page-scoped images + `seoOgImage`, EC-227); editing that asset record\n * (alt/dimensions/re-upload) drops exactly the pages that baked it. Not\n * locale-scoped — an asset record is locale-agnostic.\n * - `project` — the whole-project sweep tag attached to **every** cached\n * entry; every revalidation event that changes live output invalidates it\n * (see {@link revalidationTags} for why, until EC-515).\n *\n * ## Project scope\n *\n * Every tag is prefixed `p:<projectId>:` when a scope is configured (e.g.\n * `p:prj_aurora:doc:post:post-1`). The cost is a few bytes per tag and it\n * keeps multi-project hosts (one Next app serving several builder projects)\n * from cross-invalidating, so the prefix is **on whenever the host knows its\n * project id** — decision: include it. Since EC-579 the scope has ONE owner: the\n * host. The webhook carries an event, not tags, and the receiver derives the\n * tags with the same scope its cache entries are written under (the delivery\n * kit hands both the one `contentScope`), so emitter and host can no longer\n * disagree about it.\n */\n\n/** Apply the optional multi-project scope prefix to one tag. */\nexport function scopeCacheTag(tag: string, scope?: string): string {\n return scope === undefined || scope.length === 0 ? tag : `p:${scope}:${tag}`\n}\n\n/**\n * Tag for one document identity. `docKey` is the EC-143 `documentKey` string\n * (`<collection>:<id>`), exactly as found in `CacheTags.docs`.\n */\nexport function docCacheTag(docKey: string, scope?: string): string {\n return scopeCacheTag(`doc:${docKey}`, scope)\n}\n\n/** The coarse membership tag of one collection in one locale. */\nexport function collectionCacheTag(collection: string, locale: string, scope?: string): string {\n return scopeCacheTag(`collection:${collection}:${locale}`, scope)\n}\n\n/** Tag for one resolved route path (or redirect-chain hop) in one locale. */\nexport function routeCacheTag(pattern: string, locale: string, scope?: string): string {\n return scopeCacheTag(`route:${pattern}:${locale}`, scope)\n}\n\n/**\n * Tag for one baked asset (EC-227). `assetId` is the bare asset id, as found in\n * `CacheTags.assets`. Not locale-scoped: an asset record is one row shared\n * across locales, so an edit invalidates the baking pages in every locale.\n */\nexport function assetCacheTag(assetId: string, scope?: string): string {\n return scopeCacheTag(`asset:${assetId}`, scope)\n}\n\n/**\n * The whole-project sweep tag. Attached to every cached page entry; derived for\n * every revalidation event that changes live output ({@link revalidationTags}).\n */\nexport function projectSweepTag(scope?: string): string {\n return scopeCacheTag('project', scope)\n}\n\n/**\n * Map an accessor-level `CacheTags` (what one request actually read) onto the\n * full, deterministic (sorted, deduplicated) set of Next tag strings for the\n * cache entry — including the project sweep tag.\n */\nexport function nextCacheTags(tags: CacheTags, scope?: string): string[] {\n const all = new Set<string>()\n all.add(projectSweepTag(scope))\n for (const docKey of tags.docs) all.add(docCacheTag(docKey, scope))\n for (const collection of tags.collections) {\n all.add(collectionCacheTag(collection, tags.locale, scope))\n }\n for (const route of tags.routes) all.add(routeCacheTag(route, tags.locale, scope))\n for (const assetId of tags.assets) all.add(assetCacheTag(assetId, scope))\n return [...all].sort()\n}\n\n// --- Revalidation event → tags (EC-579) ----------------------------------------\n\n/** The receiver's own project — what its cached entries are tagged with. */\nexport interface RevalidationProject {\n /** The tag scope of this host's cache entries (`p:<scope>:…`), if any. */\n scope?: string | undefined\n /** Every locale this host serves — one `collection:` tag per locale. */\n locales: readonly string[]\n}\n\n/**\n * The tags one revalidation event invalidates on this host — sorted, deduplicated,\n * scoped with the receiver's OWN scope, so the emitter never needs to know it.\n *\n * - `publish` / `unpublish` / `delete` → `doc:<collection>:<id>` (its detail page and\n * every cached list that contained it) + `collection:<collection>:<locale>` for\n * EVERY locale this host serves (list membership may have changed) + the sweep;\n * - `redirects` → the sweep (routing can change any URL; see the `redirects` trigger\n * in the studio's `convex/triggers.ts` for why a targeted `route:` tag is wrong);\n * - `asset` → `asset:<id>` + the sweep (a page reading the asset through a document\n * asset FIELD carries no `asset:` tag — EC-566 D16, EC-589);\n * - `probe` → nothing (the doctor's no-op ping).\n *\n * **Why every live-output event also carries the project sweep — a deliberate,\n * measured deviation from the EC-579 ruling's text, which lists the sweep only for\n * redirects and asset removes.** A self-hosted Next host honours ONLY the baseline\n * tags of a cache read (measured in EC-515 on Next 16.2.10 — see\n * {@link loadCachedEntry}): a discovered `doc:` / `collection:` / `asset:` tag\n * invalidates nothing there, silently, and Vercel is unmeasured. Until EC-515's\n * option D makes the precise tags honoured, the precise tags alone would turn\n * \"revalidates coarsely\" into \"never revalidates\" on every self-hosted host — so the\n * sweep stays on every event, exactly as the Convex path sent it before. Dropping it\n * from a publish is EC-515's job, together with the read that makes it safe.\n */\nexport function revalidationTags(event: RevalidationEvent, project: RevalidationProject): string[] {\n const { scope } = project\n const tags = new Set<string>()\n switch (event.kind) {\n case 'publish':\n case 'unpublish':\n case 'delete':\n tags.add(docCacheTag(`${event.collection}:${event.id}`, scope))\n for (const locale of project.locales) tags.add(collectionCacheTag(event.collection, locale, scope))\n tags.add(projectSweepTag(scope)) // until EC-515 — see above\n break\n case 'redirects':\n tags.add(projectSweepTag(scope))\n break\n case 'asset':\n tags.add(assetCacheTag(event.id, scope))\n tags.add(projectSweepTag(scope))\n break\n case 'probe':\n break\n }\n return [...tags].sort()\n}\n\n// --- The route entry key --------------------------------------------------------\n\n/** What identifies one cached route resolution — see {@link canvasCacheKey}. */\nexport interface CanvasCacheKeyInput {\n /** The tag scope (the project id the studio publishes under), if configured. */\n scope?: string | undefined\n /**\n * Which content world the loader reads — the delivery kit passes its\n * `contentMode` (`live` / `fallback` / `empty`). A host that drives the route\n * helper directly and has only one world may omit it.\n */\n mode?: string | undefined\n /** The request URL path (locale prefix included). */\n url: string\n}\n\n/**\n * The `unstable_cache` key parts for one route resolution: `elytra-route`,\n * the scope, the **content mode**, the URL.\n *\n * The mode is in the key because a data-cache entry outlives the process that\n * wrote it and Next never asks the loader whether it still applies (a hit never\n * runs the callback — that is the outage story). Before EC-543 the key was\n * `(scope, url)` only, and since `ELYTRA_PROJECT_ID` *is* the config slug, a\n * live host and an empty one keyed the same URL identically. Measured on\n * 0.0.85: a warm `.next/cache` written in live mode kept answering `200` with\n * the live world's bytes after both env vars were blanked and the host itself\n * reported `contentMode: 'empty'`; only deleting the cache produced the 404s\n * the docs promised. Keying on the mode makes that cross-read structurally\n * impossible rather than something to detect — a world change is a cold miss,\n * never a stale hit. Tags are untouched, so `revalidateTag` semantics are not\n * (EC-515 is a separate decision).\n */\nexport function canvasCacheKey({ scope, mode, url }: CanvasCacheKeyInput): string[] {\n return ['elytra-route', scope ?? '', mode ?? '', url]\n}\n\n// --- Tag-discovery caching over `unstable_cache` ------------------------------\n\n/**\n * The slice of `unstable_cache` the discovery logic needs, injectable so the\n * logic is testable against a faithful in-memory model (and so the test model\n * can mirror the real semantics this design depends on — see below).\n */\nexport interface CacheWrapperOptions {\n tags: string[]\n /** `false` = never expire by time (tag-driven invalidation only). */\n revalidate: number | false\n}\n\nexport type CacheWrapper = <T>(\n cb: () => Promise<T>,\n keyParts: string[],\n options: CacheWrapperOptions,\n) => () => Promise<T>\n\n/** What one cached request stores: the outcome plus its own discovered tags. */\nexport interface CachedEntry<T> {\n value: T\n /** The Next tag strings discovered at fetch time by the run that wrote this entry. */\n tags: string[]\n}\n\n/**\n * Internal sentinel: thrown by the probe callback after a cache **miss** ran\n * the loader, so `unstable_cache` never persists the entry under the\n * incomplete baseline tag set (a thrown callback caches nothing).\n */\nclass CacheDiscoveryBail extends Error {\n constructor() {\n super('elytra cache discovery bail (never user-visible)')\n this.name = 'CacheDiscoveryBail'\n }\n}\n\n/**\n * Both the probe and the write wrapper get their callback from this single\n * factory, so the two callbacks have **identical source text**. This matters:\n * `unstable_cache` derives its cache key from `cb.toString()` + `keyParts`,\n * so identical source is what makes the write pass store the entry under the\n * exact key future probe passes read.\n */\nfunction passthrough<T>(impl: () => Promise<T>): () => Promise<T> {\n return async () => impl()\n}\n\n/**\n * Fetch-time tag discovery over `unstable_cache` — the heart of EC-146.\n *\n * The problem: `unstable_cache` fixes an entry's tags when the *wrapper* is\n * created (`options.tags` is validated and **copied** at construction time in\n * Next 15 — mutating the array during the callback does not propagate), but\n * the correct tags are only known *after* the loader resolved the page. So a\n * single wrapper can never both (a) run the loader and (b) store the result\n * under the tags that run discovered.\n *\n * The design (\"resolve once, cache with discovered tags, serve cached\n * thereafter\") uses two wrapper constructions per request around **one**\n * cache entry:\n *\n * 1. **Probe pass** — a wrapper keyed by `keyParts` whose callback runs the\n * loader, captures the result + its discovered tags into the closure, and\n * then throws an internal sentinel.\n * - Cache **hit**: the stored entry (written by a previous request with\n * its full discovered tags) is returned; the callback — and therefore\n * the loader and any backend access — never runs. This is the outage\n * story: fully cached routes keep serving when the backend is down.\n * - Cache **miss**: the loader runs exactly once; the sentinel throw\n * prevents `unstable_cache` from persisting the entry under the\n * incomplete baseline tags (thrown callbacks cache nothing).\n * 2. **Write pass** (miss only) — a second wrapper is constructed *now that\n * the tags are known*, with `options.tags` = the discovered set, same key.\n * Its callback just returns the already-loaded entry, so the backend is\n * not hit again; `unstable_cache` persists the entry under the full\n * fetch-time tag set. `revalidateTag` on any of those tags drops the\n * entry and the next request repeats the cycle with fresh data.\n *\n * The loader runs **once per revalidation cycle** (per key), never once per\n * request. Time-based revalidation is intentionally not exposed, and the reason\n * is a real hazard rather than a preference: a stale-while-revalidate background\n * refresh re-runs the probe callback, whose bail aborts the refresh, so the\n * entry is never rewritten. `revalidate: false` keeps *time* from ever putting\n * an entry into that state — which is the AD-5 model.\n *\n * **What `revalidate: false` does NOT buy (EC-515, measured on Next 16.2.10).**\n * The earlier version of this comment claimed `revalidate: false` made the SWR\n * hazard unreachable. It does not: a **tag** can put an entry into the stale\n * state too, and then exactly the aborted-refresh loop above happens. Whether it\n * does is decided by how the invalidator stamps the tag —\n * `revalidateTag(tag, 'max')` marks entries stale (365-day expiry) and triggers\n * the loop; `revalidateTag(tag, { expire: 0 })` drops them outright and does\n * not. `revalidate.ts` therefore sends `{ expire: 0 }`, and the reasoning lives\n * at that call site. Keep the two in step: this design only survives an\n * invalidation that **drops** the entry, never one that merely staleness-marks\n * it.\n *\n * Concurrency: wrappers and the captured closure are per-request, so there is\n * no cross-request mutation; two racing first requests both resolve and the\n * last write wins (deterministic content ⇒ identical entries).\n *\n * ## ⚠️ Self-hosted hosts: only the BASELINE tags invalidate (EC-515)\n *\n * **Measured on a self-hosted `next start` (Next 16.2.10, default\n * `FileSystemCache`, `.next/cache` on disk); NOT measured on Vercel.**\n *\n * Fetch-time tag discovery writes the entry with the tags the response actually\n * read — but the default self-hosted cache handler decides staleness and expiry\n * from the tags the **reading** wrapper names (`ctx.tags`, see\n * `IncrementalCache.get` / `FileSystemCache.get` for `kind: FETCH`), not from\n * the tags the entry was **stored** with. The stored tag set is only ever used\n * to decide whether to re-write the entry with a superset.\n *\n * Every read in {@link loadCachedEntry} goes through the **probe** wrapper,\n * whose tags are `baselineTags`. So on such a host:\n *\n * - revalidating a tag that is in `baselineTags` works (the route helper passes\n * the project sweep tag, so `project` sweeps do invalidate);\n * - revalidating a *discovered* `doc:` / `collection:` / `route:` / `asset:` tag\n * invalidates **nothing** — silently. No error, no log line, no loader run.\n *\n * Measured 2×2 that isolates it (same host, same run): a probe handed the full\n * discovered tag set up front DOES honour a `doc:` tag, and the same probe\n * handed only the baseline does not. So the tags, their formatting and the\n * webhook are all correct — the read simply never consults them.\n *\n * Consequence for a self-hosted deployment: invalidation granularity collapses\n * to whatever is in `baselineTags`. {@link revalidationTags} therefore adds the\n * project sweep to every event that changes live output (EC-579), so instant\n * publish still *works* there, coarsely; its targeted `doc:`/`collection:`/`asset:`\n * tags do not reach a self-hosted host's entries at all. Vercel-hosted deployments\n * are unaffected in practice — both consumer\n * sites were measured healthy on 2026-08-01 — which is consistent with Vercel's\n * cache handler purging by the entry's stored tags, though that has **not** been\n * measured here.\n *\n * Fixing this properly means the read must be able to name the entry's own tags,\n * which no `unstable_cache` API allows without restructuring the probe/bail\n * seam. That is a deliberate open decision — see EC-515, do not \"fix\" it here\n * incidentally.\n */\nexport async function loadCachedEntry<T>(\n cache: CacheWrapper,\n keyParts: string[],\n baselineTags: readonly string[],\n load: () => Promise<CachedEntry<T>>,\n): Promise<CachedEntry<T>> {\n let fresh: CachedEntry<T> | undefined\n\n const probe = cache(\n passthrough(async (): Promise<CachedEntry<T>> => {\n fresh = await load()\n throw new CacheDiscoveryBail()\n }),\n keyParts,\n { tags: [...baselineTags], revalidate: false },\n )\n\n try {\n return await probe()\n } catch (error) {\n // Loader failures (backend down on a cold cache) propagate to the caller\n // — explicit, never silently cached. Only our own sentinel is absorbed.\n if (!(error instanceof CacheDiscoveryBail)) throw error\n }\n\n // Miss path: `fresh` was assigned before the sentinel was thrown.\n const entry = fresh as CachedEntry<T>\n const write = cache(\n passthrough(async () => entry),\n keyParts,\n { tags: entry.tags, revalidate: false },\n )\n return write()\n}\n","import type { Metadata, ResolvedMetadata } from 'next'\nimport type { ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\n\n/**\n * Resolved page → Next.js `Metadata` (EC-171, reshaped by EC-187). SEO now\n * lives on the resolved **document**: a `page`-collection document carries flat\n * `seo*` fields (`seoTitle`, `seoDescription`, `seoOgTitle`, `seoOgImage`,\n * `seoNoindex`); a content-collection document falls back to its `title`. There\n * is no separate page-graph `seo` block anymore.\n *\n * ```ts\n * // app/[[...slug]]/page.tsx — generateMetadata already applies this helper:\n * export const generateMetadata = route.generateMetadata\n * ```\n *\n * Mapping (in fallback order):\n * - `title`: `seoTitle` → document `name`/`title`.\n * - `description`: `seoDescription`.\n * - `openGraph`: OG title (`seoOgTitle` → effective title), description, the\n * OG image URL (`seoOgImage`) — and `locale` from the page's resolved locale\n * when, and only when, the declared code carries a territory (EC-531, narrowed by\n * EC-610). The site-wide half of Open Graph (`type`, `siteName`, the site image,\n * a bare-code site's `locale`) is the LAYOUT's: the canvas route merges this\n * object over the layout's resolved one (`mergeMetadataOverParent`), so a page\n * states only what is its own.\n * - `robots`: `seoNoindex` → `{ index: false }`.\n * - `alternates.canonical`: `seoCanonical` (EC-228) — an absolute or\n * root-relative URL, passed through verbatim. Real Sanity sites declare one per\n * page; omitted when unset so Next applies its own default.\n *\n * `hreflang` language alternates are intentionally NOT derived here: correct\n * hreflang needs a per-page translation map (which locale variants actually\n * exist), and auto-deriving from configured locales would emit alternates for\n * translations that don't exist — an SEO anti-pattern. Tracked as a follow-up.\n *\n * `seoOgImage` resolution (EC-202): the field holds either an absolute URL or\n * an **asset id**. An asset id is resolved to its delivery URL against the\n * page's baked `assets` (the same page-scoped set the renderer gets, widened to\n * include the OG asset in `route-core`). An asset id that can't be resolved is\n * **dropped** rather than emitted — an `<meta property=\"og:image\">` must carry a\n * real URL, never leak an internal id.\n *\n * Pure data-in/data-out — safe inside `generateMetadata` on both the cached\n * and uncached paths.\n */\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\n/** An already-resolvable image URL (absolute or root-relative), not an asset id. */\nfunction looksLikeUrl(value: string): boolean {\n return /^(https?:)?\\/\\//.test(value) || value.startsWith('/')\n}\n\n/**\n * Resolve a `seoOgImage` field value to a URL: a delivery-resolved asset OBJECT →\n * its `url` (EC-266); a URL string → passed through; a raw asset-id string →\n * resolved against the baked assets; anything unresolvable → dropped (EC-202).\n */\nfunction resolveOgImage(value: unknown, assets: readonly ResolvedAsset[]): string | null {\n // EC-266: a `seoOgImage` modelled as a `type:'asset'` FIELD (e.g. Konditorei,\n // migrated from Sanity) is resolved by delivery to a ResolvedAsset OBJECT\n // ({ id, url, … }), NOT a string — so read its `url`. A string value is the\n // legacy shape: a passthrough URL, or a raw asset id resolved against the baked\n // assets. A `str()`-only path turned the object into null, dropping og:image.\n if (value && typeof value === 'object') {\n const url = (value as { url?: unknown }).url\n return typeof url === 'string' && url.length > 0 ? url : null\n }\n if (typeof value !== 'string' || value.length === 0) return null\n if (looksLikeUrl(value)) return value\n return assets.find((asset) => asset.id === value)?.url ?? null\n}\n\n/**\n * EC-531, narrowed by EC-610 (owner ruling 3, 2026-10-23): `og:locale` ONLY in the\n * shape Open Graph specifies — `language_TERRITORY` with an underscore (`de_DE`),\n * where BCP 47 writes `de-DE`. The value is the page's RESOLVED locale\n * (`result.tags.locale`, the same fact the router used to pick the content). A\n * bare language code (`de`) is outside that format, so the kit emits nothing for\n * it and the layout's own value stands through the merge: inventing a territory\n * (`de` → `de_DE`) would be wrong exactly where it matters (Austria, the UK,\n * Brazil, Latin America), and a wrong valid value looks deliberate.\n */\nexport function openGraphLocale(locale: string): string | null {\n return locale.includes('-') ? locale.replace(/-/g, '_') : null\n}\n\nexport function canvasPageMetadata(\n result: ResolvePageResult,\n assets: readonly ResolvedAsset[] = [],\n): Metadata {\n const doc = result.document\n\n const seoTitle = str(doc?.['seoTitle'])\n const seoDescription = str(doc?.['seoDescription'])\n const seoOgTitle = str(doc?.['seoOgTitle'])\n const seoOgImage = resolveOgImage(doc?.['seoOgImage'], assets)\n const seoNoindex = doc?.['seoNoindex'] === true\n const seoCanonical = str(doc?.['seoCanonical'])\n\n // Fallback title: the page document's `name`, or a content document's `title`.\n const docTitle = str(doc?.['name']) ?? str(doc?.['title'])\n const title = seoTitle ?? docTitle\n\n const metadata: Metadata = {}\n if (title !== null) metadata.title = title\n if (seoDescription) metadata.description = seoDescription\n\n const ogTitle = seoOgTitle ?? title\n if (doc) {\n const ogLocale = openGraphLocale(result.tags.locale)\n metadata.openGraph = {\n ...(ogTitle !== null ? { title: ogTitle } : {}),\n ...(seoDescription ? { description: seoDescription } : {}),\n ...(seoOgImage ? { images: [{ url: seoOgImage }] } : {}),\n ...(ogLocale !== null ? { locale: ogLocale } : {}),\n }\n }\n\n if (seoNoindex) metadata.robots = { index: false }\n\n // EC-228: a page-declared canonical URL. Passed through verbatim (absolute or\n // root-relative); Next resolves it against `metadataBase` when relative.\n if (seoCanonical) metadata.alternates = { canonical: seoCanonical }\n\n return metadata\n}\n\n/**\n * EC-610 (owner ruling 1, 2026-10-13): **the site provides the default tags, and\n * the page's tags are merged on top** — a value the page states overrides the\n * site's, a value the page leaves empty falls back to the site's.\n *\n * Next merges metadata SHALLOWLY (read: `generate-metadata.md`, \"Merging\"): a\n * page segment's `openGraph` object REPLACES the layout's whole `openGraph`, so a\n * site-wide `type`, `siteName`, site image or `locale` declared once in the root\n * layout never reached a canvas page, and both real sites hand-rolled around it\n * differently (NK-039's page hook, Konditorei's post bypass). This is the one\n * merge, applied by the canvas route to whatever builds the page's metadata (the\n * kit's default or the host's `metadata` option): every object-valued key the\n * page sets (`openGraph`, `twitter`, `robots`, `alternates`, …) is merged one\n * level deep over the parent's RESOLVED value; a key the page does not set is left\n * to Next, which inherits it from the parent as before; a scalar or array the\n * page sets wins outright.\n *\n * The parent's resolved shapes spread back cleanly: Next hands `generateMetadata`\n * a `ResolvedMetadata` whose URLs are already strings, whose `images` are\n * descriptor objects and whose titles are `{ absolute, template }` — all of which\n * `Metadata` accepts (Next's own \"extend rather than replace\" example spreads\n * `(await parent).openGraph?.images`).\n */\nexport function mergeMetadataOverParent(\n parent: ResolvedMetadata | null | undefined,\n page: Metadata,\n): Metadata {\n if (!parent) return page\n const merged: Record<string, unknown> = { ...page }\n for (const [key, pageValue] of Object.entries(page)) {\n const parentValue = (parent as Record<string, unknown>)[key]\n if (isPlainObject(pageValue) && isPlainObject(parentValue)) {\n merged[key] = { ...parentValue, ...pageValue }\n }\n }\n return merged as Metadata\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","import type { Metadata, ResolvingMetadata } from 'next'\nimport { unstable_cache } from 'next/cache'\nimport { draftMode } from 'next/headers'\nimport { notFound, permanentRedirect, redirect } from 'next/navigation'\nimport type { ReactNode } from 'react'\nimport type { CacheTags, Perspective, ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\nimport { canvasCacheKey, loadCachedEntry, nextCacheTags, projectSweepTag } from './cache'\nimport type { CacheWrapper } from './cache'\nimport { CanvasRenderer } from './canvas-renderer'\nimport { canvasPageMetadata, mergeMetadataOverParent } from './metadata'\nimport type { HostComponents } from './components'\nimport { canvasWrapContext, pathFromSlug, resolveCanvasPage } from './route-core'\nimport type { CanvasPageOutcome, CanvasRequestInfo, CanvasWrapContext } from './route-core'\nimport type { ContentSource } from './source'\n\n/**\n * Catch-all route helper (EC-144): the factory a host app calls once in\n * `app/[[...slug]]/page.tsx` to wire `resolvePage` → `<CanvasRenderer />`,\n * with redirects answered by Next's `redirect()`/`permanentRedirect()` (per\n * the route's `permanent` flag), unknown URLs answered by `notFound()` (the\n * host's 404 renders), and the perspective chosen by Next `draftMode()` —\n * draft when enabled (via the token-gated preview route), published\n * otherwise.\n *\n * With `cache` configured (EC-146), the published-perspective resolve+render\n * data work runs inside `unstable_cache`, keyed by (scope, content mode, URL)\n * and tagged with the fetch-time tag set of what the response actually\n * contained (see `cache.ts`). Publishing in the studio then invalidates exactly\n * the affected entries via the signed `/api/revalidate` webhook — and while the\n * content backend is unreachable, cached entries keep serving because a cache\n * hit never invokes `loadContent`. That same property is why the content mode\n * is part of the key (EC-543): an entry outlives the process that wrote it, so\n * a host that has since lost its backend must miss, not hit, the live world.\n */\n\n// EC-524: both context shapes and the wrap-context derivation live in the PURE\n// half (`route-core.ts`) — no Next request APIs are involved in either, and the\n// derivation earns a unit test there. Re-exported here because this module is\n// the published API surface hosts import from.\nexport type { CanvasRequestInfo, CanvasWrapContext } from './route-core'\n\nexport interface CanvasCacheOptions {\n /**\n * Project scope prefix for every tag (`p:<scope>:…`) — must be the SAME scope\n * the revalidate receiver derives its tags with (`createRevalidateRoute`'s\n * `scope`; the delivery kit hands its one `contentScope` to both, EC-579).\n */\n scope?: string\n /**\n * Which content world `loadContent` reads — the delivery kit passes its\n * `contentMode` (`'live' | 'fallback' | 'empty'`), so entries written in one\n * world can never be served in another (EC-543, see `canvasCacheKey`). A host\n * driving this helper directly with a single world may omit it.\n */\n mode?: string\n}\n\n/**\n * What the route helper tells the content loader about the request it is\n * loading for (EC-156). Loaders that serve both perspectives from one\n * prefetched source (the fixture snapshot) can ignore it; a live loader uses\n * it to fetch the draft-perspective snapshot only for draft-mode requests —\n * it must NOT call request APIs like `draftMode()` itself, because on the\n * cached published path the loader runs inside `unstable_cache`, where\n * dynamic request APIs are unavailable.\n */\nexport interface CanvasContentRequest {\n perspective: Perspective\n}\n\nexport interface CanvasRouteOptions {\n /**\n * Async content loader — the seam EC-145/146 plug Convex into. For the\n * fixture-backed example this memoizes a `createContentSnapshot` call.\n * Invoked per request; cache/memoize inside the loader as appropriate.\n * With `cache` configured it only runs on a cache miss (once per\n * revalidation cycle) — cache hits never touch the content backend.\n * Receives the request's perspective (see {@link CanvasContentRequest});\n * zero-arg loaders remain valid.\n */\n loadContent: (request: CanvasContentRequest) => Promise<ContentSource>\n /** Host component surface from `defineHostComponents`. */\n components: HostComponents\n /**\n * EC-146 instant publish: cache the published-perspective resolution in\n * Next's data cache (`unstable_cache`), tagged with the response's own\n * fetch-time tags, invalidated by the `/api/revalidate` webhook. Draft mode\n * always bypasses this cache entirely. Tag-driven only — no time-based\n * revalidation (the AD-5 model). Cached values are JSON-serialized plain data.\n */\n cache?: CanvasCacheOptions\n /**\n * EC-146 hook: receives the merged `CacheTags` of everything this request\n * read (the `resolvePage` tags plus the baked relations/listings/links). On a\n * cache hit these are the stored tags of the run that wrote the entry.\n */\n onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void\n /** Override the generated `<head>` metadata for a resolved page. */\n metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata\n /**\n * Wrap the rendered page in the host's own chrome (EC-524) — a detail\n * template, a per-collection layout, a draft ribbon. Receives the rendered\n * canvas as `children` and returns whatever should stand in its place:\n *\n * ```tsx\n * elytra.canvasRoute({\n * wrap: (children, ctx) =>\n * ctx.collection === 'post' ? <PostDetail document={ctx.document}>{children}</PostDetail> : children,\n * })\n * ```\n *\n * **This is deliberately host code, not config** (owner ruling 2026-08-01):\n * *\"this sounds very much like page-templates … templates belong in code, so\n * the wrapper is something the developer has to care about … the page-content\n * (Canvas or Rich-Text) should render agnostically.\"* A collection's\n * `presentation.body` still names WHICH field is the content — that is content\n * selection, and it is what lets one catch-all render a canvas page and a\n * rich-text post through the same call. What surrounds that content is the\n * developer's implementation decision, branched here in their own file.\n *\n * It replaced a `presentation.wrapper` component-id STRING resolved from the\n * canvas registry, which forced a delivery-only component to register as a\n * pseudo-block (EC-514). A function beats that on every axis that matters: it\n * cannot dangle, the compiler checks it, and it can branch on anything in\n * {@link CanvasWrapContext} rather than on collection identity alone.\n *\n * Applied to the `render` outcome only — a redirect or a 404 never reaches it.\n * Returning `children` unchanged is the no-op, and omitting `wrap` entirely is\n * the default.\n */\n wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode\n}\n\nexport interface CanvasRouteProps {\n params: Promise<{ slug?: string[] }>\n}\n\nexport interface CanvasRoute {\n /** The page component: `export default route.Page`. */\n Page: (props: CanvasRouteProps) => Promise<ReactNode>\n /**\n * Next metadata hook: `export const generateMetadata = route.generateMetadata`.\n * Next passes the parent segment's resolving metadata as the second argument;\n * the route merges the page's tags over it (EC-610). A caller that invokes this\n * by hand (the scaffold's `app/page.tsx`) forwards its own `parent`.\n *\n * `parent` is REQUIRED, not optional (EC-614): Next's webpack build validates a\n * page's `generateMetadata` with `SecondArg<…> extends ResolvingMetadata`, and an\n * optional parameter reads as `ResolvingMetadata | undefined`, which fails that\n * constraint — a consumer re-exporting this hook could not `next build --webpack`\n * on 0.0.88 (the Turbopack validator is lenient, which is how the gate missed it).\n */\n generateMetadata: (props: CanvasRouteProps, parent: ResolvingMetadata) => Promise<Metadata>\n}\n\n/**\n * Default metadata (EC-171): the page's canonical SEO fields — title,\n * description, OG tags, robots noindex — with document-title / page-name\n * fallbacks. `export const generateMetadata = route.generateMetadata` is the\n * one line a host needs; a custom `metadata` option replaces this builder.\n * Either way the result is merged over the layout's resolved metadata (EC-610).\n */\nfunction defaultMetadata(result: ResolvePageResult, assets: readonly ResolvedAsset[]): Metadata {\n return canvasPageMetadata(result, assets)\n}\n\nasync function requestPerspective(): Promise<Perspective> {\n const { isEnabled } = await draftMode()\n return isEnabled ? 'draft' : 'published'\n}\n\n/** `unstable_cache` narrowed to the injectable seam the pure logic consumes. */\nconst nextCacheWrapper: CacheWrapper = (cb, keyParts, options) =>\n unstable_cache(cb, keyParts, { tags: options.tags, revalidate: options.revalidate })\n\n/** Create the `{ Page, generateMetadata }` pair for `app/[[...slug]]/page.tsx`. */\nexport function createCanvasRoute(options: CanvasRouteOptions): CanvasRoute {\n /**\n * Resolve one request, optionally through the tagged data cache. Shared by\n * `Page` and `generateMetadata` from a single call site, so both read (and\n * on a miss, write) the same cache entry.\n */\n const loadOutcome = async (url: string, perspective: Perspective): Promise<CanvasPageOutcome> => {\n const resolveFresh = async (): Promise<CanvasPageOutcome> => {\n const source = await options.loadContent({ perspective })\n return resolveCanvasPage(source, url, perspective, {\n // EC-205: page-scope the baked assets to what the page actually uses.\n registry: options.components.registry,\n })\n }\n\n // Draft mode BYPASSES the cache entirely: draft content is per-editor,\n // changes on every keystroke, and must never be persisted into (or served\n // from) the shared published-data cache.\n //\n // `next dev` ALSO bypasses it: the tag-based instant-publish webhook (the\n // only cache invalidation, AD-5) can't reach a localhost host, so a\n // published change would stay stale until the dev server restarts (which\n // clears `.next`). Scoped to `development` so tests (`NODE_ENV=test`) and\n // production (`next build`/`start`) keep the tag-driven cache unchanged.\n const devBypass = process.env.NODE_ENV === 'development'\n if (!options.cache || perspective !== 'published' || devBypass) return resolveFresh()\n\n const scope = options.cache.scope\n const entry = await loadCachedEntry(\n nextCacheWrapper,\n canvasCacheKey({ scope, mode: options.cache.mode, url }),\n [projectSweepTag(scope)],\n async () => {\n const outcome = await resolveFresh()\n return { value: outcome, tags: nextCacheTags(outcome.tags, scope) }\n },\n )\n return entry.value\n }\n\n const Page = async (props: CanvasRouteProps): Promise<ReactNode> => {\n const { slug } = await props.params\n const url = pathFromSlug(slug)\n const perspective = await requestPerspective()\n\n const outcome = await loadOutcome(url, perspective)\n options.onTags?.(outcome.tags, { url, perspective })\n\n switch (outcome.kind) {\n case 'redirect':\n // 301 vs 302 per the redirect record's `permanent` flag (EC-019/020).\n return outcome.permanent ? permanentRedirect(outcome.target) : redirect(outcome.target)\n case 'not-found':\n return notFound()\n case 'render': {\n const rendered = (\n <CanvasRenderer\n result={outcome.result}\n components={options.components}\n assets={outcome.assets}\n relations={outcome.relations}\n listings={outcome.listings}\n links={outcome.links}\n // EC-499: the resolved collection's declared body, so this ONE\n // catch-all renders a composition page and a rich-text post detail\n // alike — no per-collection Next file route needed.\n presentation={outcome.presentation}\n />\n )\n // EC-524: the host's own chrome goes AROUND the renderer, never inside\n // it. `children` is the (async) canvas element — React composes it in\n // place, so a wrapper may be a server or a client component.\n if (!options.wrap) return rendered\n return options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective }))\n }\n }\n }\n\n const pageMetadata = async (props: CanvasRouteProps): Promise<Metadata> => {\n const { slug } = await props.params\n const url = pathFromSlug(slug)\n const perspective = await requestPerspective()\n\n // Cached path: shares `Page`'s entry (same call site/key), so metadata is\n // a cache read, not a second resolution. Uncached path: resolve without\n // the payload factory — metadata only needs the resolution itself, and\n // side effects (addTags/onTags) must fire exactly once per request, in Page.\n if (options.cache && perspective === 'published') {\n const outcome = await loadOutcome(url, perspective)\n if (outcome.kind !== 'render') return {}\n return options.metadata\n ? options.metadata(outcome.result, { url, perspective })\n : defaultMetadata(outcome.result, outcome.assets)\n }\n\n const source = await options.loadContent({ perspective })\n const outcome = await resolveCanvasPage(source, url, perspective)\n if (outcome.kind !== 'render') return {}\n return options.metadata\n ? options.metadata(outcome.result, { url, perspective })\n : defaultMetadata(outcome.result, outcome.assets)\n }\n\n // EC-610: the page's tags over the site's. `parent` is the layout chain's\n // resolved metadata, which Next otherwise merges only one key deep — a\n // page-level `openGraph` would erase the layout's `type`, `siteName`, site\n // image and `locale` on every canvas page.\n const generateMetadata = async (\n props: CanvasRouteProps,\n parent: ResolvingMetadata,\n ): Promise<Metadata> => {\n const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent])\n return mergeMetadataOverParent(resolvedParent, page)\n }\n\n return { Page, generateMetadata }\n}\n","import type { MetadataRoute } from 'next'\nimport { composePath, hierarchyMount, splitPath } from '@elytracms/core/cms-core'\nimport type { CmsDocument, Locale, RouteRecord } from '@elytracms/core/cms-core'\nimport { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph'\nimport type { ContentClient } from '@elytracms/core/content'\nimport { localeUrlPath } from './locale-url'\nimport type { ContentSource } from './source'\n\n/**\n * `sitemap.xml` from published routes (EC-171): walk the same route records\n * `resolvePage` resolves against, resolve every candidate URL in the\n * **published** perspective, and emit `MetadataRoute.Sitemap` entries per\n * locale — Next's `app/sitemap.ts` convention. Pages marked `noindex` (and\n * URLs that do not resolve, e.g. a page that was never published) are\n * omitted.\n *\n * Dynamic routes (`/blog/:slug`): a route record does not declare which\n * collection feeds its params, so enumeration is delegated to the host's\n * {@link SitemapParamsProvider} — it gets a published-perspective accessor\n * client and returns one param record per concrete URL (typically from\n * `listDocuments`). **Honest limit:** parameterized routes without a provider\n * (or provider entries missing a param) are skipped and reported in\n * `skipped`, never guessed. `lastModified` is omitted — the delivery shape\n * carries no per-page timestamps today.\n *\n * Hierarchy mounts (`/:path*`, EC-218): a page-tree mount self-describes its\n * URLs, so it is enumerated automatically from the collection's published\n * documents (composing each page's nested path from its parent chain) — no\n * params provider entry needed.\n *\n * **Enumeration is a candidate list, not the output (EC-486).** Every branch\n * above — static, provider-fed, and mount — only proposes paths; each one is\n * then put back through `resolvePage`, and a path the resolver refuses is\n * dropped. So the sitemap cannot claim a URL the delivery host would 404, even\n * though the mount branch enumerates its collection independently of route\n * precedence. Do not read the enumeration in isolation: the gate below it is\n * what makes the two agree. What a mount enumerated and the resolver then\n * refused is reported in {@link CanvasSitemapResult.skipped}, never dropped in\n * silence.\n *\n * Host usage (`app/sitemap.ts`):\n *\n * ```ts\n * import { createCanvasSitemap } from '@elytracms/next'\n * import { loadContent } from '../lib/content'\n *\n * export default createCanvasSitemap({\n * loadContent,\n * baseUrl: 'https://example.com',\n * params: ({ route, client }) =>\n * route.id === 'r-post'\n * ? (client.listDocuments('post').ok ? … : []) // slug per published post\n * : null,\n * })\n * ```\n */\n\nexport interface SitemapRouteContext {\n route: RouteRecord\n locale: Locale\n /** Published-perspective accessor client bound to `locale`. */\n client: ContentClient\n}\n\n/**\n * Enumerate the param sets of one dynamic route — one record per concrete\n * URL (e.g. `[{ slug: 'hello-world' }, …]`). Return `null`/`undefined` to\n * skip the route (reported in `skipped`).\n */\nexport type SitemapParamsProvider = (\n ctx: SitemapRouteContext,\n) => readonly Record<string, string>[] | null | undefined\n\nexport interface CanvasSitemapOptions {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n baseUrl: string\n /** Route records to enumerate; defaults to the source's own route table. */\n routes?: readonly RouteRecord[]\n /** Param enumeration for dynamic routes (see {@link SitemapParamsProvider}). */\n params?: SitemapParamsProvider\n}\n\nexport interface SitemapSkippedRoute {\n routeId: string\n pattern: string\n reason: string\n}\n\nexport interface CanvasSitemapResult {\n entries: MetadataRoute.Sitemap\n /** Routes that could not be enumerated — explicit, never silent. */\n skipped: SitemapSkippedRoute[]\n}\n\nfunction paramNamesOf(pattern: string): string[] {\n return splitPath(pattern)\n .filter((segment) => segment.startsWith(':'))\n .map((segment) => segment.slice(1))\n}\n\n/** Substitute `:name` segments; `null` when a param is missing from the record. */\nfunction fillPattern(pattern: string, params: Record<string, string>): string | null {\n const segments: string[] = []\n for (const segment of splitPath(pattern)) {\n if (!segment.startsWith(':')) {\n segments.push(segment)\n continue\n }\n const value = params[segment.slice(1)]\n if (value === undefined || value.length === 0) return null\n segments.push(encodeURIComponent(value))\n }\n return '/' + segments.join('/')\n}\n\n/** The origin half; the locale ⇄ path half is `localeUrlPath` (EC-486). */\nfunction originOf(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, '')\n}\n\n/**\n * Enumerate the sitemap entries of one content source (published\n * perspective). Pure over the prefetched source — synchronous accessors,\n * deterministic ordering (sorted by URL).\n */\nexport function canvasSitemapEntries(\n source: ContentSource,\n options: CanvasSitemapOptions,\n): CanvasSitemapResult {\n const routes = options.routes ?? source.routes ?? []\n const locales = source.locales\n const skipped: SitemapSkippedRoute[] = []\n const skip = (route: RouteRecord, reason: string): void => {\n if (!skipped.some((s) => s.routeId === route.id && s.reason === reason)) {\n skipped.push({ routeId: route.id, pattern: route.pattern, reason })\n }\n }\n\n const clients = new Map<Locale, ContentClient>()\n const clientFor = (locale: Locale): ContentClient => {\n let client = clients.get(locale)\n if (!client) {\n client = source.client({ perspective: 'published', locale })\n clients.set(locale, client)\n }\n return client\n }\n\n // EC-517 — two maps, because the ENTRY set and the ALTERNATES set are keyed on\n // different facts and conflating them lost `hreflang` exactly where it mattered.\n //\n // `byUrl` one entry per addressable URL, deduped — what gets published.\n // `byGroup` the locales of ONE DOCUMENT, whatever paths they compose — what\n // each entry advertises as its alternates.\n //\n // Until EC-517 both jobs were done by a single map keyed on the composed path.\n // Two locales of one document only share a path while the localized slug is the\n // same string, so a template route (`/blog/hello` in every locale) alternated\n // correctly while a hierarchy mount with a genuinely translated slug composed\n // `/about` and `/ueber-uns`, formed two groups of one, and emitted nothing. The\n // failure was silent and inverted: the feature worked while the localization did\n // nothing and broke for the consumer who used it properly.\n const byUrl = new Map<string, { group: string; locale: Locale }>()\n const byGroup = new Map<string, Map<Locale, string>>()\n\n for (const route of routes) {\n const paramNames = paramNamesOf(route.pattern)\n // EC-218: a hierarchy mount (`/:path*`) serves a collection's pages by their\n // COMPOSED nested path — it self-describes its tree, so it is enumerated from\n // the collection's published documents, not the host's params provider.\n const mount = hierarchyMount(route)\n const routeLocales = route.locale ? [route.locale] : locales.locales\n\n for (const locale of routeLocales) {\n const client = clientFor(locale)\n\n let paths: string[]\n if (mount) {\n const listed = client.listDocuments(mount.collection)\n if (!listed.ok) {\n skip(route, `hierarchy collection \"${mount.collection}\" could not be listed`)\n continue\n }\n // Delivery docs are flat (`{ id, slug, parent, … }`); rebuild the minimal\n // CmsDocument shape composePath reads. Published perspective already\n // filtered to published rows, so a child whose parent is unpublished has a\n // dangling chain and drops out — exactly as live resolution would 404 it.\n const rows: CmsDocument[] = listed.documents.map((doc) => ({\n collection: mount.collection,\n id: doc.id,\n values: { slug: doc['slug'], parent: doc['parent'] },\n }))\n const byId = new Map(rows.map((row) => [row.id, row]))\n paths = []\n for (const row of rows) {\n // Only documents with their own slug are addressed through the mount\n // (a page routed explicitly elsewhere carries none).\n if (typeof row.values['slug'] !== 'string' || row.values['slug'] === '') continue\n const composed = composePath(row, (id) => byId.get(id))\n if (!composed.ok) continue\n paths.push('/' + [...mount.prefix, ...composed.segments].join('/'))\n }\n } else if (paramNames.length === 0) {\n paths = [route.pattern]\n } else {\n const paramSets = options.params?.({ route, locale, client })\n if (paramSets == null) {\n skip(route, 'dynamic route not enumerated (no params provider entry)')\n continue\n }\n paths = []\n for (const paramSet of paramSets) {\n const path = fillPattern(route.pattern, paramSet)\n if (path === null) {\n skip(route, `params entry missing one of :${paramNames.join(', :')}`)\n continue\n }\n paths.push(path)\n }\n }\n\n // EC-486: how many enumerated paths the RESOLVER refused. A mount that\n // composed paths but resolves none of them is a dead declaration (its\n // collection is not hierarchical, or it is shadowed) — the sitemap then\n // publishes nothing for it, and without this it would say nothing either.\n let unresolved = 0\n\n for (const path of paths) {\n // Same resolution the live request performs: only URLs that resolve\n // `ok` in the published perspective — and are not noindex — qualify.\n // Redirect-shadowed paths drop out (their targets are listed via\n // their own routes), and so do routes whose bound page/template is\n // not visible in the published graph (never published).\n const result = client.resolvePage(path, locale)\n // A document-bound route whose document is not published already\n // resolves `notFound` (EC-177 gap 6), so `status !== 'ok'` covers the\n // never-published case. A `page`-collection document carries its own\n // `seoNoindex` flat field (EC-187); honor it for the page sitemap.\n if (result.status !== 'ok') {\n // A `redirect` is a deliberate authoring act, not a dead route.\n if (result.status !== 'redirect') unresolved += 1\n continue\n }\n if (result.document?.['seoNoindex'] === true) continue\n // EC-486: the URL a crawler will actually request. `resolvePage` above\n // was asked for `path` in `locale`, but the host re-derives BOTH from\n // the URL — and for a path whose first segment is another locale's code\n // that derivation lands somewhere else, so no URL addresses this page.\n // Publishing one anyway would submit a guaranteed 404 to search engines.\n const { urlPath, addressable } = localeUrlPath(path, locale, locales)\n if (!addressable) {\n skip(\n route,\n `\"${path}\" (locale \"${locale}\") has no addressable URL — its first segment is read as a locale prefix`,\n )\n continue\n }\n const absolute = originOf(options.baseUrl) + urlPath\n // EC-517: group by the DOCUMENT this URL resolves to — the fact that is\n // actually locale-invariant. Scoped by route id so a document reachable\n // through two routes still yields two groups, each alternating within\n // itself, rather than one route's URL advertising another's. A route that\n // resolves no document (a template-bound pattern) keeps the old path key,\n // which is already locale-invariant for it.\n const group = result.document\n ? `${route.id}${COMPOSITE_KEY_SEPARATOR}doc${COMPOSITE_KEY_SEPARATOR}${result.document.collection}${COMPOSITE_KEY_SEPARATOR}${result.document.id}`\n : `${route.id}${COMPOSITE_KEY_SEPARATOR}path${COMPOSITE_KEY_SEPARATOR}${path}`\n if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale })\n const perLocale = byGroup.get(group) ?? new Map<Locale, string>()\n if (!perLocale.has(locale)) perLocale.set(locale, absolute)\n byGroup.set(group, perLocale)\n }\n\n if (mount && paths.length > 0 && unresolved === paths.length) {\n skip(\n route,\n `hierarchy mount over \"${mount.collection}\" composed ${paths.length} path(s) in locale \"${locale}\", none of which the resolver serves`,\n )\n }\n }\n }\n\n const entries: MetadataRoute.Sitemap = []\n for (const [url, { group }] of byUrl) {\n const perLocale = byGroup.get(group)\n // A single-locale group is a page that exists in one language; `hreflang`\n // between one URL and itself says nothing, so it stays off.\n const languages = perLocale && perLocale.size > 1 ? Object.fromEntries(perLocale) : undefined\n entries.push({ url, ...(languages ? { alternates: { languages } } : {}) })\n }\n entries.sort((a, b) => (a.url < b.url ? -1 : a.url > b.url ? 1 : 0))\n\n return { entries, skipped }\n}\n\nexport interface CreateCanvasSitemapOptions extends CanvasSitemapOptions {\n /** The same content loader the catch-all route uses (published perspective). */\n loadContent: (request: { perspective: 'published' }) => Promise<ContentSource>\n}\n\n/**\n * The `app/sitemap.ts` one-liner: `export default createCanvasSitemap({ … })`.\n * Loads the published content source and returns the enumerated entries\n * (skipped routes are dropped here — use {@link canvasSitemapEntries} to\n * inspect them).\n */\nexport function createCanvasSitemap(\n options: CreateCanvasSitemapOptions,\n): () => Promise<MetadataRoute.Sitemap> {\n return async () => {\n const source = await options.loadContent({ perspective: 'published' })\n return canvasSitemapEntries(source, options).entries\n }\n}\n","import { readRevalidationBody, verifyRevalidationSignature } from '@elytracms/core/revalidation'\nimport type { RevalidationEvent } from '@elytracms/core/revalidation'\n\n/**\n * Pure request logic of the revalidation webhook receiver (EC-146), kept free of\n * Next.js imports so it is unit-testable. `revalidate.ts` wires it into a route\n * handler with `revalidateTag`.\n *\n * The wire — event schema, signer, header — is NOT declared here: it is the one\n * module `@elytracms/core/revalidation`, shared with the emitter (the studio's\n * Convex write chokepoint) and the doctor probe (EC-579). This file only decides\n * the HTTP answer: auth before parsing, then the event.\n */\n\nexport type RevalidateEvaluation =\n | { ok: true; event: RevalidationEvent }\n | { ok: false; status: 400 | 401; message: string }\n\n/**\n * Evaluate one webhook request: verify the HMAC first (auth before parsing), then\n * read the event.\n *\n * - unconfigured (empty) secret → 401 for every request: the endpoint stays closed\n * until a secret is deliberately configured (mirrors EC-144 preview);\n * - missing/malformed/mismatched signature → 401 (constant-time verify);\n * - unparseable or schema-invalid body → 400, nothing revalidated; a body in the\n * pre-EC-579 tag-list shape gets its own named 400 (an older Elytra emitter —\n * deploy the matching studio functions), because a version skew between the\n * Convex deployment and this host is the one way a correctly signed body can\n * arrive in the wrong shape.\n */\nexport async function evaluateRevalidateRequest(\n rawBody: string,\n signature: string | null | undefined,\n secret: string | undefined,\n): Promise<RevalidateEvaluation> {\n if (secret === undefined || secret.length === 0) {\n return { ok: false, status: 401, message: 'Revalidation webhook is not configured.' }\n }\n if (typeof signature !== 'string' || signature.length === 0) {\n return { ok: false, status: 401, message: 'Missing or malformed webhook signature.' }\n }\n if (!(await verifyRevalidationSignature(rawBody, signature, secret))) {\n return { ok: false, status: 401, message: 'Invalid webhook signature.' }\n }\n const read = readRevalidationBody(rawBody)\n if (!read.ok) return { ok: false, status: 400, message: read.message }\n return { ok: true, event: read.event }\n}\n","import { revalidateTag } from 'next/cache'\nimport { REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation'\nimport { revalidationTags } from './cache'\nimport type { RevalidationProject } from './cache'\nimport { evaluateRevalidateRequest } from './revalidate-core'\n\n/**\n * Revalidation webhook route handler (EC-146): the receiving end of the\n * publish → live-in-seconds pipeline. The studio's Convex write chokepoint — the ONE\n * emitter (EC-579) — POSTs a signed EVENT (`{ kind, collection?, id? }`, the wire in\n * `@elytracms/core/revalidation`); this handler verifies the HMAC over the raw body,\n * derives the tags from the event with this host's OWN scope and locales\n * (`revalidationTags`, `cache.ts`) and calls `revalidateTag` for each, dropping the\n * cached entries whose fetch-time tag sets contained them.\n *\n * Server-to-server only: the emitter runs in Convex, so there is no CORS and no\n * `OPTIONS` preflight (the studio's browser dispatcher that needed them is deleted).\n * Server-only by construction — consumed from an App Router route handler file,\n * never from client bundles.\n */\n\nexport interface RevalidateRouteOptions extends RevalidationProject {\n /**\n * Shared HMAC secret. An empty/undefined secret rejects every request with\n * 401 — the endpoint stays closed until deliberately configured.\n */\n secret: string | undefined\n}\n\nexport interface RevalidateRouteHandlers {\n POST(request: Request): Promise<Response>\n}\n\n/**\n * Create the webhook route handler for `app/api/revalidate/route.ts`. Through the\n * delivery kit it is `export const { POST } = elytra.revalidateRoute()`, which hands\n * it the kit's own scope and locales — the ones its cached entries carry. Wiring it\n * by hand, pass the SAME scope your canvas route caches under:\n *\n * ```ts\n * export const { POST } = createRevalidateRoute({\n * secret: process.env.REVALIDATE_SECRET,\n * scope: process.env.ELYTRA_PROJECT_ID,\n * locales: ['en', 'de'],\n * })\n * ```\n *\n * Unsigned/invalid requests are rejected with 401 (constant-time HMAC verify, see\n * `evaluateRevalidateRequest`), signed-but-unreadable ones with 400; a valid event\n * revalidates its derived tags and answers `{ revalidated: tags }` (`[]` for the\n * doctor's `probe`).\n */\nexport function createRevalidateRoute(options: RevalidateRouteOptions): RevalidateRouteHandlers {\n const project: RevalidationProject = { scope: options.scope, locales: [...options.locales] }\n return {\n async POST(request: Request): Promise<Response> {\n const rawBody = await request.text()\n const signature = request.headers.get(REVALIDATE_SIGNATURE_HEADER)\n const evaluation = await evaluateRevalidateRequest(rawBody, signature, options.secret)\n if (!evaluation.ok) {\n return Response.json({ error: evaluation.message }, { status: evaluation.status })\n }\n const tags = revalidationTags(evaluation.event, project)\n // EC-515: `{ expire: 0 }`, NOT `'max'`. Next 16 requires a cache-life\n // profile as the second argument, and EC-219 reached for `'max'` on the\n // claim that it \"preserves the legacy drop-by-tag semantics\". **Measured\n // false on Next 16.2.10** — `'max'` is the *opposite* of a drop:\n //\n // `revalidateTag(tag, profile)` passes `{ expire: <profile>.expire }`\n // down to the cache handler. The `max` profile is `expire: 60*60*24*365`\n // (`server/config-shared.js`), so the default self-hosted handler stamps\n // `{ stale: now, expired: now + 365 days }` on the tag\n // (`FileSystemCache.revalidateTag`). `areTagsExpired` requires\n // `expired <= now` — a year in the future never qualifies — so the entry\n // is only marked STALE. `unstable_cache` then serves the stale value and\n // kicks a background refresh, which re-runs our probe callback, whose\n // `CacheDiscoveryBail` aborts the write (see `cache.ts`). The entry is\n // never rewritten, the stale value is served forever, and the loader is\n // hit on every request while Next logs `revalidating cache with key: …`.\n //\n // `{ expire: 0 }` stamps `expired: now`, which `areTagsExpired` treats as\n // an immediate hard drop — byte-for-byte the behaviour of the deprecated\n // one-argument form, and the behaviour the comment above always claimed,\n // without the deprecation warning. `CacheLifeConfig` is a public part of\n // the `revalidateTag` signature, so this is not a cast or a back door.\n // `updateTag` is still not an option — Server-Action-only, throws here.\n //\n // Measured (self-hosted `next start`, Next 16.2.10, cache on disk):\n // project-sweep webhook + `'max'` → stale forever; the same webhook with\n // `{ expire: 0 }` → fresh on the next request. See EC-515.\n for (const tag of tags) revalidateTag(tag, { expire: 0 })\n return Response.json({ revalidated: tags })\n },\n }\n}\n","import NextImage from 'next/image'\nimport type { ImageLoader } from 'next/image'\nimport type { ReactNode } from 'react'\nimport type { ResolvedAsset } from '@elytracms/core/content'\nimport { ImageManifest, normalizeImageSource } from '@elytracms/core/runtime-renderer'\nimport type { HostComponent } from './components'\n// TYPE-ONLY by design: importing `./host-image` at runtime would bundle a second\n// singleton instance into the server entry (see host-image.ts module docs).\nimport type { HostImageRenderer } from './host-image'\n\n/**\n * Image delivery for the embedded runtime (EC-152).\n *\n * ## The honest v1 design\n *\n * Stored asset bytes are served from plain blob-storage URLs (Convex file\n * storage serve URLs in production). Those URLs do **not** transform — there\n * is no `?w=`/`?q=` resizing service behind them, and no image CDN build-out\n * in v1. The standard, no-CDN path is therefore `next/image` with its\n * **default loader**: the host app's own Next.js optimizer fetches the remote\n * asset URL, resizes/re-encodes it, and serves the variants same-origin from\n * `/_next/image`. That is exactly what {@link ElytraImage} does.\n *\n * For that to work the host app must allowlist the asset origin in\n * `next.config.mjs`:\n *\n * ```js\n * const nextConfig = {\n * images: {\n * remotePatterns: [\n * // Convex file storage serve URLs:\n * { protocol: 'https', hostname: '*.convex.cloud' },\n * ],\n * },\n * }\n * ```\n *\n * {@link createAssetImageLoader} exists for the day the asset URLs sit behind\n * a CDN that *does* honor width/quality query params — it appends them and\n * nothing more. Pointing it at raw Convex serve URLs would silently serve\n * full-size bytes while pretending to resize, so it is **not** the default.\n */\n\n/**\n * The slice of the {@link ResolvedAsset} delivery shape an image needs:\n * the resolvable URL, intrinsic dimensions for layout stability, and alt\n * text. Structurally satisfied by every `ResolvedAsset`.\n */\nexport type ElytraImageAsset = Pick<\n ResolvedAsset,\n 'url' | 'width' | 'height' | 'alt' | 'focalPoint'\n> & {\n /**\n * LQIP blur-placeholder data URI (EC-420) → `next/image` `blurDataURL`. Optional\n * so existing call sites are unaffected; when present, the optimized image blurs\n * up while loading. `null`/absent = today's behaviour (no placeholder).\n */\n lqip?: string | null\n}\n\nexport interface ElytraImageProps {\n /** The resolved asset (delivery shape). `null` renders nothing. */\n asset: ElytraImageAsset | null | undefined\n /** `sizes` hint forwarded to `next/image` for responsive variants. */\n sizes?: string\n /** Preload hint forwarded to `next/image` (above-the-fold images). */\n priority?: boolean\n /** Quality (1–100) forwarded to `next/image`; Next defaults to 75. */\n quality?: number\n className?: string\n /**\n * Loading strategy forwarded to `next/image` (EC-428/NK-026). `'eager'` opts an\n * image out of lazy-loading without the preload `priority` implies. Next throws\n * on `priority` + `loading='lazy'` together, so {@link ElytraImage} drops `lazy`\n * when `priority` is set (priority wins) — delivery never crashes.\n */\n loading?: 'eager' | 'lazy'\n /**\n * Custom `next/image` loader (e.g. from {@link createAssetImageLoader})\n * when assets sit behind a transforming CDN. Omit for the v1 default:\n * Next's own optimizer.\n */\n loader?: ImageLoader\n}\n\n/** LQIP raster long edge (EC-420). MUST equal studio `LQIP_MAX_EDGE` (thumbnail.ts). */\nconst LQIP_MAX_EDGE = 24\n\n/**\n * Recompute the EC-420 LQIP raster's pixel dimensions from the asset's intrinsic\n * size — the SAME `fitLongEdge` math the generator uses\n * (`packages/studio/src/lib/assets/thumbnail.ts:64-67`), duplicated here (3 lines)\n * because `packages/next` must NOT import studio internals (EC-428 AD-3). Fits the\n * long edge to {@link LQIP_MAX_EDGE}, rounds, floors each edge to ≥1px, and NEVER\n * upscales — so a ≤24px source keeps its own dims (the case the aspect-ratio\n * approximation gets wrong). Deterministic from `width`/`height`, non-null on the\n * NextImage path (the only place the wrap runs).\n */\nfunction fitLqipLongEdge(width: number, height: number): { width: number; height: number } {\n const scale = Math.min(1, LQIP_MAX_EDGE / Math.max(width, height))\n return { width: Math.max(1, Math.round(width * scale)), height: Math.max(1, Math.round(height * scale)) }\n}\n\n/**\n * Pre-wrap a raster LQIP data URI in an SVG that reproduces Next's OWN\n * static-import blur strength (EC-428 AD-2, ported from the NK-029 reference).\n *\n * Next's `getImageBlurSvg` only scales its blur (`blurWidth * 40` viewBox) for\n * **static imports** that carry `blurWidth`/`blurHeight`; a runtime `blurDataURL`\n * falls back to `stdDeviation=20` over the image's INTRINSIC-size viewBox — on a\n * large hero that is effectively no blur, so the ≤24px LQIP shows raw pixel blocks.\n * There is no public API to pass `blurWidth`/`blurHeight` for runtime URLs, so we\n * reproduce the scaled path ourselves: `viewBox = lqipDims * 40`, Next's identical\n * alpha-safe filter chain (feGaussianBlur → alpha-clamp feColorMatrix → feFlood/\n * feComposite `out` → composite over SourceGraphic → feGaussianBlur), and an\n * `<image preserveAspectRatio='none'>` over the original LQIP.\n *\n * Base64-encoded (`data:image/svg+xml;base64,`), NEVER percent-encoding: Next nests\n * this whole URI as its outer SVG's `<image href>`, and the browser percent-decodes\n * the outer data URI exactly once — percent-encoded nested `%xx` would corrupt. The\n * SVG string is pure ASCII (the inner `href` is itself a base64 data URI), so `btoa`\n * (a global in every runtime this targets — browser, Node ≥16, edge) is safe.\n */\nexport function scaledLqipBlur(lqip: string, width: number, height: number): string {\n const { width: lqipW, height: lqipH } = fitLqipLongEdge(width, height)\n const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${lqipW * 40} ${lqipH * 40}'><filter id='b' color-interpolation-filters='sRGB'><feGaussianBlur stdDeviation='20'/><feColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/><feFlood x='0' y='0' width='100%' height='100%'/><feComposite operator='out' in='s'/><feComposite in2='SourceGraphic'/><feGaussianBlur stdDeviation='20'/></filter><image width='100%' height='100%' x='0' y='0' preserveAspectRatio='none' style='filter:url(#b)' href='${lqip}'/></svg>`\n return `data:image/svg+xml;base64,${btoa(svg)}`\n}\n\n/**\n * Render a resolved asset through `next/image` (EC-152).\n *\n * - With known intrinsic dimensions the optimizer serves resized variants\n * and the reserved width/height prevent layout shift.\n * - Without dimensions `next/image` cannot render (it requires `width` +\n * `height` or `fill`), so the component degrades to a plain `<img>` —\n * un-optimized but visible, never a crash. Records written by the EC-151\n * upload flow always carry detected dimensions.\n * - A `null`/empty asset renders nothing (missing assets surface as\n * structured `unknown-asset` validation issues upstream, not here).\n */\nexport function ElytraImage(props: ElytraImageProps): ReactNode {\n const { asset, sizes, priority, quality, className, loader, loading } = props\n if (!asset || asset.url.length === 0) return null\n const alt = asset.alt ?? ''\n // EC-230: a focal point becomes CSS `object-position`, so an art-directed image\n // keeps its subject in frame when a layout crops it (`object-fit: cover`).\n // Inert under the default fit — harmless to set, correct once a layout crops.\n const objectPosition = asset.focalPoint\n ? `${asset.focalPoint.x * 100}% ${asset.focalPoint.y * 100}%`\n : undefined\n if (asset.width === null || asset.height === null) {\n // Honest fallback: next/image requires intrinsic dimensions; a\n // dimensionless record degrades to an unoptimized <img> instead of\n // crashing the page. No placeholder here — a plain <img> can't consume a\n // blurDataURL — and the wrap needs non-null dims, so it stays below this guard.\n return (\n <img\n data-ec-image=\"fallback\"\n src={asset.url}\n alt={alt}\n className={className}\n {...(objectPosition ? { style: { objectPosition } } : {})}\n />\n )\n }\n // EC-420 (AD-2): a resolved LQIP data URI becomes a next/image blur-up\n // placeholder — the image blurs while the optimized bytes stream in.\n // EC-428 (AD-2/AD-3): but Next only scales its blur for static imports; a runtime\n // blurDataURL under-blurs a large image to raw pixel blocks — so pre-wrap the\n // raster LQIP in a viewBox-scaled SVG using the recomputed LQIP dims. AD-4: an\n // lqip that is ALREADY an SVG (a future engine that pre-blurs, or NK's interim\n // consumer wrapper) passes through untouched. Absent lqip = pre-EC-420 behaviour.\n const blurDataURL =\n asset.lqip == null\n ? null\n : asset.lqip.startsWith('data:image/svg')\n ? asset.lqip\n : scaledLqipBlur(asset.lqip, asset.width, asset.height)\n const blur = blurDataURL ? { placeholder: 'blur' as const, blurDataURL } : {}\n // EC-428 (AD-5): forward `loading`, but Next throws when `priority` +\n // `loading='lazy'` arrive together (get-img-props.js). Priority wins; drop the\n // conflicting `lazy` so a priority image never crashes delivery.\n const loadingProp = priority && loading === 'lazy' ? undefined : loading\n return (\n <NextImage\n data-ec-image=\"next\"\n src={asset.url}\n alt={alt}\n width={asset.width}\n height={asset.height}\n {...(sizes !== undefined ? { sizes } : {})}\n {...(priority !== undefined ? { priority } : {})}\n {...(loadingProp !== undefined ? { loading: loadingProp } : {})}\n {...(quality !== undefined ? { quality } : {})}\n {...(className !== undefined ? { className } : {})}\n {...(loader !== undefined ? { loader } : {})}\n {...blur}\n {...(objectPosition ? { style: { objectPosition } } : {})}\n />\n )\n}\n\nexport interface AssetImageLoaderOptions {\n /** Query param name for the requested width. Default `\"w\"`. */\n widthParam?: string\n /** Query param name for the requested quality. Default `\"q\"`. */\n qualityParam?: string\n /** Quality used when `next/image` passes none. Default `75`. */\n defaultQuality?: number\n}\n\n/**\n * A `next/image` loader for asset URLs served through a transforming CDN:\n * it appends width/quality query params to the asset URL and returns it.\n *\n * Be honest about what this does: it only *requests* a transform. Plain\n * Convex file-storage serve URLs ignore these params and return the original\n * bytes, so this loader is only correct once the asset origin actually\n * resizes (e.g. an image CDN in front of storage). Until then, use the\n * default `next/image` loader (see module docs) — that is the v1 path.\n */\nexport function createAssetImageLoader(options: AssetImageLoaderOptions = {}): ImageLoader {\n const widthParam = options.widthParam ?? 'w'\n const qualityParam = options.qualityParam ?? 'q'\n const defaultQuality = options.defaultQuality ?? 75\n return ({ src, width, quality }) => {\n const separator = src.includes('?') ? '&' : '?'\n return `${src}${separator}${widthParam}=${width}&${qualityParam}=${quality ?? defaultQuality}`\n }\n}\n\n/**\n * The `base.Image` implementation for Next.js host apps: same\n * canonical manifest, rendered optimized through {@link ElytraImage}. The\n * renderer resolves an `asset`-typed `src` (EC-195) to `{ url, width, height,\n * alt }` before this runs, so `normalizeImageSource` yields intrinsic dimensions\n * (→ no layout shift, sized variants) whether the editor picked an uploaded asset\n * or pasted a URL. `priority` opts an above-the-fold image out of lazy-loading.\n */\n/**\n * Per-deployment image-delivery options for {@link nextImagePrimitive} (EC-271).\n * The default is the host's own Next.js optimizer (Vercel) — zero config. A host\n * on a different CDN supplies its own `loader` (a plain function) to route variants\n * through that CDN instead, without touching component code.\n */\nexport interface NextImagePrimitiveOptions {\n /**\n * Custom `next/image` loader — the override seam for non-Vercel hosting. Omit\n * for the Vercel default (Next's own `/_next/image` optimizer). Supply a\n * function to delegate resizing to another CDN:\n *\n * - a CDN that honors `?w=&q=` query params → {@link createAssetImageLoader};\n * - Cloudflare Images / Imgix / a bespoke transform → your own `ImageLoader`\n * (e.g. rewrite to `/cdn-cgi/image/width=<w>,quality=<q>/<src>`).\n *\n * The loader receives `{ src, width, quality }` and returns the variant URL.\n */\n loader?: ImageLoader\n /** Default responsive `sizes` hint when a block doesn't set one. */\n sizes?: string\n /** Default quality (1–100) when a block doesn't set one (Next defaults to 75). */\n quality?: number\n}\n\nfunction createNextImagePrimitive(options: NextImagePrimitiveOptions) {\n return function NextImagePrimitive(props: Record<string, unknown>): ReactNode {\n const { url, width, height, alt, focalPoint, lqip } = normalizeImageSource(props)\n if (url.length === 0) return null\n return (\n <ElytraImage\n asset={{ url, width: width ?? null, height: height ?? null, alt, focalPoint, lqip }}\n {...(props.priority === true ? { priority: true } : {})}\n {...(options.loader !== undefined ? { loader: options.loader } : {})}\n {...(options.sizes !== undefined ? { sizes: options.sizes } : {})}\n {...(options.quality !== undefined ? { quality: options.quality } : {})}\n />\n )\n }\n}\n\n/**\n * Host registration that swaps the `base.Image` implementation\n * for {@link ElytraImage} while keeping the primitive's canonical manifest\n * (the first registration's manifest wins in `defineHostComponents` — by\n * design, so prop schemas stay canonical; the duplicate-id registry issue it\n * reports is the documented override signal, not an error):\n *\n * ```ts\n * export const hostComponents = defineHostComponents([\n * nextImagePrimitive(), // Vercel default optimizer\n * // ...project components\n * ])\n * ```\n *\n * Pass {@link NextImagePrimitiveOptions} to delegate image delivery to another\n * CDN (EC-271) — the v1 default is Vercel, with room to grow:\n *\n * ```ts\n * nextImagePrimitive({ loader: createAssetImageLoader() }) // ?w=&q= CDN\n * nextImagePrimitive({ loader: cloudflareImageLoader }) // Cloudflare Images\n * ```\n */\nexport function nextImagePrimitive(options: NextImagePrimitiveOptions = {}): HostComponent {\n return {\n manifest: ImageManifest,\n implementation: createNextImagePrimitive(options),\n }\n}\n\n/** Per-deployment options for {@link elytraHostImage} (the EC-271 loader seam). */\nexport interface ElytraHostImageOptions {\n /** Custom `next/image` loader for non-Vercel CDNs (see {@link NextImagePrimitiveOptions}). */\n loader?: ImageLoader\n /** Default quality (1–100) when a call site doesn't set one (Next defaults to 75). */\n quality?: number\n}\n\n/**\n * The `next/image`-backed {@link HostImageRenderer} (EC-368 image defaults): the\n * renderer a Next host registers on the `@elytracms/next/host-image` bridge so\n * DUAL-RENDERED project components (which can't import `next/image` themselves)\n * deliver optimized images — resized/modern-format variants, intrinsic\n * dimensions (no layout shift), focal-point `object-position`. A dimensionless\n * record degrades to a plain `<img>` (honest, never a crash), exactly like\n * {@link ElytraImage}.\n *\n * ```ts\n * import { elytraHostImage } from '@elytracms/next'\n * import { setHostImage } from '@elytracms/next/host-image'\n *\n * setHostImage(elytraHostImage()) // once, at host boot (never in the studio)\n * ```\n */\nexport function elytraHostImage(options: ElytraHostImageOptions = {}): HostImageRenderer {\n // EC-428 (AD-5/NK-026): forward `loading` — before this the renderer destructured\n // only asset/alt/className/priority/sizes, so a non-priority eager image silently\n // fell back to lazy. ElytraImage resolves the priority + loading='lazy' conflict.\n return ({ asset, alt, className, priority, sizes, loading }) => (\n <ElytraImage\n asset={{ ...asset, alt }}\n {...(className !== undefined ? { className } : {})}\n {...(priority ? { priority: true } : {})}\n {...(sizes !== undefined ? { sizes } : {})}\n {...(loading !== undefined ? { loading } : {})}\n {...(options.loader !== undefined ? { loader: options.loader } : {})}\n {...(options.quality !== undefined ? { quality: options.quality } : {})}\n />\n )\n}\n","import type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport { compositionNodesOf } from './canvas-renderer'\nimport type { DeliveryPresentation } from './canvas-renderer'\n\n/**\n * Manifest-driven header clearance (EC-368): does this page OPEN full-bleed?\n *\n * A host with a floating header (occupying no flow space) must pad a page's\n * content down by the header height — unless the page's FIRST canvas block is a\n * structural hero that deliberately bleeds to the viewport top and renders\n * under the header. Which components bleed is declared on their OWN manifest\n * (`presentation: { fullBleed: true }`, `@elytracms/core/component-registry`),\n * so the host derives the decision from the registry instead of keeping a\n * parallel hand-maintained component-id set that silently drifts when a new\n * hero ships.\n *\n * Pure over the delivery-shaped document: the composition field is found by\n * VALUE (the same `compositionNodesOf` selection the renderer uses), so hosts\n * with `body`, `content`, or bespoke composition field names all work. Absent\n * document, empty composition, unknown component, or an undeclared hint all\n * answer `false` — the safe default is clearance.\n *\n * EC-499: pass the collection's resolved `presentation` (from the render\n * outcome) so this reads the SAME body the renderer will render when the\n * collection declares one — otherwise the value-shape pick could name a\n * different field than the declaration does. A body whose first item is prose\n * rather than a block answers `false`, which is right: rich text opens no hero.\n *\n * ```tsx\n * const outcome = client.resolvePage(path)\n * const fullBleed = firstBlockFullBleed(outcome.document, components.registry)\n * return fullBleed ? page : <div className=\"pt-[var(--header-clearance)]\">{page}</div>\n * ```\n *\n * The host kit wires the resolution half for you: `elytra.pageOpensFullBleed(slug)`.\n */\nexport function firstBlockFullBleed(\n document: Record<string, unknown> | null | undefined,\n registry: ComponentRegistry,\n presentation?: DeliveryPresentation,\n): boolean {\n const first = compositionNodesOf(document, presentation)[0]\n if (!first) return false\n return registry.getManifest(first.componentId)?.presentation?.fullBleed === true\n}\n","/**\n * Env resolution for the delivery host kit (EC-368). ONE documented name per\n * concern — every kit surface reads the same resolved bag, so the two-\n * disconnected-secrets class of bug (a preview route gating on one env var\n * while the draft loader reads another) is structurally impossible: there is\n * exactly one `DRAFT_PREVIEW_TOKEN`, consumed by BOTH the preview-enable route\n * and the draft snapshot fetch.\n *\n * | Env var | Concern |\n * | --------------------- | ---------------------------------------------------- |\n * | `ELYTRA_CONVEX_URL` | Convex deployment URL — presence (with the project |\n * | | id) switches the loader to LIVE mode |\n * | `ELYTRA_PROJECT_ID` | The project the studio publishes under (= tag scope) |\n * | `DRAFT_PREVIEW_TOKEN` | The ONE draft secret: gates the preview-enable route |\n * | | AND authenticates the draft snapshot fetch (must |\n * | | equal the Convex deployment's own env var) |\n * | `REVALIDATE_SECRET` | Instant-publish webhook HMAC secret (must equal the |\n * | | deployment's `CANVAS_REVALIDATE_SECRET`) |\n *\n * EC-579 retired `STUDIO_ORIGIN` (the CORS origin of the studio's deleted browser\n * dispatcher): the one emitter is the Convex deployment, server to server.\n */\n\n/** The kit's documented env names (single source of truth, also used in docs/tests). */\nexport const ELYTRA_HOST_ENV_KEYS = {\n convexUrl: 'ELYTRA_CONVEX_URL',\n projectId: 'ELYTRA_PROJECT_ID',\n draftToken: 'DRAFT_PREVIEW_TOKEN',\n revalidateSecret: 'REVALIDATE_SECRET',\n} as const\n\nexport interface ElytraHostEnv {\n convexUrl?: string\n projectId?: string\n draftToken?: string\n revalidateSecret?: string\n}\n\nfunction cleaned(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * Resolve the kit's env bag from a raw env record (`process.env` by default at\n * the factory). Values are trimmed; empty strings resolve to `undefined` — an\n * env var set to `\"\"` must behave exactly like an unset one (endpoints stay\n * closed, live mode stays off), never like a configured empty secret.\n */\nexport function resolveHostEnv(env: Record<string, string | undefined>): ElytraHostEnv {\n const resolved: ElytraHostEnv = {}\n for (const [key, name] of Object.entries(ELYTRA_HOST_ENV_KEYS)) {\n const value = cleaned(env[name])\n if (value !== undefined) resolved[key as keyof ElytraHostEnv] = value\n }\n return resolved\n}\n","import { documentSchema, fallbackHomeRoutes, redirectRecordSchema } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n LocaleConfigInput,\n RedirectRecord,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { ContentDocumentSource, Perspective } from '@elytracms/core/content'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { createStaticContentSource, mergeRouteRecords } from './snapshot'\nimport type { ContentSource } from './source'\n\n/**\n * The LIVE half of the delivery host kit (EC-368): fetch the delivery snapshot\n * from a Convex deployment (`convex/delivery.ts` in the builder app) over\n * Convex's public HTTP query API — a plain `fetch` to\n * `POST {deployment}/api/query`, no Convex npm dependency — and assemble it\n * through the same `createStaticContentSource` path the fixture snapshot uses,\n * so accessor and rendering semantics are byte-identical across seeded, empty,\n * and live modes. This engine-izes the ~300-line `live-content.ts` both real\n * consumers hand-copied (EC-369 finding #5); the fetch + Zod-validation logic\n * now lives in exactly one place.\n *\n * Perspective handling: published requests fetch the PUBLIC `delivery:snapshot`\n * (published documents only — the standing live-is-published-only invariant).\n * Draft-mode requests fetch `delivery:draftSnapshot`, gated by the deployment's\n * `DRAFT_PREVIEW_TOKEN` — the same single secret that gates the kit's\n * preview-enable route (see `host-env.ts`).\n *\n * Routes: config-sourced route records (AD-3/AD-11, EC-207 — the host repo's\n * own `cms/`, passed via options) and the implicit per-locale home fallback are\n * MERGED (EC-177 gap 2), never XOR'd — config records win per pattern+locale.\n * The backend never carries route data.\n */\n\nexport interface LiveContentOptions {\n /** Convex deployment URL, e.g. `https://your-deployment-123.convex.cloud`. */\n convexUrl: string\n projectId: string\n /**\n * Project locale config — config-owned (AD-11), never read from the snapshot.\n * The declared shape; parsed where the source is assembled (EC-580).\n */\n locales: LocaleConfigInput\n /** CMS collection schema — config-owned (AD-11); drives depth-1 relation population. */\n collections: readonly CollectionDef[]\n /** Route records — config-owned (AD-3/AD-11); merged with the home fallback. */\n routes: readonly RouteRecord[]\n /** `DRAFT_PREVIEW_TOKEN` — required for draft-perspective loads. */\n draftToken?: string\n /** Fetch implementation override (tests, custom agents). Defaults to global fetch. */\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * The `delivery.ts` payload shape (validated field-by-field below). A\n * deployment IS one environment (EC-179); the snapshot carries content only\n * (EC-287) — documents, assets, editorial redirects (EC-303). (A `sources`\n * strand from an older deployment is ignored — it belonged to the deleted\n * data-binding subsystem, EC-574.)\n */\ninterface DeliverySnapshotPayload {\n projectId: string\n redirects?: unknown[]\n documents: unknown[]\n assets: unknown[]\n}\n\n/**\n * Run one query through Convex's public HTTP API. Response envelope (verified\n * against live deployments): `{ status: 'success', value }` or\n * `{ status: 'error', errorMessage }`. `cache: 'no-store'` keeps Next's fetch\n * cache out of the loop — the route helper's tagged data cache is the only\n * cache layer, so tag revalidation always reaches the deployment.\n */\nasync function runConvexQuery(\n fetchImpl: typeof globalThis.fetch,\n convexUrl: string,\n path: string,\n args: Record<string, unknown>,\n): Promise<unknown> {\n const response = await fetchImpl(`${convexUrl.replace(/\\/$/, '')}/api/query`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ path, args, format: 'json' }),\n cache: 'no-store',\n })\n const body = (await response.json().catch(() => undefined)) as\n | { status?: string; value?: unknown; errorMessage?: string; message?: string }\n | undefined\n if (!response.ok || body?.status !== 'success') {\n throw new Error(\n `Convex query \"${path}\" failed (${response.status}): ${\n body?.errorMessage ?? body?.message ?? 'no error message'\n }`,\n )\n }\n return body.value\n}\n\n/** Canonical-schema validation: invalid stored rows are skipped, not fatal. */\nfunction parseDocuments(rows: unknown[]): CmsDocument[] {\n const documents: CmsDocument[] = []\n for (const row of rows) {\n const parsed = documentSchema.safeParse(row)\n if (parsed.success) documents.push(parsed.data)\n }\n return documents\n}\n\n/** Validate the snapshot's editorial redirects (EC-303); malformed rows drop out. */\nfunction parseRedirects(rows: unknown[]): RedirectRecord[] {\n const redirects: RedirectRecord[] = []\n for (const row of rows) {\n const parsed = redirectRecordSchema.safeParse(row)\n if (parsed.success) redirects.push(parsed.data)\n }\n return redirects\n}\n\n// `fallbackHomeRoutes` moved to `@elytracms/core/cms-core` (EC-384) so the\n// studio's document-URL preview (`urlForDocument`) can derive the SAME implicit\n// `/` → first-page-document fallback the delivery resolver below applies — one\n// rule, not a studio-side reimplementation. Re-exported here (imported above)\n// to keep this module's/`@elytracms/next`'s existing public surface. The\n// delivery snapshot is already perspective-filtered server-side, so in draft\n// mode this correctly previews the draft home even when unpublished.\nexport { fallbackHomeRoutes }\n\n/**\n * Assemble a fetched delivery-snapshot payload into a `ContentSource` — the\n * pure half of {@link loadLiveContent}, exported so the parsing/assembly rules\n * are directly testable against fixture payloads (no network).\n */\nexport function assembleLiveSource(\n payload: DeliverySnapshotPayload,\n options: Pick<LiveContentOptions, 'locales' | 'collections' | 'routes'>,\n perspective: Perspective,\n): ContentSource {\n // Locales/collections/routes are config-owned (AD-11): from the host repo's\n // own `cms/`, passed in via options — never from the snapshot.\n const locales = options.locales\n const documents = parseDocuments(payload.documents)\n\n // MERGE, not XOR (EC-177 gap 2): config route records win per pattern+locale;\n // the implicit per-locale home fallback fills the rest.\n const routes = mergeRouteRecords([...options.routes], fallbackHomeRoutes(documents, locales))\n\n // EC-224 B-1: the published snapshot ships each document as its PINNED\n // published content (delivery resolved the version pointer server-side). Wrap\n // those rows as a `DocumentHistory` so the pure published gate\n // (`selectPerspective`) serves `.published` directly.\n //\n // TRUST CONTRACT: the wrap treats whatever the published fetch returned AS\n // published and deliberately does NOT re-gate — it cannot, the wire carries\n // no version pointer. Correctness rests ENTIRELY on `delivery:snapshot`\n // shipping published-only content (pinned version snapshots, never a draft —\n // see the builder's `convex/delivery.ts`). Guard the invariant in delivery,\n // not here. Draft-fetch rows stay bare: `selectPerspective('draft')` serves\n // them as-is.\n const documentSources: ContentDocumentSource[] =\n perspective === 'published'\n ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] }))\n : documents\n\n return createStaticContentSource({\n projectId: payload.projectId,\n locales,\n routes,\n // Redirects are editorial DATA (EC-303): the ENABLED ones ship in the\n // snapshot, so they change dynamically (no redeploy). Native next.config\n // redirects run before this.\n redirects: parseRedirects(payload.redirects ?? []),\n collections: [...options.collections],\n documents: documentSources,\n assets: payload.assets as AssetRecord[],\n })\n}\n\n/** Fetch the live delivery snapshot and assemble the `ContentSource` for one request. */\nexport async function loadLiveContent(\n options: LiveContentOptions,\n perspective: Perspective,\n): Promise<ContentSource> {\n const fetchImpl = options.fetch ?? globalThis.fetch\n const args: Record<string, unknown> = { projectId: options.projectId }\n\n let payload: DeliverySnapshotPayload\n if (perspective === 'draft') {\n if (!options.draftToken) {\n // Explicit, visible failure: draft mode without a configured token must\n // never silently fall back to published content.\n throw new Error(\n 'Draft preview requested but DRAFT_PREVIEW_TOKEN is not configured on the host.',\n )\n }\n payload = (await runConvexQuery(fetchImpl, options.convexUrl, 'delivery:draftSnapshot', {\n ...args,\n token: options.draftToken,\n })) as DeliverySnapshotPayload\n } else {\n payload = (await runConvexQuery(\n fetchImpl,\n options.convexUrl,\n 'delivery:snapshot',\n args,\n )) as DeliverySnapshotPayload\n }\n\n return assembleLiveSource(payload, options, perspective)\n}\n","import type { DocumentRef, Locale } from '@elytracms/core/cms-core'\nimport type { ContentClient, Perspective, ResolvedDocument } from '@elytracms/core/content'\nimport type { ContentSource } from './source'\n\n/**\n * Site-chrome globals for delivery hosts (EC-368, from EC-369 finding #5): the\n * layout, header, and footer are NOT rendered through the canvas, so they read\n * the `settings` singleton + `navigation` documents straight from the\n * `ContentSource` and pre-resolve everything — keeping the chrome components\n * pure presentational, with no `ContentClient` or `@elytracms/*` knowledge.\n * Both real consumers hand-authored this ~200-line layer independently; the\n * MECHANISM now lives here, while the per-project projection (which settings\n * fields exist, what the chrome looks like) stays consumer-owned:\n *\n * - **settings**: the singleton collection's lone document (EC-274) — no\n * document id is ever named.\n * - **navigation by slot**: menus are addressed by their EC-284 alias slot\n * (`main`, `footer`), never a document id — delivery shape carries `alias`\n * since EC-364.\n * - **link resolution**: link-object values (`linkType: href | page | file`)\n * resolve through `client.resolveLink` — the same `urlForDocument` authority\n * the router serves, so a nav link to a nested page carries its FULL composed\n * path (`/krippe/tagesablauf`, EC-218/EC-360), and `home → '/'` needs no\n * special-casing.\n * - **asset resolution**: asset field values (stored ids) resolve to serve URLs\n * through the source's delivery-shaped asset list.\n *\n * Everything degrades explicitly: a missing settings document is `null`, a\n * missing slot is `null`/`[]`, an unresolvable link drops out (never a silent\n * `#`), an unknown asset id is `undefined` — the host renders sensible empties\n * for the empty-source build and surfaces gaps honestly.\n */\n\nexport interface GlobalsConventions {\n /** The settings singleton's collection id. Default `'settings'`. */\n settingsCollection?: string\n /** The navigation documents' collection id. Default `'navigation'`. */\n navigationCollection?: string\n /**\n * Field names probed (in order) for a navigation document's entry list.\n * Default `['items', 'links']` — the two shapes the shipped consumers and the\n * starter template use.\n */\n navigationItemsFields?: readonly string[]\n}\n\n/** A fully-resolved link: the composed href plus the new-tab flag. */\nexport interface ResolvedLink {\n href: string\n openInNewTab: boolean\n}\n\n/** A leaf navigation link (children of a dropdown / footer column). */\nexport interface ResolvedNavLink extends ResolvedLink {\n label: string\n /** Optional subline shown under the label (wide header dropdowns). */\n description?: string\n}\n\n/** A top-level navigation entry; `href` is absent for pure group headings. */\nexport interface ResolvedNavEntry {\n label: string\n href?: string\n openInNewTab: boolean\n description?: string\n children: ResolvedNavLink[]\n}\n\n/** The resolved chrome-globals surface `loadGlobals` yields (EC-368). */\nexport interface SiteGlobals {\n /** The settings singleton, delivery-shaped, or `null` (empty build / not published). */\n settings: ResolvedDocument | null\n /** The navigation document filling a slot alias (EC-284/EC-364), or `null`. */\n navigation(slot: string): ResolvedDocument | null\n /**\n * A slot's entry list, fully resolved: labels, composed hrefs (nested paths\n * included), new-tab flags, sublines, one level of children. Entries whose\n * link cannot resolve to an href are kept only when they carry children (a\n * group heading); leaf entries without a resolvable href drop out.\n */\n navigationEntries(slot: string): ResolvedNavEntry[]\n /** Resolve a link-object field value to an href + new-tab flag, or `null`. */\n resolveLink(value: unknown): ResolvedLink | null\n /** Resolve an asset field value (stored id / URL / `{ url }`) to a URL, or `undefined`. */\n assetUrl(value: unknown): string | undefined\n /** The accessor client behind the globals — for bespoke projections. */\n client: ContentClient\n /** The underlying content source (assets(), locales, routes). */\n source: ContentSource\n}\n\nexport interface ResolveGlobalsOptions {\n /** Perspective of the chrome read. Default `'published'` (live is published-only). */\n perspective?: Perspective\n locale?: Locale\n conventions?: GlobalsConventions\n}\n\nconst DEFAULT_ITEMS_FIELDS = ['items', 'links'] as const\n\nfunction record(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\nfunction items(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? value.map(record) : []\n}\n\n/**\n * Resolve the chrome globals over an already-loaded `ContentSource` — the pure\n * core of the host kit's `loadGlobals` (directly testable over fixture\n * sources).\n */\nexport function resolveGlobals(\n source: ContentSource,\n options: ResolveGlobalsOptions = {},\n): SiteGlobals {\n const perspective = options.perspective ?? 'published'\n const conventions = options.conventions ?? {}\n const settingsCollection = conventions.settingsCollection ?? 'settings'\n const navigationCollection = conventions.navigationCollection ?? 'navigation'\n const itemsFields = conventions.navigationItemsFields ?? DEFAULT_ITEMS_FIELDS\n\n const client = source.client({ perspective, ...(options.locale ? { locale: options.locale } : {}) })\n const assets = source.assets?.() ?? []\n const assetById = new Map(assets.map((asset) => [asset.id, asset] as const))\n\n const listDocs = (collection: string): ResolvedDocument[] => {\n const result = client.listDocuments(collection)\n return result.ok ? result.documents : []\n }\n\n // EC-274: `settings` is a singleton — the lone document of its collection.\n const settings = listDocs(settingsCollection)[0] ?? null\n\n // EC-284/EC-364: menus are addressed by slot alias through the content client\n // (delivery shape carries `alias`), never by document id.\n const navDocs = listDocs(navigationCollection)\n const navigation = (slot: string): ResolvedDocument | null =>\n navDocs.find((doc) => (doc as Record<string, unknown>).alias === slot) ?? null\n\n /** Asset field value → serve URL. The stored value is the asset id (or already a URL). */\n const assetUrl = (value: unknown): string | undefined => {\n if (typeof value === 'string') {\n const found = assetById.get(value)\n if (found) return found.url\n // A passthrough URL (external image / public asset path) — not an id.\n return value.startsWith('http') || value.startsWith('/') ? value : undefined\n }\n const url = record(value).url\n return typeof url === 'string' && url.length > 0 ? url : undefined\n }\n\n /** A raw or populated relation value's `{ collection, id }` identity, if present. */\n const refOf = (value: unknown): DocumentRef | null => {\n const ref = record(value)\n return typeof ref.collection === 'string' && typeof ref.id === 'string'\n ? { collection: ref.collection, id: ref.id }\n : null\n }\n\n /** Resolve a link-object value (`linkType: href | page | file`) to an href. */\n const resolveLink = (value: unknown): ResolvedLink | null => {\n const link = record(value)\n const openInNewTab = link.openInNewTab === true\n const href = str(link.href) || str(link.url)\n const linkType = str(link.linkType) || (href ? 'href' : '')\n\n if (linkType === 'href' || linkType === 'external') {\n return href ? { href, openInNewTab } : null\n }\n if (linkType === 'file') {\n const url = assetUrl(link.file)\n return url ? { href: url, openInNewTab } : null\n }\n if (linkType === 'page') {\n const page = link.page\n // The reverse-router authority (EC-291/EC-360): the SAME `urlForDocument`\n // the routes serve — composed nested paths, `home → '/'`, locale prefix.\n const ref = refOf(page)\n if (ref) {\n const path = client.resolveLink(ref)\n if (path !== null) return { href: path, openInNewTab }\n }\n // Degraded fallbacks for partially-shaped values; else explicit null.\n const shaped = record(page)\n if (typeof shaped.path === 'string' && shaped.path.startsWith('/')) {\n return { href: shaped.path, openInNewTab }\n }\n const slug = str(shaped.slug)\n if (slug) return { href: `/${slug}`, openInNewTab }\n return null\n }\n return null\n }\n\n const toNavLink = (entry: Record<string, unknown>): ResolvedNavLink | null => {\n const label = str(entry.label)\n // Structured shape: the link object lives under `link`; flat starter shape:\n // the entry itself carries `href` (resolveLink's href fallback picks it up).\n const resolved = resolveLink('link' in entry ? entry.link : entry)\n if (!label || !resolved) return null\n const description = str(entry.description)\n return { label, ...resolved, ...(description ? { description } : {}) }\n }\n\n const toNavEntry = (entry: Record<string, unknown>): ResolvedNavEntry | null => {\n const label = str(entry.label)\n if (!label) return null\n const resolved = resolveLink('link' in entry ? entry.link : entry)\n const description = str(entry.description)\n const children = items(entry.children)\n .map(toNavLink)\n .filter((child): child is ResolvedNavLink => child !== null)\n // A leaf entry needs a resolvable target; an entry with children stands as a\n // group heading even without one (footer columns, pure dropdowns).\n if (!resolved && children.length === 0) return null\n return {\n label,\n ...(resolved ? { href: resolved.href } : {}),\n openInNewTab: resolved?.openInNewTab ?? false,\n ...(description ? { description } : {}),\n children,\n }\n }\n\n const navigationEntries = (slot: string): ResolvedNavEntry[] => {\n const doc = navigation(slot)\n if (!doc) return []\n const values = doc as Record<string, unknown>\n const field = itemsFields.find((name) => Array.isArray(values[name]))\n if (!field) return []\n return items(values[field])\n .map(toNavEntry)\n .filter((entry): entry is ResolvedNavEntry => entry !== null)\n }\n\n return {\n settings,\n navigation,\n navigationEntries,\n resolveLink,\n assetUrl,\n client,\n source,\n }\n}\n","/**\n * Pure token/target logic of the draft-preview routes (EC-144), kept free of\n * Next.js imports so it is unit-testable. `preview.ts` wires it into route\n * handlers with `draftMode()`.\n */\n\nexport type PreviewEvaluation =\n | { ok: true; redirectTo: string }\n | { ok: false; status: 400 | 401; message: string }\n\n/**\n * The fixed origin redirect targets are resolved against. `.invalid` is reserved\n * (RFC 6761), so it never names a real host; `https:` is a special scheme, so the\n * parser applies the same normalisation a browser applies when it resolves a\n * `Location` header against the site (`\\`→`/`, tab/LF/CR stripped, dot segments\n * removed).\n */\nconst RESOLUTION_ORIGIN = 'https://redirect-target.invalid'\n\n/** Resolve `value` the way a browser would, keeping it only if it stays on the origin. */\nfunction resolveOnOrigin(value: string): URL | null {\n let resolved: URL\n try {\n resolved = new URL(value, RESOLUTION_ORIGIN)\n } catch {\n return null\n }\n return resolved.origin === RESOLUTION_ORIGIN ? resolved : null\n}\n\n/** C0 controls (U+0000–U+001F) and DEL (U+007F): never part of a legitimate target. */\nfunction hasControlCharacter(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code <= 0x1f || code === 0x7f) return true\n }\n return false\n}\n\n/**\n * The ONE same-site check for a preview redirect target, shared by the enable and\n * disable routes, so the preview endpoints can never be used as an open redirect.\n * `null` means rejected; an empty value yields `fallback`.\n *\n * EC-594: the parser a browser uses decides, not a blocklist of shapes (the old\n * blocklist caught `//host` and backslashes — EC-304 B-3 — but not `/<TAB>/host`,\n * which the parser collapses to `//host`):\n *\n * - only a path-absolute reference (`/…`) is accepted — a relative one would be\n * resolved against the route's own path by a browser, so it has no fixed meaning;\n * - any C0 control or DEL is rejected outright;\n * - the value is resolved against a fixed origin and must stay on it;\n * - what is returned is the parser's normalised `pathname + search + hash`, never\n * the raw input — and that returned target must itself stay on the origin, since\n * dot-segment removal can leave a pathname starting with `//` (`/.//host`,\n * `/..//host`, `/%2e//host`), which as a `Location` is scheme-relative.\n *\n * A scheme-relative or absolute value naming the resolution origin itself resolves\n * on it and yields its path — harmless, because only that path is returned.\n */\nexport function safeRedirectTarget(\n value: string | null | undefined,\n fallback = '/',\n): string | null {\n if (value === null || value === undefined || value === '') return fallback\n if (!value.startsWith('/') || hasControlCharacter(value)) return null\n const resolved = resolveOnOrigin(value)\n if (resolved === null) return null\n const target = resolved.pathname + resolved.search + resolved.hash\n return resolveOnOrigin(target) === null ? null : target\n}\n\n/**\n * Evaluate a preview-enable request. Drafts are token-gated (EC-144\n * acceptance: without the token, drafts are never reachable):\n *\n * - an unconfigured (empty) expected token rejects every request — preview\n * cannot be accidentally left open;\n * - a missing or mismatched `token` query param is a 401;\n * - an off-site `redirect` target is a 400.\n */\nexport function evaluatePreviewRequest(\n requestUrl: string | URL,\n expectedToken: string | undefined,\n): PreviewEvaluation {\n if (expectedToken === undefined || expectedToken.length === 0) {\n return { ok: false, status: 401, message: 'Draft preview is not configured.' }\n }\n\n let url: URL\n try {\n url = typeof requestUrl === 'string' ? new URL(requestUrl, 'http://localhost') : requestUrl\n } catch {\n return { ok: false, status: 400, message: 'Malformed preview request URL.' }\n }\n\n const token = url.searchParams.get('token')\n if (token !== expectedToken) {\n return { ok: false, status: 401, message: 'Invalid preview token.' }\n }\n\n const redirectTo = safeRedirectTarget(url.searchParams.get('redirect'))\n if (redirectTo === null) {\n return { ok: false, status: 400, message: 'Preview redirect target must be a same-site path.' }\n }\n\n return { ok: true, redirectTo }\n}\n\n/**\n * The redirect target of a preview-disable request. Disabling needs no token (it\n * only ever reduces visibility), so anyone can reach the route — its `redirect`\n * param goes through the same {@link safeRedirectTarget} as the enable route. An\n * off-site or unreadable target falls back to the site root: leaving draft mode\n * always succeeds.\n */\nexport function previewDisableTarget(requestUrl: string | URL): string {\n let url: URL\n try {\n url = typeof requestUrl === 'string' ? new URL(requestUrl, 'http://localhost') : requestUrl\n } catch {\n return '/'\n }\n return safeRedirectTarget(url.searchParams.get('redirect')) ?? '/'\n}\n","import { draftMode } from 'next/headers'\nimport { redirect } from 'next/navigation'\nimport { evaluatePreviewRequest, previewDisableTarget } from './preview-core'\n\n/**\n * Draft preview route handlers (EC-144): Next.js `draftMode()` integration.\n * Server-only by construction — these factories are consumed from App Router\n * route handler files, never from client bundles.\n */\n\nexport interface PreviewRouteOptions {\n /**\n * The shared secret gating draft preview. An empty/undefined token means\n * every request is rejected with 401 — drafts stay unreachable until a\n * token is configured.\n */\n token: string | undefined\n}\n\n/** The shape an `app/api/.../route.ts` file re-exports. */\nexport interface PreviewRouteHandlers {\n GET(request: Request): Promise<Response>\n}\n\n/**\n * Create the preview-enable route handler for\n * `app/api/preview/route.ts`:\n *\n * ```ts\n * export const { GET } = createPreviewRoute({ token: process.env.PREVIEW_TOKEN })\n * ```\n *\n * `GET /api/preview?token=...&redirect=/some/path` checks the token, enables\n * Next draft mode (the catch-all helper then renders the `draft`\n * perspective), and redirects to the target path. Wrong/missing token → 401.\n */\nexport function createPreviewRoute(options: PreviewRouteOptions): PreviewRouteHandlers {\n return {\n async GET(request: Request): Promise<Response> {\n const evaluation = evaluatePreviewRequest(request.url, options.token)\n if (!evaluation.ok) {\n return new Response(evaluation.message, { status: evaluation.status })\n }\n const draft = await draftMode()\n draft.enable()\n redirect(evaluation.redirectTo)\n },\n }\n}\n\n/**\n * Create the preview-disable route handler (e.g.\n * `app/api/preview/disable/route.ts`). Disabling needs no token — it only\n * ever reduces visibility back to the published perspective. The `redirect`\n * target passes the same same-site check as the enable route; an off-site one\n * redirects to `/` instead.\n */\nexport function createPreviewDisableRoute(): PreviewRouteHandlers {\n return {\n async GET(request: Request): Promise<Response> {\n const draft = await draftMode()\n draft.disable()\n redirect(previewDisableTarget(request.url))\n },\n }\n}\n","import { draftMode } from 'next/headers'\nimport type { ReactNode } from 'react'\nimport type { CollectionDef, LocaleConfigInput, RouteRecord } from '@elytracms/core/cms-core'\nimport type { Perspective } from '@elytracms/core/content'\nimport type { Metadata } from 'next'\nimport { defineHostComponents } from './components'\nimport type { HostComponents } from './components'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\nimport { firstBlockFullBleed } from './full-bleed'\nimport { ELYTRA_HOST_ENV_KEYS, resolveHostEnv } from './host-env'\nimport type { ElytraHostEnv } from './host-env'\nimport { loadLiveContent } from './host-content'\nimport { resolveGlobals } from './host-globals'\nimport type { GlobalsConventions, SiteGlobals } from './host-globals'\nimport { nextImagePrimitive } from './image'\nimport type { NextImagePrimitiveOptions } from './image'\nimport { createPreviewDisableRoute, createPreviewRoute } from './preview'\nimport type { PreviewRouteHandlers } from './preview'\nimport { createRevalidateRoute } from './revalidate'\nimport type { RevalidateRouteHandlers, RevalidateRouteOptions } from './revalidate'\nimport { createCanvasRoute } from './route'\nimport type {\n CanvasContentRequest,\n CanvasRequestInfo,\n CanvasRoute,\n CanvasWrapContext,\n} from './route'\nimport { pathFromSlug, splitLocalePath } from './route-core'\nimport { createStaticContentSource } from './snapshot'\nimport type { ContentSource } from './source'\nimport type { CacheTags, ResolvePageResult } from '@elytracms/core/content'\n\n/**\n * The delivery host kit (EC-368): ONE factory over (env, project config) that\n * yields every engine-shaped delivery surface a Next.js host mounts — content\n * loading (live snapshot / build-safe empty / token-gated draft), the catch-all\n * canvas route, the instant-publish revalidate webhook, draft-mode plumbing,\n * chrome globals, and the full-bleed layout hint. A consumer wires env + ~5\n * lines per surface; no engine logic is ever copied into a host repo again\n * (EC-369 findings #4/#5/#6/#11).\n *\n * ```ts\n * // frontend/lib/elytra.ts — the ONE seam file\n * import { createElytraHost } from '@elytracms/next'\n * // Read the project out of the repo's own config — `elytra.config.ts` is the\n * // single contract surface, and importing it costs a delivery bundle nothing\n * // (EC-532). Any other module layout works just as well; this is an example,\n * // not a required path.\n * import { site } from '../../elytra.config'\n *\n * export const elytra = createElytraHost({ ...site })\n * ```\n *\n * ```ts\n * // app/[[...slug]]/page.tsx\n * const route = elytra.canvasRoute()\n * export default route.Page\n * export const generateMetadata = route.generateMetadata\n *\n * // app/api/revalidate/route.ts\n * export const { POST } = elytra.revalidateRoute()\n *\n * // app/api/draft/route.ts — GET /api/draft?token=…&redirect=/path\n * export const { GET } = elytra.previewRoute()\n *\n * // app/api/draft/disable/route.ts\n * export const { GET } = elytra.previewDisableRoute()\n * ```\n *\n * Trust boundaries are unchanged: live surfaces read the PUBLISHED snapshot;\n * draft is reachable only through explicit Next draft mode behind the ONE\n * `DRAFT_PREVIEW_TOKEN` (which also authenticates the draft snapshot fetch —\n * see `host-env.ts`); the revalidate webhook stays HMAC-signed. The consumer\n * keeps ownership of project config (AD-11), host components, form endpoints,\n * and anything genuinely bespoke.\n */\n\nexport interface ElytraHostOptions {\n /**\n * The project slug (config-as-code identity, mirrors `elytra.config.ts`).\n * Used as the content scope when no live backend is configured.\n */\n slug: string\n /**\n * Locale config — config-owned (AD-11), never read from the snapshot.\n *\n * **Import it, do not restate it** (EC-519). This is the *same*\n * {@link LocaleConfigInput} `elytra.config.ts` takes, so a project declares its\n * locales once and hands that one binding to both mounts. It stays a host\n * option because the engine cannot know where a repo keeps the value — but it\n * is no longer an option you have to *retype*: since EC-532 the delivery\n * bundle can import `elytra.config.ts` itself for zero engine modules\n * (measured), so `createElytraHost({ ...site })` over a project exported from\n * the config is the shortest correct answer.\n *\n * This used to be `{ default, locales }` against a config spelling the same\n * fact `locales` + `defaultLocale`, and in our own demo the two drifted — the\n * host said `['en']`, the config said `['en','de']`, and every `/de` URL 404'd\n * silently for weeks (EC-512).\n *\n * The DECLARED shape (EC-580): the kit parses it into the engine's `LocaleConfig`\n * where every content source is assembled (`createStaticContentSource` — the\n * empty source is built at boot), so a `default` outside `locales` stops the host\n * with a located message instead of serving — the same rule the studio's\n * workspace gate applies to the same value.\n */\n locales: LocaleConfigInput\n /** CMS collection schema — config-owned (AD-11). */\n collections: readonly CollectionDef[]\n /** Routes as code (AD-3/AD-11): records, or the `(collections) => records` derive. */\n routes: readonly RouteRecord[] | ((collections: CollectionDef[]) => RouteRecord[])\n /**\n * The repo's page-builder components (manifest + implementation pairs — the\n * SAME modules the studio composes with). Base primitives are always\n * included; `base.Image` is swapped for `next/image` delivery by default\n * (see `image`).\n *\n * Typed as the CONFIG's `HostComponentEntry` (`implementation: unknown`), not\n * the stricter {@link HostComponent}, so that `createElytraHost({ ...site })`\n * — the shape EC-532 makes the contract — compiles however the consumer typed\n * their project entry. It previously demanded `ComponentType`, which meant the\n * recommended spread worked only when the entry's implementation types\n * survived inference: `apps/demo/elytra.config.ts` gets away with it via\n * `satisfies`, and anyone using the engine's own published `HostComponentEntry`\n * / `WorkspaceProjectEntry` annotation did not. Both production consumers hit\n * exactly that and each carried one hand-restated `components` line (EC-539).\n *\n * What the widening costs, and where it is paid (EC-541): a list passed HERE\n * gets no compile-time check that each `implementation` is really a\n * component. The check is RUNTIME instead — `defineHostComponents` takes the\n * same `HostComponentEntry` shape (no cast, one signature) and tests every\n * implementation on registration; a value that is not a component becomes an\n * `invalid-implementation` issue in `components.issues` and is withheld from\n * the implementation map, so its nodes render the visible missing-component\n * fallback rather than crashing the server render. Authoring a component\n * module as the strict {@link HostComponent} keeps the compile-time check at\n * the place the implementation is written.\n */\n components?: readonly HostComponentEntry[]\n /**\n * `base.Image` delivery options (loader/sizes/quality — the non-Vercel CDN\n * seam), or `false` to keep the plain-`<img>` primitive. Default: Next's own\n * optimizer, registered out of the box.\n */\n image?: NextImagePrimitiveOptions | false\n /**\n * The public base URL of the delivery site (mirrors the config's\n * `frontendUrl`) — exposed on the handle for sitemap/canonical use.\n */\n frontendUrl?: string\n /**\n * Env bag; defaults to `process.env`. Documented names (see `host-env.ts`):\n * `ELYTRA_CONVEX_URL`, `ELYTRA_PROJECT_ID`, `DRAFT_PREVIEW_TOKEN`,\n * `REVALIDATE_SECRET`.\n */\n env?: Record<string, string | undefined>\n /**\n * The no-backend content fallback, used whenever `ELYTRA_CONVEX_URL` +\n * `ELYTRA_PROJECT_ID` are not both set. Default: a build-safe EMPTY source —\n * consumer CI builds green with no backend, and every URL is a clean 404\n * until content exists — in every cache state, not only a cold one, because\n * the content mode is part of the route's data-cache key (see\n * {@link ElytraContentMode}). The starter template overrides this with its\n * seeded in-memory world.\n */\n fallbackContent?: (request: CanvasContentRequest) => Promise<ContentSource>\n /** Chrome-globals conventions (settings/navigation collection ids, items fields). */\n globals?: GlobalsConventions\n /** Fetch implementation override for the live snapshot loader (tests, proxies). */\n fetch?: typeof globalThis.fetch\n}\n\n/** Pass-through canvas-route options the kit does not own. */\nexport interface HostCanvasRouteOptions {\n /** Tag observation hook (see `CanvasRouteOptions.onTags`). */\n onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void\n /** Metadata override (see `CanvasRouteOptions.metadata`). */\n metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata\n /**\n * Host-owned chrome around the rendered page (EC-524) — the seam a per-\n * collection detail template lives on. Branch on `ctx.collection` in your own\n * route file (see `CanvasRouteOptions.wrap`).\n */\n wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode\n /**\n * Tagged data cache (instant publish). Always on and always scoped to the kit's\n * {@link ElytraHost.contentScope}, with the kit's\n * {@link ElytraHost.contentMode} in the entry key (EC-543). `false` disables\n * caching entirely.\n *\n * There is no scope override (EC-579): the kit's revalidate receiver derives its\n * tags with that same `contentScope`, so one value scopes both the entries and\n * their invalidation. An override here was the one way left for the two to\n * disagree — and a disagreement drops nothing, silently.\n */\n cache?: false\n}\n\nexport interface LoadGlobalsOptions {\n /** Perspective of the chrome read. Default `'published'` (live is published-only). */\n perspective?: Perspective\n}\n\n/**\n * Which world {@link ElytraHost.loadContent} reads from.\n *\n * - `live` — the configured Convex deployment (published; token-gated draft).\n * - `fallback` — the host's own `fallbackContent` world (the scaffold's seeded\n * starter, the demo's in-memory fixture). A legitimate, supported mode.\n * - `empty` — no backend and no fallback: this project's structure with no\n * content, so every URL is a clean 404. Correct for a CI build; almost never\n * what a deployed site intends.\n *\n * The mode was decided from env long before EC-527 and readable by **nobody**,\n * so a site serving the fallback looked identical from the outside to one\n * reading the real deployment. Measured on the demo: `/` returned `200` while\n * all four of EC-512's published fixtures 404'd — an agent following AGENTS.md\n * §5 gets no signal that the thing under test was never exercised. Declared\n * once, read here, so a host can *say* which world it is in instead of\n * re-deriving it from `env` and drifting.\n *\n * **The mode is also part of the route's data-cache key** (EC-543). A cache\n * entry outlives the process that wrote it and a hit never runs the loader, so\n * until 0.0.85 the \"clean 404\" above held only on a COLD cache: measured, a\n * `next start` with both env vars blanked and a warm `.next/cache` from a live\n * run answered `200` with the live world's bytes on every cached URL while\n * reporting `empty` — the wrong-world failure EC-527 removed, one layer down.\n * Keyed on the mode (`canvasCacheKey`, `cache.ts`), a world change is a cold\n * miss per URL, never a stale hit, in either direction.\n */\nexport type ElytraContentMode = 'live' | 'fallback' | 'empty'\n\n/** The delivery host handle {@link createElytraHost} yields. */\nexport interface ElytraHost {\n /**\n * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode, else the config slug.\n * The ONE scope of this host (EC-579): its cache entries are written under it and\n * its revalidate receiver derives every invalidation tag with it.\n */\n contentScope: string\n /**\n * Which world {@link loadContent} reads from — see {@link ElytraContentMode}.\n * Read it to announce the mode (a boot line, a dev-only banner) rather than\n * re-deriving it from {@link env}, which is the drift this field exists to\n * prevent.\n */\n contentMode: ElytraContentMode\n /** The resolved env bag (documented names, trimmed, empty = unset). */\n env: ElytraHostEnv\n /** Materialized route records (a derive is run once, at factory time). */\n routes: RouteRecord[]\n /** The registered component surface (base primitives + image swap + project components). */\n components: HostComponents\n /** The public base URL of the delivery site, when configured. */\n frontendUrl?: string\n /**\n * The content loader (the seam every kit surface reads through): live\n * delivery snapshot when env is configured — published, or the token-gated\n * draft for draft-mode requests — else the fallback source.\n */\n loadContent(request: CanvasContentRequest): Promise<ContentSource>\n /**\n * Load + resolve the site-chrome globals (settings singleton, navigation by\n * slot alias, link/asset resolution — see `host-globals.ts`). One content\n * load per call; published perspective by default.\n */\n loadGlobals(options?: LoadGlobalsOptions): Promise<SiteGlobals>\n /** The catch-all canvas route pair for `app/[[...slug]]/page.tsx`. */\n canvasRoute(options?: HostCanvasRouteOptions): CanvasRoute\n /**\n * The instant-publish webhook handler for `app/api/revalidate/route.ts`, wired to\n * `REVALIDATE_SECRET` and to this host's own project — {@link contentScope} and the\n * config locales — which it derives the tags of every event with (EC-579). Unset\n * secret = endpoint closed (401), never a default secret. Only the secret can be\n * overridden: the scope and locales are the ones this kit's cache entries carry.\n */\n revalidateRoute(overrides?: Partial<Pick<RevalidateRouteOptions, 'secret'>>): RevalidateRouteHandlers\n /**\n * The draft-mode ENABLE route for e.g. `app/api/draft/route.ts` —\n * `GET /api/draft?token=…&redirect=/path`, gated by the ONE\n * `DRAFT_PREVIEW_TOKEN` (the same secret the draft snapshot fetch sends, so\n * the two can never disconnect). Unset token = drafts unreachable.\n */\n previewRoute(): PreviewRouteHandlers\n /** The draft-mode DISABLE route (no token — it only reduces visibility). */\n previewDisableRoute(): PreviewRouteHandlers\n /**\n * Does the page for this catch-all `slug` OPEN full-bleed? Manifest-driven\n * (`presentation.fullBleed`, EC-368) and draft-mode aware — the host lays out\n * header clearance from data instead of a hand-kept component-id set.\n */\n pageOpensFullBleed(slug: readonly string[] | undefined): Promise<boolean>\n}\n\n/** Materialize a config `routes` value (EC-207): run a derive or copy the array. */\nfunction materializeRoutes(\n routes: ElytraHostOptions['routes'],\n collections: readonly CollectionDef[],\n): RouteRecord[] {\n return typeof routes === 'function' ? routes([...collections]) : [...routes]\n}\n\n/** Create the delivery host handle — see the module docs for the full wiring. */\nexport function createElytraHost(options: ElytraHostOptions): ElytraHost {\n const env = resolveHostEnv(options.env ?? process.env)\n const routes = materializeRoutes(options.routes, options.collections)\n const live = env.convexUrl !== undefined && env.projectId !== undefined\n\n // EC-527: half-configured is never deliberate. Someone set one of the pair\n // meaning to connect, and the site would otherwise serve the fallback world\n // while looking completely healthy — the exact silent-wrong-world failure this\n // ticket exists to remove, except in production. An explicit gap beats a\n // connected-shaped no-op; `revalidateRoute` already makes that same call about\n // its secret. Both unset stays silent: no-backend is a supported mode.\n if (!live && (env.convexUrl !== undefined || env.projectId !== undefined)) {\n const [present, missing] =\n env.convexUrl !== undefined\n ? [ELYTRA_HOST_ENV_KEYS.convexUrl, ELYTRA_HOST_ENV_KEYS.projectId]\n : [ELYTRA_HOST_ENV_KEYS.projectId, ELYTRA_HOST_ENV_KEYS.convexUrl]\n throw new Error(\n `Elytra delivery host: ${present} is set but ${missing} is not — live content needs both. ` +\n `Left as it is, this site would serve fallback content and look healthy. ` +\n `Set ${missing} to go live, or unset ${present} to run the no-backend fallback on purpose.`,\n )\n }\n\n const contentMode: ElytraContentMode = live\n ? 'live'\n : options.fallbackContent\n ? 'fallback'\n : 'empty'\n const contentScope = live ? (env.projectId as string) : options.slug\n\n const components = defineHostComponents([\n // Batteries included (EC-368 image defaults): graph `base.Image` nodes render\n // through next/image — resized variants via the host's own optimizer. The\n // canonical manifest stays (first registration wins in defineHostComponents).\n ...(options.image === false ? [] : [nextImagePrimitive(options.image ?? {})]),\n // EC-539: the option is the CONFIG's entry shape (`implementation: unknown`)\n // so the recommended `{ ...site }` spread compiles; EC-541: registration\n // takes that shape too and checks each implementation at runtime (see the\n // option's doc comment) — no narrowing cast here.\n ...(options.components ?? []),\n ])\n\n // The build-safe default fallback: an EMPTY source over the config's own\n // structure. Constructed once — it is fully static.\n const emptySource: ContentSource = createStaticContentSource({\n projectId: contentScope,\n locales: options.locales,\n routes,\n // Redirects are editorial DATA (EC-303) — the no-backend build has none.\n redirects: [],\n collections: [...options.collections],\n documents: [],\n assets: [],\n })\n\n const loadContent = (request: CanvasContentRequest): Promise<ContentSource> => {\n if (live) {\n return loadLiveContent(\n {\n convexUrl: env.convexUrl as string,\n projectId: env.projectId as string,\n locales: options.locales,\n collections: options.collections,\n routes,\n ...(env.draftToken ? { draftToken: env.draftToken } : {}),\n ...(options.fetch ? { fetch: options.fetch } : {}),\n },\n request.perspective,\n )\n }\n if (options.fallbackContent) return options.fallbackContent(request)\n return Promise.resolve(emptySource)\n }\n\n const loadGlobals = async (globalsOptions: LoadGlobalsOptions = {}): Promise<SiteGlobals> => {\n const perspective = globalsOptions.perspective ?? 'published'\n const source = await loadContent({ perspective })\n return resolveGlobals(source, {\n perspective,\n ...(options.globals ? { conventions: options.globals } : {}),\n })\n }\n\n const canvasRoute = (routeOptions: HostCanvasRouteOptions = {}): CanvasRoute => {\n const { cache, ...rest } = routeOptions\n return createCanvasRoute({\n loadContent,\n components,\n // Tag scope = `contentScope`, the SAME value `revalidateRoute` derives its\n // invalidation tags with (EC-579) — one scope, so they cannot disagree.\n // EC-543: the mode is in the entry key so no world ever reads another's entries.\n ...(cache === false ? {} : { cache: { mode: contentMode, scope: contentScope } }),\n ...rest,\n })\n }\n\n const revalidateRoute = (\n overrides: Partial<Pick<RevalidateRouteOptions, 'secret'>> = {},\n ): RevalidateRouteHandlers =>\n createRevalidateRoute({\n // An unset secret keeps the endpoint CLOSED (401) — the kit never ships a\n // default secret (an auth-shaped no-op is worse than an explicit gap).\n secret: 'secret' in overrides ? overrides.secret : env.revalidateSecret,\n // EC-579: the receiver's OWN project — the scope its cache entries are written\n // under (`canvasRoute` above) and every locale it serves. The emitter sends only\n // the event, so this is the one place the scope and locales are read for it.\n scope: contentScope,\n locales: options.locales.locales,\n })\n\n // ONE secret by construction: the preview gate and the draft snapshot fetch\n // both read env.draftToken — they cannot disconnect (EC-369 finding #4).\n const previewRoute = (): PreviewRouteHandlers => createPreviewRoute({ token: env.draftToken })\n const previewDisableRoute = (): PreviewRouteHandlers => createPreviewDisableRoute()\n\n const pageOpensFullBleed = async (slug: readonly string[] | undefined): Promise<boolean> => {\n const { isEnabled } = await draftMode()\n const perspective: Perspective = isEnabled ? 'draft' : 'published'\n const source = await loadContent({ perspective })\n const { locale, path } = splitLocalePath(pathFromSlug(slug), source.locales)\n const client = source.client({ perspective, locale })\n const resolved = client.resolvePage(path)\n return firstBlockFullBleed(resolved.document, components.registry)\n }\n\n return {\n contentScope,\n contentMode,\n env,\n routes,\n components,\n ...(options.frontendUrl !== undefined ? { frontendUrl: options.frontendUrl } : {}),\n loadContent,\n loadGlobals,\n canvasRoute,\n revalidateRoute,\n previewRoute,\n previewDisableRoute,\n pageOpensFullBleed,\n }\n}\n","/**\n * @elytracms/next — the embedded runtime (EC-144, vision AD-1): render\n * builder-managed pages inside the user's own Next.js App Router repo with\n * the user's own components. Drop `createCanvasRoute` into a catch-all\n * route, register components with `defineHostComponents`, done.\n *\n * Nothing here imports builder-only code (no studio, no operations, no\n * TanStack) — only the runtime packages: content accessors, the component\n * registry, and the runtime renderer.\n */\nexport const PACKAGE = '@elytracms/next'\n\n// Component registration (host components + base primitives).\nexport { defineHostComponents } from './components'\nexport type { DefineHostComponentsOptions, HostComponent, HostComponents } from './components'\n\n// The server component. EC-499: `bodyItemsOf` is the body selection it renders\n// (declared `presentation.body`, else the EC-458 value-shape pick) — exported so\n// a host that renders a document body itself splits it the same way delivery does.\nexport { CanvasRenderer, bodyItemsOf } from './canvas-renderer'\nexport type { CanvasRendererProps, DeliveryPresentation } from './canvas-renderer'\n\n// EC-574: the binding payload surface (`createBindingResolver`, `BindingPayloads`,\n// `SourcePayloadError`, `defaultBindingPayloads`, the route `payloads` option) and\n// the EC-166 source materialization (`materializeSourcePayloads`,\n// `collectBindingSourceIds`, `CanvasDataSource`) went with the data-binding\n// subsystem. Props are static values; dynamic data lives in the component (AD-12).\n\n// Content source seam + cache-tag merging (EC-145/146 plug in here).\nexport { mergeCacheTags } from './source'\nexport type { ContentSource } from './source'\n\n// PersistenceAdapter → ContentLookup snapshot bridge, plus the plain-data\n// assembler for hosts that prefetched the content themselves (EC-156).\nexport {\n createContentSnapshot,\n createStaticContentSource,\n mergeRouteRecords,\n} from './snapshot'\nexport type { ContentSnapshot, ContentSnapshotOptions, StaticContentData } from './snapshot'\n\n// Catch-all route helper (pure core + Next wiring).\nexport { pathFromSlug, resolveCanvasPage, splitLocalePath } from './route-core'\n// The locale ⇄ URL mapping, both directions from one definition (EC-486), and\n// the request-side capability over it (EC-531): `resolveLocale` answers which\n// locale a request is for, `textDirection` gives the `dir` a locale renders under.\n// A multi-locale host renders `<html lang dir>` from these; a single-locale host\n// needs neither (its `locales.default` is correct by definition).\nexport { localeUrlPath, resolveLocale, textDirection } from './locale-url'\nexport type { LocaleRequest, LocaleStrategy, LocaleUrlPath } from './locale-url'\nexport type { CanvasPageOutcome, ResolveCanvasPageOptions } from './route-core'\nexport { createCanvasRoute } from './route'\nexport type {\n CanvasCacheOptions,\n CanvasContentRequest,\n CanvasRequestInfo,\n CanvasRoute,\n CanvasRouteOptions,\n CanvasRouteProps,\n // EC-524: what a host's `wrap` function is told — the collection to branch on,\n // the resolved document, and the whole resolution.\n CanvasWrapContext,\n} from './route'\n\n// Per-page SEO → Next metadata, and sitemap.xml from published routes (EC-171).\nexport { canvasPageMetadata, mergeMetadataOverParent, openGraphLocale } from './metadata'\nexport { canvasSitemapEntries, createCanvasSitemap } from './sitemap'\nexport type {\n CanvasSitemapOptions,\n CanvasSitemapResult,\n CreateCanvasSitemapOptions,\n SitemapParamsProvider,\n SitemapRouteContext,\n SitemapSkippedRoute,\n} from './sitemap'\n\n// Cache tags & instant publish (EC-146): tag format, tag-discovery caching, the\n// event → tags derivation, and the signed revalidation webhook receiver. EC-579: the\n// wire itself (event schema, signer, header) is `@elytracms/core/revalidation`,\n// re-exported so a host that signs its own event imports one package.\nexport {\n collectionCacheTag,\n docCacheTag,\n loadCachedEntry,\n nextCacheTags,\n projectSweepTag,\n revalidationTags,\n routeCacheTag,\n scopeCacheTag,\n} from './cache'\nexport type { CacheWrapper, CacheWrapperOptions, CachedEntry, RevalidationProject } from './cache'\nexport {\n REVALIDATE_SIGNATURE_HEADER,\n revalidationEventSchema,\n signRevalidationBody,\n signedRevalidationRequest,\n} from '@elytracms/core/revalidation'\nexport type { RevalidationEvent, RevalidationEventKind } from '@elytracms/core/revalidation'\nexport { evaluateRevalidateRequest } from './revalidate-core'\nexport type { RevalidateEvaluation } from './revalidate-core'\nexport { createRevalidateRoute } from './revalidate'\nexport type { RevalidateRouteHandlers, RevalidateRouteOptions } from './revalidate'\n\n// Asset-pipeline image primitive (EC-147) + the host-image renderer (EC-368):\n// the next/image-backed implementation a host registers on the\n// `@elytracms/next/host-image` bridge (the bridge itself is its OWN subpath —\n// importing it from here would bundle a second singleton instance).\nexport { ElytraImage, createAssetImageLoader, elytraHostImage, nextImagePrimitive } from './image'\nexport type {\n AssetImageLoaderOptions,\n ElytraHostImageOptions,\n ElytraImageAsset,\n ElytraImageProps,\n NextImagePrimitiveOptions,\n} from './image'\n\n// The delivery host kit (EC-368): one factory over (env, project config)\n// yielding every engine-shaped host surface — content loading, the catch-all\n// route, revalidate, draft-mode plumbing, chrome globals, layout hints.\nexport { createElytraHost } from './host-kit'\nexport type {\n ElytraContentMode,\n ElytraHost,\n ElytraHostOptions,\n HostCanvasRouteOptions,\n LoadGlobalsOptions,\n} from './host-kit'\nexport { ELYTRA_HOST_ENV_KEYS, resolveHostEnv } from './host-env'\nexport type { ElytraHostEnv } from './host-env'\nexport { assembleLiveSource, fallbackHomeRoutes, loadLiveContent } from './host-content'\nexport type { LiveContentOptions } from './host-content'\nexport { resolveGlobals } from './host-globals'\nexport type {\n GlobalsConventions,\n ResolveGlobalsOptions,\n ResolvedLink,\n ResolvedNavEntry,\n ResolvedNavLink,\n SiteGlobals,\n} from './host-globals'\nexport { firstBlockFullBleed } from './full-bleed'\n\n// Draft preview (token-gated draftMode integration).\nexport { evaluatePreviewRequest, safeRedirectTarget } from './preview-core'\nexport type { PreviewEvaluation } from './preview-core'\nexport { createPreviewDisableRoute, createPreviewRoute } from './preview'\nexport type { PreviewRouteHandlers, PreviewRouteOptions } from './preview'\n\n// Re-exports so host apps can author manifests, declare their config, and shape\n// content WITHOUT depending on the internal packages (or on zod) directly — a\n// consuming repo installs ONLY `@elytracms/next` for delivery (EC-193).\nexport { defineComponent } from '@elytracms/core/component-registry'\n// EC-190: component props are field-defs (`PropField`), not the retired `PropSpec`.\nexport type { ComponentManifest, PropField, SlotSpec } from '@elytracms/core/component-registry'\nexport type { ComponentImplementations, RenderAsset } from '@elytracms/core/runtime-renderer'\nexport { z } from 'zod'\n\n// Config + content shapes a host declares (collections, locales, routes,\n// documents) — the public surface of the structure that lives in the repo\n// (AD-11). Re-exported here so the host imports one package (EC-193).\nexport {\n documentSchema,\n routeRecordSchema,\n redirectRecordSchema,\n localeConfigSchema,\n parseLocaleConfig,\n} from '@elytracms/core/cms-core'\nexport type {\n CollectionDef,\n CmsDocument,\n FieldDef,\n LocaleConfig,\n LocaleConfigInput,\n Locale,\n RouteRecord,\n RedirectRecord,\n} from '@elytracms/core/cms-core'\n// Composition node shape (a page document's `body` is a ComponentNode list, per AD-12).\nexport type { ComponentNode } from '@elytracms/core/project-graph'\n// The delivery perspective (draft | published).\nexport type { Perspective } from '@elytracms/core/content'\n// The delivery shape of an asset (resolved url + metadata).\nexport type { AssetRecord } from '@elytracms/core/persistence'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components.ts","../src/canvas-renderer.tsx","../src/source.ts","../src/snapshot.ts","../src/route-core.ts","../src/cache.ts","../src/metadata.ts","../src/route.tsx","../src/sitemap.ts","../src/revalidate-core.ts","../src/revalidate.ts","../src/image.tsx","../src/full-bleed.ts","../src/host-env.ts","../src/host-content.ts","../src/host-globals.ts","../src/preview-core.ts","../src/preview.ts","../src/host-kit.ts","../src/index.ts"],"names":["Fragment","record","items","documentBodyCompositionNodes","cacheTags","jsx","outcome","registerComponents","str","draftMode","redirect"],"mappings":";;;;;;;;;;;;;;;;;;;AAwEO,SAAS,oBAAA,CAAqB,UAAA,GAA4C,EAAC,EAAmB;AACnG,EAAA,MAAM,EAAE,QAAA,EAAU,eAAA,EAAgB,GAAI,mBAAmB,UAAU,CAAA;AACnE,EAAA,OAAO,EAAE,UAAU,eAAA,EAAgB;AACrC;AC0BA,SAAS,iBAAiB,SAAA,EAA4D;AACpF,EAAA,OAAO,CAAC,GAAA,KAAQ,SAAA,CAAU,CAAA,EAAG,GAAA,CAAI,UAAU,CAAA,CAAA,EAAI,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA,IAAK,IAAA;AAC9D;AAGA,SAAS,aAAa,KAAA,EAA4C;AAChE,EAAA,OAAO,CAAC,GAAA,KAAQ,KAAA,CAAM,CAAA,EAAG,GAAA,CAAI,UAAU,CAAA,CAAA,EAAI,GAAA,CAAI,EAAE,CAAA,CAAE,CAAA,IAAK,IAAA;AAC1D;AAOA,SAAS,gBAAgB,QAAA,EAAwD;AAC/E,EAAA,OAAO,CAAC,KAAA,KAAU;AAChB,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,qBAAA,CAAsB,KAAK,CAAC,CAAA;AACnD,IAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,IAAA;AAAA,EACxC,CAAA;AACF;AASA,SAAS,gBAAA,CACP,IAAA,EACA,UAAA,EACA,QAAA,EACiB;AACjB,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,QAAA,CAAS,WAAA,CAAY,KAAK,WAAW,CAAA;AACjE,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,EAAA,MAAM,OAAA,GAAU,aAAA,CAAc,IAAA,EAAM,QAAA,EAAU,WAAW,QAAQ,CAAA;AACjE,EAAA,IAAI,CAAC,OAAA,IAAW,OAAA,CAAQ,KAAA,KAAU,YAAY,OAAO,IAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAA;AAChD,EAAA,MAAM,KAAA,GAAQ,QAAA,GAAW,qBAAA,CAAsB,OAAA,CAAQ,KAAK,CAAC,CAAA;AAC7D,EAAA,OAAO,KAAA,IAAS,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,CAAM,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,KAAA,CAAM,OAAO,CAAA,GAAI,IAAA;AACvF;AAOA,SAAS,qBAAqB,KAAA,EAAmE;AAC/F,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,kBAAA,EAAiB,iBAAA,EAAkB,mBAAiB,KAAA,CAAM,MAAA,EAAQ,IAAA,EAAK,MAAA,EACzE,qCAA2B,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,EACtD,CAAA;AAEJ;AAGA,SAAS,cAAc,MAAA,EAAsE;AAC3F,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,EAAA,EAAI,KAAK,CAAC,CAAC,CAAA;AAC7D,EAAA,OAAO,CAAC,EAAA,KAAO;AACb,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA;AACzB,IAAA,OAAO,KAAA,GACH;AAAA,MACE,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,YAAY,KAAA,CAAM,UAAA;AAAA;AAAA,MAElB,MAAM,KAAA,CAAM,IAAA;AAAA,MACZ,MAAM,KAAA,CAAM;AAAA,KACd,GACA,IAAA;AAAA,EACN,CAAA;AACF;AAGA,SAAS,uBAAuB,KAAA,EAAsC;AACpE,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,kBAAA,EAAiB,iBAAA,EAAkB,gBAAA,EAAgB,MAAM,MAAA,EAC3D,QAAA,EAAA,CAAA,4CAAA,EAA+C,KAAA,CAAM,MAAM,CAAA,EAAA,CAAA,EAC9D,CAAA;AAEJ;AAGA,SAAS,uBAAA,GAAqC;AAC5C,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,kBAAA,EAAiB,kBAAA,EACnB,QAAA,EAAA,CAAA,sDAAA,CAAA,EACH,CAAA;AAEJ;AASA,SAAS,wBAAA,CAAyB,OAAsB,QAAA,EAAgC;AACtF,EAAA,IAAI,cAAA,CAAe,QAAQ,CAAA,IAAK,QAAA,CAAS,SAAS,mBAAA,EAAqB;AACrE,IAAA,OAAQ,QAAA,CAAS,MAAmC,QAAA,IAAY,IAAA;AAAA,EAClE;AACA,EAAA,OAAO,QAAA;AACT;AAOA,eAAsB,eAAe,KAAA,EAAgD;AACnF,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,KAAA;AAE/B,EAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAG1B,IAAA,uBAAO,GAAA,CAAC,sBAAA,EAAA,EAAuB,MAAA,EAAQ,MAAA,CAAO,MAAA,EAAQ,CAAA;AAAA,EACxD;AAEA,EAAA,MAAM,GAAA,GAAqB;AAAA,IACzB,UAAU,UAAA,CAAW,QAAA;AAAA,IACrB,iBAAiB,UAAA,CAAW,eAAA;AAAA,IAC5B,YAAA,EAAc,aAAA,CAAc,KAAA,CAAM,MAAA,IAAU,EAAE,CAAA;AAAA;AAAA;AAAA,IAG9C,GAAI,KAAA,CAAM,SAAA,GAAY,EAAE,eAAA,EAAiB,iBAAiB,KAAA,CAAM,SAAS,CAAA,EAAE,GAAI,EAAC;AAAA;AAAA;AAAA,IAGhF,GAAI,KAAA,CAAM,QAAA,GAAW,EAAE,cAAA,EAAgB,gBAAgB,KAAA,CAAM,QAAQ,CAAA,EAAE,GAAI,EAAC;AAAA;AAAA;AAAA;AAAA,IAI5E,GAAI,KAAA,CAAM,KAAA,GAAQ,EAAE,WAAA,EAAa,aAAa,KAAA,CAAM,KAAK,CAAA,EAAE,GAAI,EAAC;AAAA,IAChE,YAAA,EAAc,CAAC,IAAA,EAAM,QAAA,KAAa;AAChC,MAAA,MAAM,QAAA,GAAW,wBAAA,CAAyB,IAAA,EAAM,QAAQ,CAAA;AAExD,MAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,EAAM,UAAA,EAAY,MAAM,QAAQ,CAAA;AACjE,MAAA,IAAI,CAAC,SAAS,OAAO,QAAA;AACrB,MAAA,4BACGA,UAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,oBAAA,EAAA,EAAqB,MAAA,EAAQ,IAAA,CAAK,EAAA,EAAI,UAAU,OAAA,EAAS,CAAA;AAAA,QACzD;AAAA,OAAA,EAAA,EAFY,KAAK,EAGpB,CAAA;AAAA,IAEJ;AAAA,GACF;AAEA,EAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AAUxB,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBAAO,GAAA,CAAAA,QAAAA,EAAA,EAAG,QAAA,EAAA,uBAAA,CAAwB,WAAA,CAAY,UAAU,KAAA,CAAM,YAAY,CAAA,EAAG,GAAG,CAAA,EAAE,CAAA;AAAA,EACpF;AAEA,EAAA,2BAAQ,uBAAA,EAAA,EAAwB,CAAA;AAClC;AAUO,SAAS,WAAA,CACd,UACA,YAAA,EACoB;AACpB,EAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAC;AACvB,EAAA,MAAM,MAAA,GAAS,YAAA,EAAc,IAAA,IAAQ,EAAC;AACtC,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,OAAO,MAAA,CAAO,OAAA,CAAQ,CAAC,IAAA,KAAS,iBAAA,CAAkB,QAAA,CAAS,IAAI,CAAC,CAAC,CAAA;AACxF,EAAA,OAAO,iBAAA,CAAkB,aAAA,CAAc,QAAQ,CAAC,CAAA;AAClD;AA2BA,SAAS,cAAc,QAAA,EAA4C;AACjE,EAAA,MAAM,IAAA,GAAO,SAAS,MAAM,CAAA;AAM5B,EAAA,IAAI,wBAAA,CAAyB,IAAI,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,IAAK,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,IAAA;AAChG,EAAA,MAAM,OAAA,GAAU,SAAS,SAAS,CAAA;AAClC,EAAA,IAAI,wBAAA,CAAyB,OAAO,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,oBAAA,CAAqB,OAAO,CAAA,EAAG,OAAO,OAAA;AAGzG,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,EAAG;AAC3C,IAAA,IAAI,wBAAA,CAAyB,KAAK,CAAA,EAAG,OAAO,KAAA;AAC5C,IAAA,IAAI,oBAAA,CAAqB,KAAK,CAAA,EAAG,OAAO,KAAA;AACxC,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,oBAAoB,CAAA,EAAG,OAAO,KAAA;AAAA,EAC5F;AACA,EAAA,OAAO,MAAA;AACT;AAeO,SAAS,kBAAA,CACd,UACA,YAAA,EACiB;AACjB,EAAA,OAAO,4BAAA,CAA6B,WAAA,CAAY,QAAA,EAAU,YAAY,CAAC,CAAA;AACzE;AAGA,SAAS,yBAAyB,KAAA,EAAyB;AACzD,EAAA,OACE,yBAAA,CAA0B,KAAK,CAAA,IAC/B,KAAA,CAAM,OAAA,CAAQ,IAAA;AAAA,IACZ,CAAC,SACC,OAAO,IAAA,KAAS,YAChB,IAAA,KAAS,IAAA,IACR,KAA4B,IAAA,KAAS;AAAA,GAC1C;AAEJ;AAGA,SAAS,qBAAqB,KAAA,EAAyB;AACrD,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,OAAQ,KAAA,CAA2B,EAAA,KAAO,QAAA,IAC1C,OAAQ,KAAA,CAAoC,WAAA,KAAgB,QAAA;AAEhE;ACrTO,SAAS,cAAA,CAAe,UAAqB,IAAA,EAAuC;AACzF,EAAA,MAAM,GAAA,GAAM,CAAC,KAAA,EAAO,GAAG,IAAI,CAAA;AAC3B,EAAA,OAAO,SAAA,CAAU;AAAA,IACf,MAAM,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AAAA,IAC/B,aAAa,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,WAAW,CAAA;AAAA,IAC7C,QAAQ,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACnC,QAAQ,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACnC,QAAQ,KAAA,CAAM;AAAA,GACf,CAAA;AACH;ACoCO,SAAS,0BAA0B,IAAA,EAA0C;AAKlF,EAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,IAAA,CAAK,OAAO,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,IAAU,EAAC;AAC/B,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,IAAa,EAAC;AACrC,EAAA,MAAM,WACJ,IAAA,CAAK,QAAA,KAAa,CAAC,KAAA,KAAuB,CAAA,mBAAA,EAAsB,MAAM,UAAU,CAAA,CAAA,CAAA;AAElF,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAA2B;AACnD,EAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,IAAe,IAAI,WAAA,CAAY,GAAA,CAAI,UAAA,CAAW,EAAA,EAAI,UAAU,CAAA;AAE1F,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAmC;AACzD,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAqC;AAC9D,EAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG;AACzC,IAAA,MAAM,GAAA,GAAM,iBAAA,CAAkB,MAAM,CAAA,GAAI,OAAO,KAAA,GAAQ,MAAA;AACvD,IAAA,SAAA,CAAU,GAAA,CAAI,WAAA,CAAY,GAAG,CAAA,EAAG,MAAM,CAAA;AACtC,IAAA,MAAM,OAAO,YAAA,CAAa,GAAA,CAAI,GAAA,CAAI,UAAU,KAAK,EAAC;AAClD,IAAA,IAAA,CAAK,KAAK,MAAM,CAAA;AAChB,IAAA,YAAA,CAAa,GAAA,CAAI,GAAA,CAAI,UAAA,EAAY,IAAI,CAAA;AAAA,EACvC;AAEA,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAyB;AAC5C,EAAA,KAAA,MAAWC,OAAAA,IAAU,KAAK,MAAA,IAAU,IAAI,MAAA,CAAO,GAAA,CAAIA,OAAAA,CAAO,EAAA,EAAIA,OAAM,CAAA;AAEpE,EAAA,MAAM,SAAA,GAAY,CAAC,YAAA,MAA8C;AAAA,IAC/D,UAAA,EAAY,CAAC,EAAA,KAAO,WAAA,CAAY,IAAI,EAAE,CAAA;AAAA,IACtC,UAAU,CAAC,GAAA,KAAqB,UAAU,GAAA,CAAI,WAAA,CAAY,GAAG,CAAC,CAAA;AAAA,IAC9D,WAAW,CAAC,YAAA,KAAiB,aAAa,GAAA,CAAI,YAAY,KAAK,EAAC;AAAA,IAChE,KAAA,EAAO,CAAC,EAAA,KAAO,MAAA,CAAO,IAAI,EAAE,CAAA;AAAA,IAC5B,UAAU,CAAC,KAAA,KAAU,KAAA,CAAM,GAAA,IAAO,SAAS,KAAK;AAAA,GAClD,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KACd,mBAAA,CAAoB;AAAA,IAClB,MAAA,EAAQ,SAAA,CAAU,IAAA,CAAK,WAAW,CAAA;AAAA,IAClC,SAAS,oBAAA,CAAqB;AAAA,MAC5B,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,OAAA,CAAQ,OAAA;AAAA,MAC/B,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB;AAAA,KACD,CAAA;AAAA,IACD,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAEH,EAAA,OAAO;AAAA,IACL,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,OAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,QAAQ,MACN,CAAC,GAAG,MAAA,CAAO,MAAA,EAAQ,CAAA,CAAE,GAAA,CAAI,CAACA,OAAAA,KAAW,cAAcA,OAAAA,EAAQA,OAAAA,CAAO,OAAO,QAAA,CAASA,OAAM,CAAC,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAK5F,GAAI,KAAK,WAAA,GAAc,EAAE,aAAa,IAAA,CAAK,WAAA,KAAgB;AAAC,GAC9D;AACF;AAQA,eAAsB,qBAAA,CACpB,OAAA,EACA,SAAA,EACA,OAAA,EAC0B;AAE1B,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,GAAA,CAAI,WAAW,SAAS,CAAA;AACrD,EAAA,MAAM,eAAA,GAAkB,MAAM,OAAA,CAAQ,GAAA,CAAI,cAAc,SAAS,CAAA;AACjE,EAAA,MAAM,cAAA,GAAiB,MAAM,OAAA,CAAQ,GAAA,CAAI,wBAAwB,SAAS,CAAA;AAC1E,EAAA,MAAM,YAAA,GAAe,MAAM,OAAA,CAAQ,MAAA,CAAO,WAAW,SAAS,CAAA;AAI9D,EAAA,MAAM,eAAA,GAAkB,MAAM,OAAA,CAAQ,SAAA,CAAU,cAAc,SAAS,CAAA;AACvE,EAAA,MAAM,SAAA,GAA8B,eAAA,CACjC,MAAA,CAAO,CAACA,OAAAA,KAAWA,QAAO,OAAO,CAAA,CACjC,GAAA,CAAI,CAACA,OAAAA,MAAY;AAAA,IAChB,IAAIA,OAAAA,CAAO,EAAA;AAAA,IACX,MAAMA,OAAAA,CAAO,IAAA;AAAA,IACb,IAAIA,OAAAA,CAAO,EAAA;AAAA,IACX,WAAWA,OAAAA,CAAO;AAAA,GACpB,CAAE,CAAA;AAMJ,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAsC;AAChE,EAAA,KAAA,MAAWA,WAAU,cAAA,EAAgB;AACnC,IAAA,MAAM,MAAM,CAAA,EAAGA,OAAAA,CAAO,UAAU,CAAA,CAAA,EAAIA,QAAO,KAAK,CAAA,CAAA;AAChD,IAAA,IAAI,SAAA,GAAY,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,SAAA,uBAAgB,GAAA,EAAI;AACpB,MAAA,aAAA,CAAc,GAAA,CAAI,KAAK,SAAS,CAAA;AAAA,IAClC;AACA,IAAA,SAAA,CAAU,GAAA,CAAIA,OAAAA,CAAO,OAAA,EAASA,OAAAA,CAAO,QAAQ,CAAA;AAAA,EAC/C;AACA,EAAA,MAAM,SAAA,GAAqC,eAAA,CAAgB,GAAA,CAAI,CAACA,OAAAA,KAAW;AACzE,IAAA,IAAIA,OAAAA,CAAO,gBAAA,KAAqB,MAAA,EAAW,OAAOA,OAAAA,CAAO,QAAA;AAEzD,IAAA,MAAM,SAAA,GAAY,aAAA,CACf,GAAA,CAAI,CAAA,EAAGA,QAAO,QAAA,CAAS,UAAU,CAAA,CAAA,EAAIA,OAAAA,CAAO,SAAS,EAAE,CAAA,CAAE,CAAA,EACxD,GAAA,CAAIA,QAAO,gBAAgB,CAAA;AAC/B,IAAA,OAAO,EAAE,KAAA,EAAOA,OAAAA,CAAO,UAAU,SAAA,EAAW,QAAA,EAAU,EAAC,EAAE;AAAA,EAC3D,CAAC,CAAA;AAID,EAAA,OAAO,yBAAA,CAA0B;AAAA,IAC/B,SAAA;AAAA,IACA,SAAS,OAAA,CAAQ,OAAA;AAAA,IACjB,GAAI,QAAQ,MAAA,GAAS,EAAE,QAAQ,OAAA,CAAQ,MAAA,KAAW,EAAC;AAAA,IACnD,WAAW,OAAA,CAAQ,SAAA,GAAY,CAAC,GAAG,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA;AAAA,IACxD,GAAI,MAAA,GAAS,EAAE,aAAa,MAAA,CAAO,WAAA,KAAgB,EAAC;AAAA,IACpD,SAAA;AAAA,IACA,MAAA,EAAQ,YAAA;AAAA,IACR,UAAU,CAAC,KAAA,KAAU,QAAQ,KAAA,CAAM,GAAA,CAAI,MAAM,UAAU;AAAA,GACxD,CAAA;AACH;ACvJO,SAAS,iBAAA,CACd,QACA,IAAA,EACmB;AACnB,EAAA,MAAM,UAAA,GAAa,OAAO,QAAA,EAAU,UAAA;AACpC,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe,EAAC;AAAA,IACjD,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB;AAAA,GACF;AACF;AA2EO,SAAS,aAAa,IAAA,EAA6C;AACxE,EAAA,OAAO,GAAA,GAAA,CAAO,IAAA,IAAQ,EAAC,EAAG,KAAK,GAAG,CAAA;AACpC;AA+BA,IAAM,WAAA,GAAqB,MAAA,iBAAM,IAAI,IAAA,IAAO,WAAA,EAAY;AAQxD,SAAS,iBAAA,CACP,SAAA,EACA,QAAA,EACA,MAAA,EACA,MACA,eAAA,EAC0B;AAC1B,EAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AACnC,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAK5B,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,IAAA,yBAAA,CAA0B,IAAA,EAAM,QAAA,EAAU,GAAA,EAAK,eAAA,EAAiB,IAAI,CAAA;AAAA,EACtE;AAMA,EAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,IAAA,0BAAA,CAA2B,KAAA,EAAO,EAAE,OAAA,EAAS,CAAC,OAAO,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,EAAG,CAAA;AAAA,EACpE;AAMA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,QAAA,GAAW,YAAY,CAAA;AAC9C,EAAA,MAAM,SAAA,GACJ,OAAO,OAAA,KAAY,QAAA,GACf,UACA,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,IAAY,OAAQ,OAAA,CAA6B,EAAA,KAAO,QAAA,GACnF,QAA2B,EAAA,GAC5B,IAAA;AACR,EAAA,IAAI,SAAA,EAAW,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA;AAChC,EAAA,OAAO,GAAA,CAAI,IAAA,KAAS,CAAA,GAAI,EAAC,GAAI,SAAA,CAAU,MAAA,CAAO,CAAC,KAAA,KAAU,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,EAAE,CAAC,CAAA;AAC5E;AAqBA,SAAS,UAAA,CAAW,QAA2B,YAAA,EAA+C;AAC5F,EAAA,MAAMC,MAAAA,GAAQ,WAAA,CAAY,MAAA,CAAO,QAAA,EAAU,YAAY,CAAA;AACvD,EAAA,MAAM,SAAA,GAAY,2BAA2BA,MAAK,CAAA;AAClD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO;AAAA,MACL,GAAGC,6BAA6BD,MAAK,CAAA;AAAA,MACrC,GAAG,SAAA,CAAU,OAAA,CAAQ,CAAC,KAAA,KAAU,qBAAA,CAAsB,KAAK,CAAC;AAAA,KAC9D;AAAA,IACA;AAAA,GACF;AACF;AAYA,SAAS,eAAA,CACP,QACA,MAAA,EACkC;AAClC,EAAA,MAAM,YAAA,GAAe,OAAO,QAAA,EAAU,UAAA;AACtC,EAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,MAAA,CAAO,aAAa,OAAO,MAAA;AACjD,EAAA,MAAM,UAAA,GAAa,OAAO,WAAA,CAAY,IAAA,CAAK,CAAC,SAAA,KAAc,SAAA,CAAU,OAAO,YAAY,CAAA;AACvF,EAAA,IAAI,CAAC,UAAA,EAAY,YAAA,EAAc,OAAO,MAAA;AACtC,EAAA,MAAM,IAAA,GAAO,uBAAuB,UAAU,CAAA;AAC9C,EAAA,OAAO,IAAA,CAAK,SAAS,CAAA,GAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA,EAAE,GAAI,MAAA;AACjD;AAgBA,SAAS,qBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACkE;AAClE,EAAA,IAAI,CAAC,UAAU,OAAO,EAAE,WAAW,EAAC,EAAG,OAAA,EAAS,EAAC,EAAE;AACnD,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,EAAA,MAAM,SAAA,GAAY,0BAAA;AAAA,IAChB,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG,CAAA;AAAA,IACnC,EAAC;AAAA,IACD;AAAA,GACF;AACA,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,CAAC,GAAG,OAAO,CAAA,EAAE;AAC5C;AAkBA,SAAS,oBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACA,KAAA,EACkF;AAClF,EAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAE,QAAA,EAAU,EAAC,EAAG,IAAA,EAAM,EAAC,EAAG,QAAA,EAAU,KAAA,EAAM;AAChE,EAAA,MAAM,OAAoB,EAAC;AAC3B,EAAA,IAAI,OAAA;AACJ,EAAA,MAAM,GAAA,GAAM,MAAe,OAAA,KAAY,KAAA,EAAM;AAC7C,EAAA,MAAM,QAAA,GAAW,yBAAA;AAAA,IACf,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,KAAA,KAAwB;AACvB,MAAA,MAAM,MAAA,GAAS,OAAO,aAAA,CAAc,KAAA,CAAM,YAAY,oBAAA,CAAqB,KAAA,EAAO,GAAG,CAAC,CAAA;AACtF,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,CAAA;AACrB,MAAA,OAAO,OAAO,EAAA,GAAM,MAAA,CAAO,YAAiC,EAAE,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,IACtF,CAAA;AAAA;AAAA;AAAA;AAAA,IAIA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GACrC;AACA,EAAA,OAAO,EAAE,QAAA,EAAU,IAAA,EAAM,QAAA,EAAU,YAAY,MAAA,EAAU;AAC3D;AAQA,SAAS,oBAAA,CAAqB,OAAqB,GAAA,EAAuC;AACxF,EAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,KAAA,EAAO,GAAG,CAAA;AAC3C,EAAA,OAAO;AAAA,IACL,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,GAAI,QAAQ,IAAA,GAAO,EAAE,MAAM,OAAA,CAAQ,IAAA,KAAS,EAAC;AAAA,IAC7C,GAAI,QAAQ,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAgB,GAAI;AAAC,GAC1E;AACF;AAiBA,SAAS,iBAAA,CACP,IAAA,EACA,MAAA,EACA,QAAA,EACsD;AACtD,EAAA,IAAI,CAAC,UAAU,OAAO,EAAE,OAAO,EAAC,EAAG,OAAA,EAAS,EAAC,EAAE;AAC/C,EAAA,MAAM,IAAA,GAAO,8BAAA;AAAA,IACX,IAAA,CAAK,KAAA;AAAA,IACL,QAAA;AAAA,IACA,CAAC,GAAA,KAAQ,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GACrC;AAIA,EAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,IAAA,0BAAA,CAA2B,KAAA,EAAO;AAAA,MAChC,MAAA,EAAQ,CAAC,GAAA,KAAQ;AACf,QAAA,IAAA,CAAK,GAAG,GAAA,CAAI,UAAU,IAAI,GAAA,CAAI,EAAE,EAAE,CAAA,GAAI,GAAA;AAAA,MACxC;AAAA,KACD,CAAA;AAAA,EACH;AACA,EAAA,MAAM,QAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,GAAG,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC7C,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,WAAA,CAAY,GAAG,CAAA;AAClC,IAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,KAAA,CAAM,GAAG,CAAA,GAAI,GAAA;AAAA,EACjC;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAE;AAC7C;AAQA,eAAsB,kBACpB,MAAA,EACA,GAAA,EACA,WAAA,EACA,OAAA,GAAoC,EAAC,EACT;AAC5B,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,KAAS,eAAA,CAAgB,GAAA,EAAK,OAAO,OAAO,CAAA;AAC5D,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,EAAE,WAAA,EAAa,QAAQ,CAAA;AACpD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA;AAEtC,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,UAAA,IAAc,MAAA,CAAO,QAAA,EAAU;AACnD,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,UAAA;AAAA,MACN,MAAA,EAAQ,OAAO,QAAA,CAAS,MAAA;AAAA,MACxB,SAAA,EAAW,OAAO,QAAA,CAAS,SAAA;AAAA,MAC3B,MAAM,MAAA,CAAO;AAAA,KACf;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAC1B,IAAA,OAAO,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,EAChD;AAKA,EAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,MAAA,EAAQ,MAAM,CAAA;AACnD,EAAA,MAAM,IAAA,GAAO,UAAA,CAAW,MAAA,EAAQ,YAAY,CAAA;AAE5C,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAA,IAAS,IAAK,EAAC;AAGxC,EAAA,MAAM,WAAA,GAAc,QAAQ,QAAA,GACxB,iBAAA;AAAA,IAAkB,SAAA;AAAA,IAAW,OAAA,CAAQ,QAAA;AAAA,IAAU,MAAA;AAAA,IAAQ,IAAA;AAAA,IAAM,CAAC,GAAA,KAC5D,MAAA,CAAO,eAAA,CAAgB,GAAG;AAAA,GAC5B,GACA,SAAA;AAKJ,EAAA,MAAM,YAAYE,SAAAA,CAAU;AAAA,IAC1B,QAAQ,WAAA,CAAY,GAAA,CAAI,CAAC,KAAA,KAAU,MAAM,EAAE,CAAA;AAAA,IAC3C,MAAA,EAAQ,OAAO,IAAA,CAAK;AAAA,GACrB,CAAA;AAKD,EAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,eAAA,EAAgB,GAAI,qBAAA;AAAA,IAC9C,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA,CAAQ;AAAA,GACV;AAGA,EAAA,MAAM,YAAA,GAAeA,UAAU,EAAE,IAAA,EAAM,iBAAiB,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,CAAA;AAKpF,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,IAAA,EAAM,WAAA;AAAA,IACN;AAAA,GACF,GAAI,qBAAqB,IAAA,EAAM,MAAA,EAAQ,QAAQ,QAAA,EAAU,OAAA,CAAQ,SAAS,WAAW,CAAA;AAIrF,EAAA,MAAM,EAAE,OAAO,OAAA,EAAS,WAAA,KAAgB,iBAAA,CAAkB,IAAA,EAAM,MAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAA;AACxF,EAAA,MAAM,QAAA,GAAWA,UAAU,EAAE,IAAA,EAAM,aAAa,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,CAAA;AAC5E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,MAAA;AAAA,IACA,MAAA,EAAQ,WAAA;AAAA,IACR,SAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,GAAI,YAAA,GAAe,EAAE,YAAA,KAAiB,EAAC;AAAA,IACvC,IAAA,EAAM,cAAA;AAAA,MACJ,MAAA,CAAO,IAAA;AAAA,MACP,GAAG,WAAA;AAAA,MACH,SAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA;AACF,GACF;AACF;;;AC9dO,SAAS,aAAA,CAAc,KAAa,KAAA,EAAwB;AACjE,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,CAAM,MAAA,KAAW,IAAI,GAAA,GAAM,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAC5E;AAMO,SAAS,WAAA,CAAY,QAAgB,KAAA,EAAwB;AAClE,EAAA,OAAO,aAAA,CAAc,CAAA,IAAA,EAAO,MAAM,CAAA,CAAA,EAAI,KAAK,CAAA;AAC7C;AAGO,SAAS,kBAAA,CAAmB,UAAA,EAAoB,MAAA,EAAgB,KAAA,EAAwB;AAC7F,EAAA,OAAO,cAAc,CAAA,WAAA,EAAc,UAAU,CAAA,CAAA,EAAI,MAAM,IAAI,KAAK,CAAA;AAClE;AAGO,SAAS,aAAA,CAAc,OAAA,EAAiB,MAAA,EAAgB,KAAA,EAAwB;AACrF,EAAA,OAAO,cAAc,CAAA,MAAA,EAAS,OAAO,CAAA,CAAA,EAAI,MAAM,IAAI,KAAK,CAAA;AAC1D;AAOO,SAAS,aAAA,CAAc,SAAiB,KAAA,EAAwB;AACrE,EAAA,OAAO,aAAA,CAAc,CAAA,MAAA,EAAS,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA;AAChD;AAMO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,aAAA,CAAc,WAAW,KAAK,CAAA;AACvC;AAOO,SAAS,aAAA,CAAc,MAAiB,KAAA,EAA0B;AACvE,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,EAAA,GAAA,CAAI,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC9B,EAAA,KAAA,MAAW,MAAA,IAAU,KAAK,IAAA,EAAM,GAAA,CAAI,IAAI,WAAA,CAAY,MAAA,EAAQ,KAAK,CAAC,CAAA;AAClE,EAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,EAAa;AACzC,IAAA,GAAA,CAAI,IAAI,kBAAA,CAAmB,UAAA,EAAY,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAC,CAAA;AAAA,EAC5D;AACA,EAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,cAAc,KAAA,EAAO,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAC,CAAA;AACjF,EAAA,KAAA,MAAW,OAAA,IAAW,KAAK,MAAA,EAAQ,GAAA,CAAI,IAAI,aAAA,CAAc,OAAA,EAAS,KAAK,CAAC,CAAA;AACxE,EAAA,OAAO,CAAC,GAAG,GAAG,CAAA,CAAE,IAAA,EAAK;AACvB;AAqCO,SAAS,gBAAA,CAAiB,OAA0B,OAAA,EAAwC;AACjG,EAAA,MAAM,EAAE,OAAM,GAAI,OAAA;AAClB,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,SAAA;AAAA,IACL,KAAK,WAAA;AAAA,IACL,KAAK,QAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,CAAA,EAAG,KAAA,CAAM,UAAU,IAAI,KAAA,CAAM,EAAE,CAAA,CAAA,EAAI,KAAK,CAAC,CAAA;AAC9D,MAAA,KAAA,MAAW,MAAA,IAAU,OAAA,CAAQ,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,mBAAmB,KAAA,CAAM,UAAA,EAAY,MAAA,EAAQ,KAAK,CAAC,CAAA;AAClG,MAAA;AAAA,IACF,KAAK,WAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC/B,MAAA;AAAA,IACF,KAAK,OAAA;AACH,MAAA,IAAA,CAAK,GAAA,CAAI,aAAA,CAAc,KAAA,CAAM,EAAA,EAAI,KAAK,CAAC,CAAA;AACvC,MAAA,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC/B,MAAA;AAEA;AAEJ,EAAA,OAAO,CAAC,GAAG,IAAI,CAAA,CAAE,IAAA,EAAK;AACxB;AAsCO,SAAS,cAAA,CAAe,EAAE,KAAA,EAAO,IAAA,EAAM,KAAI,EAAwC;AACxF,EAAA,OAAO,CAAC,KAAA,IAAS,EAAA,EAAI,IAAA,IAAQ,IAAI,GAAG,CAAA;AACtC;AAgCO,IAAM,iBAAA,GAAqC,EAAE,UAAA,EAAY,QAAA,EAAU,QAAQ,QAAA,EAAS;AAgBpF,IAAM,qBAAA,GAAyC,EAAE,UAAA,EAAY,CAAA,EAAG,QAAQ,GAAA,EAAI;AAW5E,IAAM,uBAAA,GAA2C,EAAE,UAAA,EAAY,EAAA,EAAI,QAAQ,QAAA,EAAS;AAMpF,SAAS,eAAA,CAAgB,OAAA,EAA6B,UAAA,GAAa,KAAA,EAAwB;AAChG,EAAA,IAAI,OAAA,KAAY,eAAe,OAAO,qBAAA;AACtC,EAAA,OAAO,aAAa,uBAAA,GAA0B,iBAAA;AAChD;AAQO,IAAM,yBAAA,GAA4B,GAAA;AAYlC,SAAS,aAAA,CAAc,MAAiB,KAAA,EAA4B;AACzE,EAAA,MAAM,KAAA,GAAQ,gBAAgB,KAAK,CAAA;AACnC,EAAA,MAAM,IAAA,GAAO,cAAc,IAAA,EAAM,KAAK,EAAE,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,KAAQ,KAAK,CAAA;AACrE,EAAA,MAAM,KAAA,GAAoB,CAAC,CAAC,KAAK,CAAC,CAAA;AAClC,EAAA,KAAA,IAAS,QAAQ,CAAA,EAAG,KAAA,GAAQ,IAAA,CAAK,MAAA,EAAQ,SAAS,yBAAA,EAA2B;AAC3E,IAAA,KAAA,CAAM,KAAK,IAAA,CAAK,KAAA,CAAM,KAAA,EAAO,KAAA,GAAQ,yBAAyB,CAAC,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,KAAA;AACT;AAKA,IAAM,eAAA,uBAAsB,GAAA,EAA0C;AAEtE,SAAS,WAAA,CAAY,OAAe,IAAA,EAAsB;AACxD,EAAA,OAAO,IAAA,CAAK,SAAA,CAAU,CAAC,KAAA,EAAO,IAAI,CAAC,CAAA;AACrC;AAkBO,SAAS,sBAAA,CACd,KAAA,EACA,IAAA,EACA,OAAA,EACM;AACN,EAAA,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,KAAA,EAAO,IAAI,GAAG,OAAuC,CAAA;AACvF;AAOO,SAAS,iBAAA,CAAqB,OAAe,IAAA,EAAsC;AACxF,EAAA,MAAM,UAAU,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,KAAA,EAAO,IAAI,CAAC,CAAA;AAC5D,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,6DAAA,EAAgE,KAAK,CAAA,oBAAA,EAC/D,IAAI,CAAA,gMAAA;AAAA,KAGZ;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;;;ACrTA,SAAS,IAAI,KAAA,EAA+B;AAC1C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,IAAA;AACjE;AAGA,SAAS,aAAa,KAAA,EAAwB;AAC5C,EAAA,OAAO,kBAAkB,IAAA,CAAK,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,GAAG,CAAA;AAC9D;AAOA,SAAS,cAAA,CAAe,OAAgB,MAAA,EAAiD;AAMvF,EAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACtC,IAAA,MAAM,MAAO,KAAA,CAA4B,GAAA;AACzC,IAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,GAAS,IAAI,GAAA,GAAM,IAAA;AAAA,EAC3D;AACA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AAC5D,EAAA,IAAI,YAAA,CAAa,KAAK,CAAA,EAAG,OAAO,KAAA;AAChC,EAAA,OAAO,MAAA,CAAO,KAAK,CAAC,KAAA,KAAU,MAAM,EAAA,KAAO,KAAK,GAAG,GAAA,IAAO,IAAA;AAC5D;AAYO,SAAS,gBAAgB,MAAA,EAA+B;AAC7D,EAAA,OAAO,MAAA,CAAO,SAAS,GAAG,CAAA,GAAI,OAAO,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,GAAI,IAAA;AAC5D;AAEO,SAAS,kBAAA,CACd,MAAA,EACA,MAAA,GAAmC,EAAC,EAC1B;AACV,EAAA,MAAM,MAAM,MAAA,CAAO,QAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,GAAM,UAAU,CAAC,CAAA;AACtC,EAAA,MAAM,cAAA,GAAiB,GAAA,CAAI,GAAA,GAAM,gBAAgB,CAAC,CAAA;AAClD,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,GAAA,GAAM,YAAY,CAAC,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,GAAA,GAAM,YAAY,GAAG,MAAM,CAAA;AAC7D,EAAA,MAAM,UAAA,GAAa,GAAA,GAAM,YAAY,CAAA,KAAM,IAAA;AAC3C,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,GAAA,GAAM,cAAc,CAAC,CAAA;AAG9C,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,GAAM,MAAM,CAAC,CAAA,IAAK,GAAA,CAAI,GAAA,GAAM,OAAO,CAAC,CAAA;AACzD,EAAA,MAAM,QAAQ,QAAA,IAAY,QAAA;AAE1B,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,IAAI,KAAA,KAAU,IAAA,EAAM,QAAA,CAAS,KAAA,GAAQ,KAAA;AACrC,EAAA,IAAI,cAAA,WAAyB,WAAA,GAAc,cAAA;AAE3C,EAAA,MAAM,UAAU,UAAA,IAAc,KAAA;AAC9B,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA;AACnD,IAAA,QAAA,CAAS,SAAA,GAAY;AAAA,MACnB,GAAI,OAAA,KAAY,IAAA,GAAO,EAAE,KAAA,EAAO,OAAA,KAAY,EAAC;AAAA,MAC7C,GAAI,cAAA,GAAiB,EAAE,WAAA,EAAa,cAAA,KAAmB,EAAC;AAAA,MACxD,GAAI,UAAA,GAAa,EAAE,MAAA,EAAQ,CAAC,EAAE,GAAA,EAAK,UAAA,EAAY,CAAA,EAAE,GAAI,EAAC;AAAA,MACtD,GAAI,QAAA,KAAa,IAAA,GAAO,EAAE,MAAA,EAAQ,QAAA,KAAa;AAAC,KAClD;AAAA,EACF;AAEA,EAAA,IAAI,UAAA,EAAY,QAAA,CAAS,MAAA,GAAS,EAAE,OAAO,KAAA,EAAM;AAIjD,EAAA,IAAI,YAAA,EAAc,QAAA,CAAS,UAAA,GAAa,EAAE,WAAW,YAAA,EAAa;AAElE,EAAA,OAAO,QAAA;AACT;AAyBO,SAAS,uBAAA,CACd,QACA,IAAA,EACU;AACV,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,MAAM,MAAA,GAAkC,EAAE,GAAG,IAAA,EAAK;AAClD,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,SAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AACnD,IAAA,MAAM,WAAA,GAAe,OAAmC,GAAG,CAAA;AAC3D,IAAA,IAAI,aAAA,CAAc,SAAS,CAAA,IAAK,aAAA,CAAc,WAAW,CAAA,EAAG;AAC1D,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,EAAE,GAAG,WAAA,EAAa,GAAG,SAAA,EAAU;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAAkD;AACvE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;ACeO,IAAM,sBAAA,GAA4C,CAAC,SAAS;AACnE,IAAM,sBAAA,GAAyB,YAAA,CAAa,CAAC,GAAG,sBAAsB,CAAC,CAAA;AASvE,SAAS,eAAA,CAAgB,QAA2B,MAAA,EAA4C;AAC9F,EAAA,OAAO,kBAAA,CAAmB,QAAQ,MAAM,CAAA;AAC1C;AAEA,eAAe,kBAAA,GAA2C;AACxD,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,SAAA,EAAU;AACtC,EAAA,OAAO,YAAY,OAAA,GAAU,WAAA;AAC/B;AAuBA,eAAe,mBAAA,CAAoB,KAAA,EAAe,IAAA,EAAc,GAAA,EAAyC;AACvG,EAAA,WAAA;AACA,EAAA,MAAM,UAAU,MAAM,iBAAA,CAAqC,KAAA,EAAO,IAAI,EAAE,GAAG,CAAA;AAC3E,EAAA,SAAA,CAAU,eAAA,CAAgB,QAAQ,GAAA,CAAI,QAAA,EAAU,QAAQ,IAAA,KAAS,QAAA,IAAY,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAC9F,EAAA,KAAA,MAAW,IAAA,IAAQ,cAAc,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA,EAAG,QAAA,CAAS,GAAG,IAAI,CAAA;AACvE,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,kBAAkB,OAAA,EAA0C;AAC1E,EAAA,MAAM,YAAA,GAAe,OAAO,GAAA,EAAa,WAAA,KAAyD;AAChG,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,EAAE,aAAa,CAAA;AACxD,IAAA,OAAO,iBAAA,CAAkB,MAAA,EAAQ,GAAA,EAAK,WAAA,EAAa;AAAA;AAAA,MAEjD,QAAA,EAAU,QAAQ,UAAA,CAAW;AAAA,KAC9B,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA;AACtB,EAAA,MAAM,QAAA,GAAW,CAAC,GAAA,KAAgB,cAAA,CAAe,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,GAAA,EAAK,CAAA;AAChG,EAAA,IAAI,KAAA,EAAO;AAGT,IAAA,MAAM,CAAC,KAAA,EAAO,IAAI,CAAA,GAAI,SAAS,EAAE,CAAA;AACjC,IAAA,sBAAA,CAAuB,OAAO,IAAA,EAAM,CAAC,QAAQ,YAAA,CAAa,GAAA,EAAK,WAAW,CAAC,CAAA;AAAA,EAC7E;AAOA,EAAA,MAAM,WAAA,GAAc,OAAO,GAAA,EAAa,WAAA,KAAyD;AAM/F,IAAA,IAAI,CAAC,KAAA,IAAS,WAAA,KAAgB,aAAa,OAAO,YAAA,CAAa,KAAK,WAAW,CAAA;AAG/E,IAAA,OAAO,mBAAA,CAAoB,GAAG,QAAA,CAAS,GAAG,CAAC,CAAA;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAM,IAAA,GAAO,OAAO,KAAA,KAAgD;AAClE,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,KAAA,CAAM,MAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,aAAa,IAAI,CAAA;AAC7B,IAAA,IAAI,GAAA,KAAQ,sBAAA,EAAwB,OAAO,QAAA,EAAS;AACpD,IAAA,MAAM,WAAA,GAAc,MAAM,kBAAA,EAAmB;AAE7C,IAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,GAAA,EAAK,WAAW,CAAA;AAClD,IAAA,OAAA,CAAQ,SAAS,OAAA,CAAQ,IAAA,EAAM,EAAE,GAAA,EAAK,aAAa,CAAA;AAEnD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,UAAA;AAEH,QAAA,OAAO,OAAA,CAAQ,YAAY,iBAAA,CAAkB,OAAA,CAAQ,MAAM,CAAA,GAAI,QAAA,CAAS,QAAQ,MAAM,CAAA;AAAA,MACxF,KAAK,WAAA;AACH,QAAA,OAAO,QAAA,EAAS;AAAA,MAClB,KAAK,QAAA,EAAU;AACb,QAAA,MAAM,2BACJC,GAAAA;AAAA,UAAC,cAAA;AAAA,UAAA;AAAA,YACC,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,YAAY,OAAA,CAAQ,UAAA;AAAA,YACpB,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,WAAW,OAAA,CAAQ,SAAA;AAAA,YACnB,UAAU,OAAA,CAAQ,QAAA;AAAA,YAClB,OAAO,OAAA,CAAQ,KAAA;AAAA,YAIf,cAAc,OAAA,CAAQ;AAAA;AAAA,SACxB;AAKF,QAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,EAAM,OAAO,QAAA;AAC1B,QAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,iBAAA,CAAkB,OAAA,CAAQ,QAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAC,CAAA;AAAA,MACvF;AAAA;AACF,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,OAAO,KAAA,KAA+C;AACzE,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,KAAA,CAAM,MAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,aAAa,IAAI,CAAA;AAC7B,IAAA,IAAI,GAAA,KAAQ,sBAAA,EAAwB,OAAO,EAAC;AAC5C,IAAA,MAAM,WAAA,GAAc,MAAM,kBAAA,EAAmB;AAM7C,IAAA,IAAI,KAAA,IAAS,gBAAgB,WAAA,EAAa;AACxC,MAAA,MAAMC,QAAAA,GAAU,MAAM,WAAA,CAAY,GAAA,EAAK,WAAW,CAAA;AAClD,MAAA,IAAIA,QAAAA,CAAQ,IAAA,KAAS,QAAA,EAAU,OAAO,EAAC;AACvC,MAAA,OAAO,OAAA,CAAQ,QAAA,GACX,OAAA,CAAQ,QAAA,CAASA,SAAQ,MAAA,EAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAA,GACrD,eAAA,CAAgBA,QAAAA,CAAQ,MAAA,EAAQA,SAAQ,MAAM,CAAA;AAAA,IACpD;AAEA,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,WAAA,CAAY,EAAE,aAAa,CAAA;AACxD,IAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,CAAkB,MAAA,EAAQ,KAAK,WAAW,CAAA;AAChE,IAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,QAAA,EAAU,OAAO,EAAC;AACvC,IAAA,OAAO,OAAA,CAAQ,QAAA,GACX,OAAA,CAAQ,QAAA,CAAS,QAAQ,MAAA,EAAQ,EAAE,GAAA,EAAK,WAAA,EAAa,CAAA,GACrD,eAAA,CAAgB,OAAA,CAAQ,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAAA,EACpD,CAAA;AAMA,EAAA,MAAM,gBAAA,GAAmB,OACvB,KAAA,EACA,MAAA,KACsB;AACtB,IAAA,MAAM,CAAC,IAAA,EAAM,cAAc,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,YAAA,CAAa,KAAK,CAAA,EAAG,MAAM,CAAC,CAAA;AAC9E,IAAA,OAAO,uBAAA,CAAwB,gBAAgB,IAAI,CAAA;AAAA,EACrD,CAAA;AAEA,EAAA,MAAM,uBAAuB,YAAgD;AAAA,IAC3E,EAAE,IAAA,EAAM,CAAC,GAAG,sBAAsB,CAAA;AAAE,GACtC;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,gBAAA,EAAkB,oBAAA,EAAqB;AACxD;AChQA,SAAS,aAAa,OAAA,EAA2B;AAC/C,EAAA,OAAO,UAAU,OAAO,CAAA,CACrB,MAAA,CAAO,CAAC,YAAY,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAC,EAC3C,GAAA,CAAI,CAAC,YAAY,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC,CAAA;AACtC;AAGA,SAAS,WAAA,CAAY,SAAiB,MAAA,EAA+C;AACnF,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,OAAA,IAAW,SAAA,CAAU,OAAO,CAAA,EAAG;AACxC,IAAA,IAAI,CAAC,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC5B,MAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC,CAAA;AACrC,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AACtD,IAAA,QAAA,CAAS,IAAA,CAAK,kBAAA,CAAmB,KAAK,CAAC,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAChC;AAGA,SAAS,SAAS,OAAA,EAAyB;AACzC,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACnC;AAOO,SAAS,oBAAA,CACd,QACA,OAAA,EACqB;AACrB,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,MAAA,CAAO,UAAU,EAAC;AACnD,EAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AACvB,EAAA,MAAM,UAAiC,EAAC;AACxC,EAAA,MAAM,IAAA,GAAO,CAAC,KAAA,EAAoB,MAAA,KAAyB;AACzD,IAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,KAAA,CAAM,EAAA,IAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,EAAG;AACvE,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,OAAA,EAAS,KAAA,CAAM,IAAI,OAAA,EAAS,KAAA,CAAM,OAAA,EAAS,MAAA,EAAQ,CAAA;AAAA,IACpE;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,OAAA,uBAAc,GAAA,EAA2B;AAC/C,EAAA,MAAM,SAAA,GAAY,CAAC,MAAA,KAAkC;AACnD,IAAA,IAAI,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AAC/B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAA,GAAS,OAAO,MAAA,CAAO,EAAE,WAAA,EAAa,WAAA,EAAa,QAAQ,CAAA;AAC3D,MAAA,OAAA,CAAQ,GAAA,CAAI,QAAQ,MAAM,CAAA;AAAA,IAC5B;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AAgBA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA+C;AACjE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAiC;AAErD,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAI7C,IAAA,MAAM,KAAA,GAAQ,eAAe,KAAK,CAAA;AAClC,IAAA,MAAM,eAAe,KAAA,CAAM,MAAA,GAAS,CAAC,KAAA,CAAM,MAAM,IAAI,OAAA,CAAQ,OAAA;AAE7D,IAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,MAAA,MAAM,MAAA,GAAS,UAAU,MAAM,CAAA;AAE/B,MAAA,IAAI,KAAA;AACJ,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,aAAA,CAAc,KAAA,CAAM,UAAU,CAAA;AACpD,QAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,UAAA,IAAA,CAAK,KAAA,EAAO,CAAA,sBAAA,EAAyB,KAAA,CAAM,UAAU,CAAA,qBAAA,CAAuB,CAAA;AAC5E,UAAA;AAAA,QACF;AAKA,QAAA,MAAM,IAAA,GAAsB,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,UACzD,YAAY,KAAA,CAAM,UAAA;AAAA,UAClB,IAAI,GAAA,CAAI,EAAA;AAAA,UACR,MAAA,EAAQ,EAAE,IAAA,EAAM,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,EAAQ,GAAA,CAAI,QAAQ,CAAA;AAAE,SACrD,CAAE,CAAA;AACF,QAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,CAAC,GAAA,CAAI,EAAA,EAAI,GAAG,CAAC,CAAC,CAAA;AACrD,QAAA,KAAA,GAAQ,EAAC;AACT,QAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AAGtB,UAAA,IAAI,OAAO,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA,KAAM,YAAY,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA,KAAM,EAAA,EAAI;AACzE,UAAA,MAAM,QAAA,GAAW,YAAY,GAAA,EAAK,CAAC,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAC,CAAA;AACtD,UAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAClB,UAAA,KAAA,CAAM,IAAA,CAAK,GAAA,GAAM,CAAC,GAAG,KAAA,CAAM,MAAA,EAAQ,GAAG,QAAA,CAAS,QAAQ,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,QACpE;AAAA,MACF,CAAA,MAAA,IAAW,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG;AAClC,QAAA,KAAA,GAAQ,CAAC,MAAM,OAAO,CAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,MAAM,YAAY,OAAA,CAAQ,MAAA,GAAS,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAQ,CAAA;AAC5D,QAAA,IAAI,aAAa,IAAA,EAAM;AACrB,UAAA,IAAA,CAAK,OAAO,yDAAyD,CAAA;AACrE,UAAA;AAAA,QACF;AACA,QAAA,KAAA,GAAQ,EAAC;AACT,QAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,UAAA,MAAM,IAAA,GAAO,WAAA,CAAY,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAA;AAChD,UAAA,IAAI,SAAS,IAAA,EAAM;AACjB,YAAA,IAAA,CAAK,OAAO,CAAA,6BAAA,EAAgC,UAAA,CAAW,IAAA,CAAK,KAAK,CAAC,CAAA,CAAE,CAAA;AACpE,YAAA;AAAA,UACF;AACA,UAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,QACjB;AAAA,MACF;AAMA,MAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,MAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AAMxB,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,IAAA,EAAM,MAAM,CAAA;AAK9C,QAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAE1B,UAAA,IAAI,MAAA,CAAO,MAAA,KAAW,UAAA,EAAY,UAAA,IAAc,CAAA;AAChD,UAAA;AAAA,QACF;AACA,QAAA,IAAI,MAAA,CAAO,QAAA,GAAW,YAAY,CAAA,KAAM,IAAA,EAAM;AAM9C,QAAA,MAAM,EAAE,OAAA,EAAS,WAAA,KAAgB,aAAA,CAAc,IAAA,EAAM,QAAQ,OAAO,CAAA;AACpE,QAAA,IAAI,CAAC,WAAA,EAAa;AAChB,UAAA,IAAA;AAAA,YACE,KAAA;AAAA,YACA,CAAA,CAAA,EAAI,IAAI,CAAA,WAAA,EAAc,MAAM,CAAA,6EAAA;AAAA,WAC9B;AACA,UAAA;AAAA,QACF;AACA,QAAA,MAAM,QAAA,GAAW,QAAA,CAAS,OAAA,CAAQ,OAAO,CAAA,GAAI,OAAA;AAO7C,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,QAAA,GACjB,CAAA,EAAG,KAAA,CAAM,EAAE,CAAA,EAAG,uBAAuB,CAAA,GAAA,EAAM,uBAAuB,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA,EAAG,uBAAuB,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,EAAE,CAAA,CAAA,GAC9I,CAAA,EAAG,KAAA,CAAM,EAAE,CAAA,EAAG,uBAAuB,CAAA,IAAA,EAAO,uBAAuB,GAAG,IAAI,CAAA,CAAA;AAC9E,QAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA,EAAG,KAAA,CAAM,GAAA,CAAI,QAAA,EAAU,EAAE,KAAA,EAAO,MAAA,EAAQ,CAAA;AAC/D,QAAA,MAAM,YAAY,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA,wBAAS,GAAA,EAAoB;AAChE,QAAA,IAAI,CAAC,UAAU,GAAA,CAAI,MAAM,GAAG,SAAA,CAAU,GAAA,CAAI,QAAQ,QAAQ,CAAA;AAC1D,QAAA,OAAA,CAAQ,GAAA,CAAI,OAAO,SAAS,CAAA;AAAA,MAC9B;AAEA,MAAA,IAAI,SAAS,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,UAAA,KAAe,MAAM,MAAA,EAAQ;AAC5D,QAAA,IAAA;AAAA,UACE,KAAA;AAAA,UACA,yBAAyB,KAAA,CAAM,UAAU,cAAc,KAAA,CAAM,MAAM,uBAAuB,MAAM,CAAA,oCAAA;AAAA,SAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,UAAiC,EAAC;AACxC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,EAAE,KAAA,EAAO,KAAK,KAAA,EAAO;AACpC,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA;AAGnC,IAAA,MAAM,SAAA,GAAY,aAAa,SAAA,CAAU,IAAA,GAAO,IAAI,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA,GAAI,MAAA;AACpF,IAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,GAAA,EAAK,GAAI,SAAA,GAAY,EAAE,UAAA,EAAY,EAAE,SAAA,EAAU,EAAE,GAAI,IAAK,CAAA;AAAA,EAC3E;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAO,EAAE,GAAA,GAAM,CAAA,CAAE,GAAA,GAAM,EAAA,GAAK,CAAA,CAAE,GAAA,GAAM,CAAA,CAAE,GAAA,GAAM,IAAI,CAAE,CAAA;AAEnE,EAAA,OAAO,EAAE,SAAS,OAAA,EAAQ;AAC5B;AAaO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,OAAO,YAAY;AACjB,IAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,YAAY,EAAE,WAAA,EAAa,aAAa,CAAA;AACrE,IAAA,OAAO,oBAAA,CAAqB,MAAA,EAAQ,OAAO,CAAA,CAAE,OAAA;AAAA,EAC/C,CAAA;AACF;AC1RA,eAAsB,yBAAA,CACpB,OAAA,EACA,SAAA,EACA,MAAA,EAC+B;AAC/B,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAC/C,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,yCAAA,EAA0C;AAAA,EACtF;AACA,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,CAAU,WAAW,CAAA,EAAG;AAC3D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,yCAAA,EAA0C;AAAA,EACtF;AACA,EAAA,IAAI,CAAE,MAAM,2BAAA,CAA4B,OAAA,EAAS,SAAA,EAAW,MAAM,CAAA,EAAI;AACpE,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,4BAAA,EAA6B;AAAA,EACzE;AACA,EAAA,MAAM,IAAA,GAAO,qBAAqB,OAAO,CAAA;AACzC,EAAA,IAAI,CAAC,IAAA,CAAK,EAAA,EAAI,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,IAAA,CAAK,OAAA,EAAQ;AACrE,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,KAAK,KAAA,EAAM;AACvC;ACIO,SAAS,sBAAsB,OAAA,EAA0D;AAC9F,EAAA,MAAM,OAAA,GAA+B,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,SAAS,CAAC,GAAG,OAAA,CAAQ,OAAO,CAAA,EAAE;AAC3F,EAAA,OAAO;AAAA,IACL,MAAM,KAAK,OAAA,EAAqC;AAC9C,MAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,IAAA,EAAK;AACnC,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,2BAA2B,CAAA;AACjE,MAAA,MAAM,aAAa,MAAM,yBAAA,CAA0B,OAAA,EAAS,SAAA,EAAW,QAAQ,MAAM,CAAA;AACrF,MAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,QAAA,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,KAAA,EAAO,UAAA,CAAW,OAAA,EAAQ,EAAG,EAAE,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AAAA,MACnF;AACA,MAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,UAAA,CAAW,KAAA,EAAO,OAAO,CAAA;AA2BvD,MAAA,KAAA,MAAW,OAAO,IAAA,EAAM,aAAA,CAAc,KAAK,EAAE,MAAA,EAAQ,GAAG,CAAA;AACxD,MAAA,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,WAAA,EAAa,MAAM,CAAA;AAAA,IAC5C;AAAA,GACF;AACF;ACNA,IAAM,aAAA,GAAgB,EAAA;AAYtB,SAAS,eAAA,CAAgB,OAAe,MAAA,EAAmD;AACzF,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,MAAM,CAAC,CAAA;AACjE,EAAA,OAAO,EAAE,OAAO,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAK,CAAC,GAAG,MAAA,EAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,MAAM,MAAA,GAAS,KAAK,CAAC,CAAA,EAAE;AAC1G;AAsBO,SAAS,cAAA,CAAe,IAAA,EAAc,KAAA,EAAe,MAAA,EAAwB;AAClF,EAAA,MAAM,EAAE,OAAO,KAAA,EAAO,MAAA,EAAQ,OAAM,GAAI,eAAA,CAAgB,OAAO,MAAM,CAAA;AACrE,EAAA,MAAM,GAAA,GAAM,wDAAwD,KAAA,GAAQ,EAAE,IAAI,KAAA,GAAQ,EAAE,kbAAkb,IAAI,CAAA,SAAA,CAAA;AAClhB,EAAA,OAAO,CAAA,0BAAA,EAA6B,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAC/C;AAcO,SAAS,YAAY,KAAA,EAAoC;AAC9D,EAAA,MAAM,EAAE,OAAO,KAAA,EAAO,QAAA,EAAU,SAAS,SAAA,EAAW,MAAA,EAAQ,SAAQ,GAAI,KAAA;AACxE,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,GAAA,CAAI,MAAA,KAAW,GAAG,OAAO,IAAA;AAC7C,EAAA,MAAM,GAAA,GAAM,MAAM,GAAA,IAAO,EAAA;AAIzB,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,UAAA,GACzB,CAAA,EAAG,KAAA,CAAM,UAAA,CAAW,CAAA,GAAI,GAAG,CAAA,EAAA,EAAK,KAAA,CAAM,UAAA,CAAW,CAAA,GAAI,GAAG,CAAA,CAAA,CAAA,GACxD,MAAA;AACJ,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,IAAA,IAAQ,KAAA,CAAM,WAAW,IAAA,EAAM;AAKjD,IAAA,uBACED,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,eAAA,EAAc,UAAA;AAAA,QACd,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,GAAA;AAAA,QACA,SAAA;AAAA,QACC,GAAI,iBAAiB,EAAE,KAAA,EAAO,EAAE,cAAA,EAAe,KAAM;AAAC;AAAA,KACzD;AAAA,EAEJ;AAQA,EAAA,MAAM,cACJ,KAAA,CAAM,IAAA,IAAQ,OACV,IAAA,GACA,KAAA,CAAM,KAAK,UAAA,CAAW,gBAAgB,CAAA,GACpC,KAAA,CAAM,OACN,cAAA,CAAe,KAAA,CAAM,MAAM,KAAA,CAAM,KAAA,EAAO,MAAM,MAAM,CAAA;AAC5D,EAAA,MAAM,OAAO,WAAA,GAAc,EAAE,aAAa,MAAA,EAAiB,WAAA,KAAgB,EAAC;AAI5E,EAAA,MAAM,WAAA,GAAc,QAAA,IAAY,OAAA,KAAY,MAAA,GAAS,MAAA,GAAY,OAAA;AACjE,EAAA,uBACEA,GAAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,eAAA,EAAc,MAAA;AAAA,MACd,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,GAAA;AAAA,MACA,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,QAAQ,KAAA,CAAM,MAAA;AAAA,MACb,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,MACvC,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA,MAC7C,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,OAAA,EAAS,WAAA,KAAgB,EAAC;AAAA,MAC5D,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,MAC3C,GAAI,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MAC/C,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA,MACzC,GAAG,IAAA;AAAA,MACH,GAAI,iBAAiB,EAAE,KAAA,EAAO,EAAE,cAAA,EAAe,KAAM;AAAC;AAAA,GACzD;AAEJ;AAqBO,SAAS,sBAAA,CAAuB,OAAA,GAAmC,EAAC,EAAgB;AACzF,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,GAAA;AACzC,EAAA,MAAM,YAAA,GAAe,QAAQ,YAAA,IAAgB,GAAA;AAC7C,EAAA,MAAM,cAAA,GAAiB,QAAQ,cAAA,IAAkB,EAAA;AACjD,EAAA,OAAO,CAAC,EAAE,GAAA,EAAK,KAAA,EAAO,SAAQ,KAAM;AAClC,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC5C,IAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,SAAS,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,YAAY,CAAA,CAAA,EAAI,OAAA,IAAW,cAAc,CAAA,CAAA;AAAA,EAC9F,CAAA;AACF;AAqCA,SAAS,gBAAgB,OAAA,EAA2B;AAClD,EAAA,OAAO,SAAS,gBAAgB,KAAA,EAA2C;AACzE,IAAA,MAAM,EAAE,KAAK,KAAA,EAAO,MAAA,EAAQ,KAAK,UAAA,EAAY,IAAA,EAAK,GAAI,oBAAA,CAAqB,KAAK,CAAA;AAChF,IAAA,IAAI,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAC7B,IAAA,uBACEA,GAAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,EAAE,GAAA,EAAK,KAAA,EAAO,KAAA,IAAS,IAAA,EAAM,MAAA,EAAQ,MAAA,IAAU,IAAA,EAAM,GAAA,EAAK,UAAA,EAAY,IAAA,EAAK;AAAA,QACjF,GAAI,MAAM,QAAA,KAAa,IAAA,GAAO,EAAE,QAAA,EAAU,IAAA,KAAS,EAAC;AAAA,QACpD,GAAI,QAAQ,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,EAAC;AAAA,QACjE,GAAI,QAAQ,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,GAAI,EAAC;AAAA,QAC9D,GAAI,QAAQ,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI;AAAC;AAAA,KACvE;AAAA,EAEJ,CAAA;AACF;AA4BO,SAAS,aAAA,CACd,UAAA,EACA,OAAA,GAA4B,EAAC,EACP;AACtB,EAAA,MAAM,IAAA,GAAO,CAAC,GAAI,UAAA,IAAc,EAAG,CAAA;AACnC,EAAA,MAAM,QAAQE,kBAAAA,CAAmB,IAAI,CAAA,CAAE,QAAA,CAAS,cAAc,OAAO,CAAA;AACrE,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,cAAA,GAAiB,gBAAgB,OAAO,CAAA;AAC9C,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,KAAA,KAAW,KAAA,CAAM,QAAA,KAAa,KAAA,GAAQ,EAAE,QAAA,EAAU,KAAA,CAAM,QAAA,EAAU,cAAA,KAAmB,KAAM,CAAA;AAC9G;AA0BO,SAAS,eAAA,CAAgB,OAAA,GAAkC,EAAC,EAAsB;AAIvF,EAAA,OAAO,CAAC,EAAE,KAAA,EAAO,GAAA,EAAK,WAAW,QAAA,EAAU,KAAA,EAAO,OAAA,EAAQ,qBACxDF,GAAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,EAAE,GAAG,KAAA,EAAO,GAAA,EAAI;AAAA,MACtB,GAAI,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MAC/C,GAAI,QAAA,GAAW,EAAE,QAAA,EAAU,IAAA,KAAS,EAAC;AAAA,MACrC,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,MACvC,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,MAC3C,GAAI,QAAQ,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,EAAC;AAAA,MACjE,GAAI,QAAQ,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI;AAAC;AAAA,GACvE;AAEJ;;;ACpUO,SAAS,mBAAA,CACd,QAAA,EACA,QAAA,EACA,YAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,QAAA,EAAU,YAAY,EAAE,CAAC,CAAA;AAC1D,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,OAAO,SAAS,WAAA,CAAY,KAAA,CAAM,WAAW,CAAA,EAAG,cAAc,SAAA,KAAc,IAAA;AAC9E;;;ACpBO,IAAM,oBAAA,GAAuB;AAAA,EAClC,SAAA,EAAW,mBAAA;AAAA,EACX,SAAA,EAAW,mBAAA;AAAA,EACX,UAAA,EAAY,qBAAA;AAAA,EACZ,gBAAA,EAAkB;AACpB;AASA,SAAS,QAAQ,KAAA,EAA+C;AAC9D,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,EAAK;AAC5B,EAAA,OAAO,OAAA,KAAY,MAAA,IAAa,OAAA,CAAQ,MAAA,GAAS,IAAI,OAAA,GAAU,MAAA;AACjE;AAQO,SAAS,eAAe,GAAA,EAAwD;AACrF,EAAA,MAAM,WAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,oBAAoB,CAAA,EAAG;AAC9D,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AAC/B,IAAA,IAAI,KAAA,KAAU,MAAA,EAAW,QAAA,CAAS,GAA0B,CAAA,GAAI,KAAA;AAAA,EAClE;AACA,EAAA,OAAO,QAAA;AACT;ACoBA,eAAe,cAAA,CACb,SAAA,EACA,SAAA,EACA,IAAA,EACA,IAAA,EACkB;AAClB,EAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,CAAA,EAAG,UAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,UAAA,CAAA,EAAc;AAAA,IAC5E,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,IAC9C,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,MAAM,IAAA,EAAM,MAAA,EAAQ,QAAQ,CAAA;AAAA,IACnD,KAAA,EAAO;AAAA,GACR,CAAA;AACD,EAAA,MAAM,OAAQ,MAAM,QAAA,CAAS,MAAK,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAGzD,EAAA,IAAI,CAAC,QAAA,CAAS,EAAA,IAAM,IAAA,EAAM,WAAW,SAAA,EAAW;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,cAAA,EAAiB,IAAI,CAAA,UAAA,EAAa,QAAA,CAAS,MAAM,MAC/C,IAAA,EAAM,YAAA,IAAgB,IAAA,EAAM,OAAA,IAAW,kBACzC,CAAA;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,IAAA,CAAK,KAAA;AACd;AAGA,SAAS,eAAe,IAAA,EAAgC;AACtD,EAAA,MAAM,YAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,SAAA,CAAU,GAAG,CAAA;AAC3C,IAAA,IAAI,MAAA,CAAO,OAAA,EAAS,SAAA,CAAU,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,SAAA;AACT;AAGA,SAAS,eAAe,IAAA,EAAmC;AACzD,EAAA,MAAM,YAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,MAAA,GAAS,oBAAA,CAAqB,SAAA,CAAU,GAAG,CAAA;AACjD,IAAA,IAAI,MAAA,CAAO,OAAA,EAAS,SAAA,CAAU,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,SAAA;AACT;AAgBO,SAAS,kBAAA,CACd,OAAA,EACA,OAAA,EACA,WAAA,EACe;AAGf,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,MAAM,SAAA,GAAY,cAAA,CAAe,OAAA,CAAQ,SAAS,CAAA;AAIlD,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,CAAC,GAAG,OAAA,CAAQ,MAAM,CAAA,EAAG,kBAAA,CAAmB,SAAA,EAAW,OAAO,CAAC,CAAA;AAc5F,EAAA,MAAM,kBACJ,WAAA,KAAgB,WAAA,GACZ,SAAA,CAAU,GAAA,CAAI,CAAC,GAAA,MAAS,EAAE,KAAA,EAAO,GAAA,EAAK,WAAW,GAAA,EAAK,QAAA,EAAU,EAAC,GAAI,CAAA,GACrE,SAAA;AAEN,EAAA,OAAO,yBAAA,CAA0B;AAAA,IAC/B,WAAW,OAAA,CAAQ,SAAA;AAAA,IACnB,OAAA;AAAA,IACA,MAAA;AAAA;AAAA;AAAA;AAAA,IAIA,SAAA,EAAW,cAAA,CAAe,OAAA,CAAQ,SAAA,IAAa,EAAE,CAAA;AAAA,IACjD,WAAA,EAAa,CAAC,GAAG,OAAA,CAAQ,WAAW,CAAA;AAAA,IACpC,SAAA,EAAW,eAAA;AAAA,IACX,QAAQ,OAAA,CAAQ;AAAA,GACjB,CAAA;AACH;AAGA,eAAsB,eAAA,CACpB,SACA,WAAA,EACwB;AACxB,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAC9C,EAAA,MAAM,IAAA,GAAgC,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAU;AAErE,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,gBAAgB,OAAA,EAAS;AAC3B,IAAA,IAAI,CAAC,QAAQ,UAAA,EAAY;AAGvB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,GAAW,MAAM,cAAA,CAAe,SAAA,EAAW,OAAA,CAAQ,WAAW,wBAAA,EAA0B;AAAA,MACtF,GAAG,IAAA;AAAA,MACH,OAAO,OAAA,CAAQ;AAAA,KAChB,CAAA;AAAA,EACH,CAAA,MAAO;AACL,IAAA,OAAA,GAAW,MAAM,cAAA;AAAA,MACf,SAAA;AAAA,MACA,OAAA,CAAQ,SAAA;AAAA,MACR,mBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,kBAAA,CAAmB,OAAA,EAAS,OAAA,EAAS,WAAW,CAAA;AACzD;;;ACjHA,IAAM,oBAAA,GAAuB,CAAC,OAAA,EAAS,OAAO,CAAA;AAE9C,SAAS,OAAO,KAAA,EAAyC;AACvD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GACrE,KAAA,GACD,EAAC;AACP;AAEA,SAASG,KAAI,KAAA,EAAwB;AACnC,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAC7C;AAEA,SAAS,MAAM,KAAA,EAA2C;AACxD,EAAA,OAAO,KAAA,CAAM,QAAQ,KAAK,CAAA,GAAI,MAAM,GAAA,CAAI,MAAM,IAAI,EAAC;AACrD;AAOO,SAAS,cAAA,CACd,MAAA,EACA,OAAA,GAAiC,EAAC,EACrB;AACb,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,WAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,EAAC;AAC5C,EAAA,MAAM,kBAAA,GAAqB,YAAY,kBAAA,IAAsB,UAAA;AAC7D,EAAA,MAAM,oBAAA,GAAuB,YAAY,oBAAA,IAAwB,YAAA;AACjE,EAAA,MAAM,WAAA,GAAc,YAAY,qBAAA,IAAyB,oBAAA;AAEzD,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,EAAE,aAAa,GAAI,OAAA,CAAQ,MAAA,GAAS,EAAE,QAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,IAAK,CAAA;AACnG,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,IAAS,IAAK,EAAC;AACrC,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,EAAA,EAAI,KAAK,CAAU,CAAC,CAAA;AAE3E,EAAA,MAAM,QAAA,GAAW,CAAC,UAAA,KAA2C;AAC3D,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,aAAA,CAAc,UAAU,CAAA;AAC9C,IAAA,OAAO,MAAA,CAAO,EAAA,GAAK,MAAA,CAAO,SAAA,GAAY,EAAC;AAAA,EACzC,CAAA;AAGA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,kBAAkB,CAAA,CAAE,CAAC,CAAA,IAAK,IAAA;AAIpD,EAAA,MAAM,OAAA,GAAU,SAAS,oBAAoB,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAa,CAAC,IAAA,KAClB,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAA,KAAS,GAAA,CAAgC,KAAA,KAAU,IAAI,CAAA,IAAK,IAAA;AAG5E,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAuC;AACvD,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACjC,MAAA,IAAI,KAAA,SAAc,KAAA,CAAM,GAAA;AAExB,MAAA,OAAO,KAAA,CAAM,WAAW,MAAM,CAAA,IAAK,MAAM,UAAA,CAAW,GAAG,IAAI,KAAA,GAAQ,MAAA;AAAA,IACrE;AACA,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,KAAK,CAAA,CAAE,GAAA;AAC1B,IAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,GAAS,IAAI,GAAA,GAAM,MAAA;AAAA,EAC3D,CAAA;AAGA,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAuC;AACpD,IAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,IAAA,OAAO,OAAO,GAAA,CAAI,UAAA,KAAe,QAAA,IAAY,OAAO,GAAA,CAAI,EAAA,KAAO,QAAA,GAC3D,EAAE,YAAY,GAAA,CAAI,UAAA,EAAY,EAAA,EAAI,GAAA,CAAI,IAAG,GACzC,IAAA;AAAA,EACN,CAAA;AAGA,EAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAAwC;AAC3D,IAAA,MAAM,IAAA,GAAO,OAAO,KAAK,CAAA;AACzB,IAAA,MAAM,YAAA,GAAe,KAAK,YAAA,KAAiB,IAAA;AAC3C,IAAA,MAAM,OAAOA,IAAAA,CAAI,IAAA,CAAK,IAAI,CAAA,IAAKA,IAAAA,CAAI,KAAK,GAAG,CAAA;AAC3C,IAAA,MAAM,WAAWA,IAAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,KAAM,OAAO,MAAA,GAAS,EAAA,CAAA;AAExD,IAAA,IAAI,QAAA,KAAa,MAAA,IAAU,QAAA,KAAa,UAAA,EAAY;AAClD,MAAA,OAAO,IAAA,GAAO,EAAE,IAAA,EAAM,YAAA,EAAa,GAAI,IAAA;AAAA,IACzC;AACA,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AAC9B,MAAA,OAAO,GAAA,GAAM,EAAE,IAAA,EAAM,GAAA,EAAK,cAAa,GAAI,IAAA;AAAA,IAC7C;AACA,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAGlB,MAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,MAAM,IAAA,GAAO,MAAA,CAAO,WAAA,CAAY,GAAG,CAAA;AACnC,QAAA,IAAI,SAAS,IAAA,EAAM,OAAO,EAAE,IAAA,EAAM,MAAM,YAAA,EAAa;AAAA,MACvD;AAEA,MAAA,MAAM,MAAA,GAAS,OAAO,IAAI,CAAA;AAC1B,MAAA,IAAI,OAAO,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAClE,QAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,YAAA,EAAa;AAAA,MAC3C;AACA,MAAA,MAAM,IAAA,GAAOA,IAAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AAC5B,MAAA,IAAI,MAAM,OAAO,EAAE,MAAM,CAAA,CAAA,EAAI,IAAI,IAAI,YAAA,EAAa;AAClD,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA2D;AAC5E,IAAA,MAAM,KAAA,GAAQA,IAAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAG7B,IAAA,MAAM,WAAW,WAAA,CAAY,MAAA,IAAU,KAAA,GAAQ,KAAA,CAAM,OAAO,KAAK,CAAA;AACjE,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,QAAA,EAAU,OAAO,IAAA;AAChC,IAAA,MAAM,WAAA,GAAcA,IAAAA,CAAI,KAAA,CAAM,WAAW,CAAA;AACzC,IAAA,OAAO,EAAE,KAAA,EAAO,GAAG,QAAA,EAAU,GAAI,cAAc,EAAE,WAAA,EAAY,GAAI,EAAC,EAAG;AAAA,EACvE,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAA4D;AAC9E,IAAA,MAAM,KAAA,GAAQA,IAAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,IAAA,MAAM,WAAW,WAAA,CAAY,MAAA,IAAU,KAAA,GAAQ,KAAA,CAAM,OAAO,KAAK,CAAA;AACjE,IAAA,MAAM,WAAA,GAAcA,IAAAA,CAAI,KAAA,CAAM,WAAW,CAAA;AACzC,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,QAAQ,CAAA,CAClC,GAAA,CAAI,SAAS,CAAA,CACb,MAAA,CAAO,CAAC,KAAA,KAAoC,KAAA,KAAU,IAAI,CAAA;AAG7D,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,MAAA,KAAW,GAAG,OAAO,IAAA;AAC/C,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,GAAI,QAAA,GAAW,EAAE,MAAM,QAAA,CAAS,IAAA,KAAS,EAAC;AAAA,MAC1C,YAAA,EAAc,UAAU,YAAA,IAAgB,KAAA;AAAA,MACxC,GAAI,WAAA,GAAc,EAAE,WAAA,KAAgB,EAAC;AAAA,MACrC;AAAA,KACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,iBAAA,GAAoB,CAAC,IAAA,KAAqC;AAC9D,IAAA,MAAM,GAAA,GAAM,WAAW,IAAI,CAAA;AAC3B,IAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAC;AAClB,IAAA,MAAM,MAAA,GAAS,GAAA;AACf,IAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,CAAC,IAAA,KAAS,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAC,CAAC,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA,CACvB,GAAA,CAAI,UAAU,CAAA,CACd,MAAA,CAAO,CAAC,KAAA,KAAqC,KAAA,KAAU,IAAI,CAAA;AAAA,EAChE,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,UAAA;AAAA,IACA,iBAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AACF;;;AC3OA,IAAM,iBAAA,GAAoB,iCAAA;AAG1B,SAAS,gBAAgB,KAAA,EAA2B;AAClD,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,IAAI,GAAA,CAAI,KAAA,EAAO,iBAAiB,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,QAAA,CAAS,MAAA,KAAW,iBAAA,GAAoB,QAAA,GAAW,IAAA;AAC5D;AAGA,SAAS,oBAAoB,KAAA,EAAwB;AACnD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC/B,IAAA,IAAI,IAAA,IAAQ,EAAA,IAAQ,IAAA,KAAS,GAAA,EAAM,OAAO,IAAA;AAAA,EAC5C;AACA,EAAA,OAAO,KAAA;AACT;AAuBO,SAAS,kBAAA,CACd,KAAA,EACA,QAAA,GAAW,GAAA,EACI;AACf,EAAA,IAAI,UAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAI,OAAO,QAAA;AAClE,EAAA,IAAI,CAAC,MAAM,UAAA,CAAW,GAAG,KAAK,mBAAA,CAAoB,KAAK,GAAG,OAAO,IAAA;AACjE,EAAA,MAAM,QAAA,GAAW,gBAAgB,KAAK,CAAA;AACtC,EAAA,IAAI,QAAA,KAAa,MAAM,OAAO,IAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,QAAA,GAAW,QAAA,CAAS,SAAS,QAAA,CAAS,IAAA;AAC9D,EAAA,OAAO,eAAA,CAAgB,MAAM,CAAA,KAAM,IAAA,GAAO,IAAA,GAAO,MAAA;AACnD;AAWO,SAAS,sBAAA,CACd,YACA,aAAA,EACmB;AACnB,EAAA,IAAI,aAAA,KAAkB,MAAA,IAAa,aAAA,CAAc,MAAA,KAAW,CAAA,EAAG;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,kCAAA,EAAmC;AAAA,EAC/E;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,OAAO,UAAA,KAAe,QAAA,GAAW,IAAI,GAAA,CAAI,UAAA,EAAY,kBAAkB,CAAA,GAAI,UAAA;AAAA,EACnF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,gCAAA,EAAiC;AAAA,EAC7E;AAEA,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAC1C,EAAA,IAAI,UAAU,aAAA,EAAe;AAC3B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,wBAAA,EAAyB;AAAA,EACrE;AAEA,EAAA,MAAM,aAAa,kBAAA,CAAmB,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAU,CAAC,CAAA;AACtE,EAAA,IAAI,eAAe,IAAA,EAAM;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,SAAS,mDAAA,EAAoD;AAAA,EAChG;AAEA,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,UAAA,EAAW;AAChC;AASO,SAAS,qBAAqB,UAAA,EAAkC;AACrE,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,OAAO,UAAA,KAAe,QAAA,GAAW,IAAI,GAAA,CAAI,UAAA,EAAY,kBAAkB,CAAA,GAAI,UAAA;AAAA,EACnF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,mBAAmB,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAU,CAAC,CAAA,IAAK,GAAA;AACjE;;;ACxFO,SAAS,mBAAmB,OAAA,EAAoD;AACrF,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,OAAA,EAAqC;AAC7C,MAAA,MAAM,UAAA,GAAa,sBAAA,CAAuB,OAAA,CAAQ,GAAA,EAAK,QAAQ,KAAK,CAAA;AACpE,MAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,QAAA,OAAO,IAAI,SAAS,UAAA,CAAW,OAAA,EAAS,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA;AAAA,MACvE;AACA,MAAA,MAAM,KAAA,GAAQ,MAAMC,SAAAA,EAAU;AAC9B,MAAA,KAAA,CAAM,MAAA,EAAO;AACb,MAAAC,QAAAA,CAAS,WAAW,UAAU,CAAA;AAAA,IAChC;AAAA,GACF;AACF;AASO,SAAS,yBAAA,GAAkD;AAChE,EAAA,OAAO;AAAA,IACL,MAAM,IAAI,OAAA,EAAqC;AAC7C,MAAA,MAAM,KAAA,GAAQ,MAAMD,SAAAA,EAAU;AAC9B,MAAA,KAAA,CAAM,OAAA,EAAQ;AACd,MAAAC,QAAAA,CAAS,oBAAA,CAAqB,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,IAC5C;AAAA,GACF;AACF;;;AC4PA,SAAS,iBAAA,CACP,QACA,WAAA,EACe;AACf,EAAA,OAAO,OAAO,MAAA,KAAW,UAAA,GAAa,MAAA,CAAO,CAAC,GAAG,WAAW,CAAC,CAAA,GAAI,CAAC,GAAG,MAAM,CAAA;AAC7E;AAGO,SAAS,iBACd,OAAA,EACyB;AACzB,EAAA,MAAM,GAAA,GAAM,cAAA,CAAe,OAAA,CAAQ,GAAA,IAAO,QAAQ,GAAG,CAAA;AACrD,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,OAAA,CAAQ,MAAA,EAAQ,QAAQ,WAAW,CAAA;AACpE,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,SAAA,KAAc,MAAA,IAAa,IAAI,SAAA,KAAc,MAAA;AAQ9D,EAAA,IAAI,CAAC,IAAA,KAAS,GAAA,CAAI,cAAc,MAAA,IAAa,GAAA,CAAI,cAAc,MAAA,CAAA,EAAY;AACzE,IAAA,MAAM,CAAC,OAAA,EAAS,OAAO,CAAA,GACrB,GAAA,CAAI,cAAc,MAAA,GACd,CAAC,oBAAA,CAAqB,SAAA,EAAW,qBAAqB,SAAS,CAAA,GAC/D,CAAC,oBAAA,CAAqB,SAAA,EAAW,qBAAqB,SAAS,CAAA;AACrE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,yBAAyB,OAAO,CAAA,YAAA,EAAe,OAAO,CAAA,oHAAA,EAE7C,OAAO,yBAAyB,OAAO,CAAA,2CAAA;AAAA,KAClD;AAAA,EACF;AAEA,EAAA,MAAM,WAAA,GAAiC,IAAA,GACnC,MAAA,GACA,OAAA,CAAQ,kBACN,UAAA,GACA,OAAA;AACN,EAAA,MAAM,YAAA,GAAe,IAAA,GAAQ,GAAA,CAAI,SAAA,GAAuB,OAAA,CAAQ,IAAA;AAOhE,EAAA,MAAM,UAAA,GAAa,oBAAA,CAAqB,OAAA,CAAQ,UAAA,IAAc,EAAE,CAAA;AAIhE,EAAA,MAAM,cAA6B,yBAAA,CAA0B;AAAA,IAC3D,SAAA,EAAW,YAAA;AAAA,IACX,SAAS,OAAA,CAAQ,OAAA;AAAA,IACjB,MAAA;AAAA;AAAA,IAEA,WAAW,EAAC;AAAA,IACZ,WAAA,EAAa,CAAC,GAAG,OAAA,CAAQ,WAAW,CAAA;AAAA,IACpC,WAAW,EAAC;AAAA,IACZ,QAAQ;AAAC,GACV,CAAA;AAED,EAAA,MAAM,WAAA,GAAc,CAAC,OAAA,KAA0D;AAC7E,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,OAAO,eAAA;AAAA,QACL;AAAA,UACE,WAAW,GAAA,CAAI,SAAA;AAAA,UACf,WAAW,GAAA,CAAI,SAAA;AAAA,UACf,SAAS,OAAA,CAAQ,OAAA;AAAA,UACjB,aAAa,OAAA,CAAQ,WAAA;AAAA,UACrB,MAAA;AAAA,UACA,GAAI,IAAI,UAAA,GAAa,EAAE,YAAY,GAAA,CAAI,UAAA,KAAe,EAAC;AAAA,UACvD,GAAI,QAAQ,KAAA,GAAQ,EAAE,OAAO,OAAA,CAAQ,KAAA,KAAU;AAAC,SAClD;AAAA,QACA,OAAA,CAAQ;AAAA,OACV;AAAA,IACF;AACA,IAAA,IAAI,OAAA,CAAQ,eAAA,EAAiB,OAAO,OAAA,CAAQ,gBAAgB,OAAO,CAAA;AACnE,IAAA,OAAO,OAAA,CAAQ,QAAQ,WAAW,CAAA;AAAA,EACpC,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,OAAO,cAAA,GAAqC,EAAC,KAA4B;AAC3F,IAAA,MAAM,WAAA,GAAc,eAAe,WAAA,IAAe,WAAA;AAClD,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,EAAE,aAAa,CAAA;AAChD,IAAA,OAAO,eAAe,MAAA,EAAQ;AAAA,MAC5B,WAAA;AAAA,MACA,GAAI,QAAQ,OAAA,GAAU,EAAE,aAAa,OAAA,CAAQ,OAAA,KAAY;AAAC,KAC3D,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,YAAA,GAAuC,EAAC,KAAmB;AAC9E,IAAA,MAAM,EAAE,KAAA,EAAO,GAAG,IAAA,EAAK,GAAI,YAAA;AAC3B,IAAA,OAAO,iBAAA,CAAkB;AAAA,MACvB,WAAA;AAAA,MACA,UAAA;AAAA;AAAA;AAAA;AAAA,MAIA,GAAI,KAAA,KAAU,KAAA,GAAQ,EAAC,GAAI,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,YAAA,EAAa,EAAE;AAAA,MAC/E,GAAG;AAAA,KACJ,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,eAAA,GAAkB,CACtB,SAAA,GAA6D,OAE7D,qBAAA,CAAsB;AAAA;AAAA;AAAA,IAGpB,MAAA,EAAQ,QAAA,IAAY,SAAA,GAAY,SAAA,CAAU,SAAS,GAAA,CAAI,gBAAA;AAAA;AAAA;AAAA;AAAA,IAIvD,KAAA,EAAO,YAAA;AAAA,IACP,OAAA,EAAS,QAAQ,OAAA,CAAQ;AAAA,GAC1B,CAAA;AAIH,EAAA,MAAM,eAAe,MAA4B,kBAAA,CAAmB,EAAE,KAAA,EAAO,GAAA,CAAI,YAAY,CAAA;AAC7F,EAAA,MAAM,mBAAA,GAAsB,MAA4B,yBAAA,EAA0B;AAElF,EAAA,MAAM,kBAAA,GAAqB,OAAO,IAAA,KAA0D;AAC1F,IAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAMD,SAAAA,EAAU;AACtC,IAAA,MAAM,WAAA,GAA2B,YAAY,OAAA,GAAU,WAAA;AACvD,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,EAAE,aAAa,CAAA;AAChD,IAAA,MAAM,EAAE,QAAQ,IAAA,EAAK,GAAI,gBAAgB,YAAA,CAAa,IAAI,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA;AAC3E,IAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,EAAE,WAAA,EAAa,QAAQ,CAAA;AACpD,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA;AACxC,IAAA,OAAO,mBAAA,CAAoB,QAAA,CAAS,QAAA,EAAU,UAAA,CAAW,QAAQ,CAAA;AAAA,EACnE,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,YAAA;AAAA,IACA,WAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,GAAI,QAAQ,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,EAAa,OAAA,CAAQ,WAAA,EAAY,GAAI,EAAC;AAAA,IAChF,WAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACF;AACF;ACrcO,IAAM,OAAA,GAAU","file":"index.js","sourcesContent":["import type { ComponentType } from 'react'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport { registerComponents } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations } from '@elytracms/core/runtime-renderer'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\n\n/**\n * One host-repo component: the manifest the builder edits against plus the\n * real React implementation that renders it (vision AD-2 — project components\n * are real code in the host repo, never generated).\n *\n * This is the STRICT shape — the one a hand-authored component module should\n * export (`export const heroComponent: HostComponent = …`), so the compiler\n * checks the implementation where it is written. It is assignable to the\n * config's {@link HostComponentEntry}, which is what {@link defineHostComponents}\n * accepts, so both hand-built lists and the config's own list register through\n * the one signature (EC-541).\n */\nexport interface HostComponent {\n manifest: ComponentManifest\n implementation: ComponentType<Record<string, unknown>>\n}\n\n/**\n * The registered component surface a host app hands to `<CanvasRenderer />`\n * and the catch-all route helper: a manifest registry plus the implementation\n * map. Registration problems — duplicate ids, invalid ids, role claims, and\n * `invalid-implementation` for an entry whose `implementation` is not a React\n * component — are structured issues in `registry.issues`, the ONE record every\n * surface reads (EC-591; never thrown — an unknown component renders the EC-015\n * fallback). The studio registers the same config list through the same core\n * step, so it shows exactly these issues.\n */\nexport interface HostComponents {\n registry: ComponentRegistry\n implementations: ComponentImplementations\n}\n\n/**\n * Component registration for the embedded runtime (EC-144, EC-591): the host\n * registers EXACTLY the list it is given — the config's `components` — through\n * core's one registration step `registerComponents`, the same step the studio\n * registry and the preview harness call. The engine adds nothing: there are no\n * base primitives and no override layer (EC-591 deleted `includeBasePrimitives`).\n * The components Elytra ships are an opt-in package listed in the config like any\n * other (`components: [...elytraComponents, ...siteComponents]`).\n *\n * One precedence rule: a duplicate id is refused WHOLE (`duplicate-id`, the first\n * entry kept), so a manifest never ships with another entry's implementation.\n * To replace a shipped component, list yours under the id and leave the shipped\n * entry out; to swap only the Image implementation for `next/image`, wrap the\n * list in {@link withNextImage}.\n *\n * Accepts the CONFIG's entry shape (`implementation: unknown`) rather than the\n * strict {@link HostComponent}: the config declares implementations opaquely,\n * and EC-539 made `createElytraHost({ ...site })` the contract, so a\n * compile-time check at this seam was already gone for the recommended path.\n * The check is RUNTIME instead (EC-541): a value that is not a component is\n * recorded as an `invalid-implementation` issue naming the component id and\n * the received kind, and is NOT put into `implementations` — so the renderer\n * takes its missing-component path (a visible EC-015 fallback) instead of\n * crashing the server render on `createElement` (\"Element type is invalid\")\n * or, for a string, silently rendering an HTML tag. The manifest still\n * registers, so the entry shows as broken rather than vanishing.\n *\n * One consequence of the `unknown` parameter: an INLINE\n * `implementation: (props) => …` literal no longer gets `props` contextually\n * typed (it is `noImplicitAny` under strict). Annotate the parameter, or\n * author the entry as a {@link HostComponent} — which is the documented shape\n * anyway. (An overload pair was tried and does not restore it: TypeScript\n * resolves the context-sensitive literal against the wider overload.)\n */\nexport function defineHostComponents(components: readonly HostComponentEntry[] = []): HostComponents {\n const { registry, implementations } = registerComponents(components)\n return { registry, implementations }\n}\n","import { Fragment, isValidElement } from 'react'\nimport type { ReactNode } from 'react'\nimport type { ContentQueryError, ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { isBlockSequenceValueShape } from '@elytracms/core/rich-text'\nimport {\n RenderErrorBoundary,\n documentBodyCompositionNodes,\n documentBodyItems,\n nodeListingOf,\n renderDocumentBodyItems,\n serializeListingQuery,\n} from '@elytracms/core/runtime-renderer'\nimport type {\n DocumentBodyItem,\n RelationTarget,\n RenderAsset,\n RenderContext,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { HostComponents } from './components'\n\n/**\n * `<CanvasRenderer />` (EC-144, vision AD-1): an async React Server Component\n * that renders a resolved page (from `resolvePage`, EC-143) through\n * `@elytracms/core/runtime-renderer` with the host app's component registry. EC-015\n * fallback behavior is identical to the builder: a missing component, invalid\n * prop, or missing required slot renders a visible fallback — a page never\n * crashes and never silently omits a node.\n */\nexport interface CanvasRendererProps {\n /** The `resolvePage` result for the current URL. */\n result: ResolvePageResult\n /** Host component surface from `defineHostComponents`. */\n components: HostComponents\n /**\n * Delivery-shaped assets the page may reference (EC-195, from the render\n * outcome). An `asset`-typed Image prop resolves through these to a url +\n * intrinsic dimensions; an unknown id degrades to the visible fallback.\n */\n assets?: readonly ResolvedAsset[]\n /**\n * Populated composition-node `relation` props, keyed `collection:id` (EC-254,\n * from the render outcome). A block's `relation` prop (e.g. a ProductGrid's\n * picked products) resolves through these to the populated target; an unknown\n * key degrades to the block's own empty state. Absent → relation props pass\n * through unresolved (raw ref), the same degradation as a missing asset list.\n */\n relations?: Record<string, RelationTarget>\n /**\n * Documents each `listing` block resolved to, keyed by serialized query (EC-255,\n * from the render outcome). A ProductGrid keyed to a picked `category` reads its\n * products through this; an unknown key (or absent map) degrades to the block's\n * own empty state — the same degradation as a missing relation. A query that\n * could not run is baked as its errors (EC-581 6a) and shown above the block.\n */\n listings?: Record<string, BakedListing>\n /**\n * URL paths for the documents this page's rich-text props LINK to, keyed\n * `collection:id` (EC-291, from the render outcome). A self-typed internal link\n * (`attrs.ref = {type:'reference',…}`) resolves through this to a real `href`; an\n * unknown key (or absent map) degrades to the renderer's visible `unresolved-ref`\n * issue (EC-290), never a silent `#`.\n */\n links?: Record<string, string>\n /**\n * The resolved document's collection-declared delivery presentation (EC-499,\n * from the render outcome): which field(s) hold the body. Absent → the EC-458\n * shape picker selects the body by VALUE, exactly as before.\n */\n presentation?: DeliveryPresentation\n}\n\n/**\n * A collection's `presentation` declaration (EC-499), resolved for delivery: the\n * body field list normalized from `string | string[]`. Plain data — it rides the\n * (serializable) render outcome and the RSC boundary like the baked\n * assets/relations/links do.\n *\n * EC-524: this renderer applies no CHROME. It carried a `wrapper` component id\n * briefly (never released); a detail wrapper is a page template, templates are\n * code, and the host now supplies one from its own route file via\n * `createCanvasRoute`'s `wrap` option. What stays here is content selection —\n * which is exactly what makes a canvas body and a rich-text body render\n * agnostically through the one entry point.\n */\nexport interface DeliveryPresentation {\n /** Field names whose values form the body, in render order. */\n body?: readonly string[]\n}\n\n/**\n * One listing's baked result (EC-255; EC-581 6a): the documents its query matched,\n * or the structured errors `listDocuments` refused it with. Plain data — it rides\n * the serializable render outcome and the RSC boundary like the other bakes.\n */\nexport type BakedListing = RelationTarget[] | { errors: readonly ContentQueryError[] }\n\n/** Build the renderer's relation resolver from the baked, populated targets (EC-254). */\nfunction relationResolver(relations: Record<string, RelationTarget>): ResolveRelation {\n return (ref) => relations[`${ref.collection}:${ref.id}`] ?? null\n}\n\n/** Build the renderer's link resolver from the baked, doc-keyed URL paths (EC-291). */\nfunction linkResolver(links: Record<string, string>): ResolveLink {\n return (ref) => links[`${ref.collection}:${ref.id}`] ?? null\n}\n\n/**\n * Build the renderer's listing resolver from the baked, query-keyed documents\n * (EC-255). A refused query resolves to nothing (the block's own empty state); the\n * listing decorator below shows why.\n */\nfunction listingResolver(listings: Record<string, BakedListing>): ResolveListing {\n return (query) => {\n const baked = listings[serializeListingQuery(query)]\n return Array.isArray(baked) ? baked : null\n }\n}\n\n/**\n * Why this node's listing cannot run on delivery (EC-581 6a), or `null`: its\n * declaration failed registration (`invalid-listing`), or `listDocuments` refused\n * its query. The SAME two judgments the studio canvas notice reads — the registry's\n * issues and the query validator's errors — so a listing that cannot run is never\n * an empty grid on one surface and a notice on the other.\n */\nfunction listingFailureOf(\n node: ComponentNode,\n components: HostComponents,\n listings: Record<string, BakedListing> | undefined,\n): string[] | null {\n const manifest = components.registry.getManifest(node.componentId)\n if (!manifest) return null\n const listing = nodeListingOf(node, manifest, components.registry)\n if (!listing || listing.state === 'awaiting') return null\n if (listing.state === 'invalid') return listing.messages\n const baked = listings?.[serializeListingQuery(listing.query)]\n return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null\n}\n\n/**\n * The visible delivery notice for a listing that cannot run (EC-581 6a), rendered\n * above the block (which keeps its own empty state) — the delivery counterpart of\n * the studio's in-canvas notice, in the renderer's `data-ec-fallback` family.\n */\nfunction InvalidListingNotice(props: { nodeId: string; messages: readonly string[] }): ReactNode {\n return (\n <div data-ec-fallback=\"invalid-listing\" data-ec-node-id={props.nodeId} role=\"note\">\n {`This listing can't run: ${props.messages.join(' ')}`}\n </div>\n )\n}\n\n/** Build the renderer's asset resolver from the baked delivery assets (EC-195). */\nfunction assetResolver(assets: readonly ResolvedAsset[]): (id: string) => RenderAsset | null {\n const byId = new Map(assets.map((asset) => [asset.id, asset]))\n return (id) => {\n const asset = byId.get(id)\n return asset\n ? {\n url: asset.url,\n width: asset.width,\n height: asset.height,\n alt: asset.alt,\n focalPoint: asset.focalPoint,\n // EC-420: carry the LQIP so a placed Image blurs up on delivery.\n lqip: asset.lqip,\n meta: asset.meta,\n }\n : null\n }\n}\n\n/** Visible fallback when the renderer is handed a non-`ok` resolution. */\nfunction UnresolvedPageFallback(props: { status: string }): ReactNode {\n return (\n <div data-ec-fallback=\"unresolved-page\" data-ec-status={props.status}>\n {`This URL did not resolve to a page (status: ${props.status}).`}\n </div>\n )\n}\n\n/** Visible fallback when an `ok` resolution carries no renderable document. */\nfunction MissingDocumentFallback(): ReactNode {\n return (\n <div data-ec-fallback=\"missing-document\">\n {`Route resolved but carried no page document to render.`}\n </div>\n )\n}\n\n/**\n * Server-side per-node decorator: unwrap the renderer's client error\n * boundary. React Server Components cannot render class components, and a\n * boundary cannot recover during server rendering anyway (the renderer\n * already converts resolution errors into visible fallbacks before this\n * point). The wrapped element keeps its key, so output is unchanged.\n */\nfunction stripServerErrorBoundary(_node: ComponentNode, rendered: ReactNode): ReactNode {\n if (isValidElement(rendered) && rendered.type === RenderErrorBoundary) {\n return (rendered.props as { children?: ReactNode }).children ?? null\n }\n return rendered\n}\n\n/**\n * Render a resolved page server-side. Async so hosts can compose it like any\n * other RSC; resolution itself is synchronous (the content was prefetched by\n * the `ContentSource`).\n */\nexport async function CanvasRenderer(props: CanvasRendererProps): Promise<ReactNode> {\n const { result, components } = props\n\n if (result.status !== 'ok') {\n // The route helper answers redirect/notFound before rendering; direct\n // users of <CanvasRenderer /> still get a visible state, never a crash.\n return <UnresolvedPageFallback status={result.status} />\n }\n\n const ctx: RenderContext = {\n registry: components.registry,\n implementations: components.implementations,\n resolveAsset: assetResolver(props.assets ?? []),\n // EC-254: wire relation population only when the host baked the populated\n // targets, so a host that did not opt in keeps the raw-ref passthrough.\n ...(props.relations ? { resolveRelation: relationResolver(props.relations) } : {}),\n // EC-255: wire the listing resolver only when the host baked the query results,\n // so a host that did not opt in keeps listing props empty (the block's own state).\n ...(props.listings ? { resolveListing: listingResolver(props.listings) } : {}),\n // EC-291: wire the rich-text link resolver only when the host baked the link map,\n // so a host that did not opt in degrades a reference link to the visible\n // `unresolved-ref` issue (EC-290) rather than a wrong href.\n ...(props.links ? { resolveLink: linkResolver(props.links) } : {}),\n decorateNode: (node, rendered) => {\n const stripped = stripServerErrorBoundary(node, rendered)\n // EC-581 6a: a listing that cannot run says so above its (empty) block.\n const failure = listingFailureOf(node, components, props.listings)\n if (!failure) return stripped\n return (\n <Fragment key={node.id}>\n <InvalidListingNotice nodeId={node.id} messages={failure} />\n {stripped}\n </Fragment>\n )\n },\n }\n\n const document = result.document\n\n // EC-187/EC-287: a routed document delivers its body directly — there is no\n // graph or layout indirection; shared chrome is composed in the host's\n // `app/layout.tsx`, and per-collection detail chrome in the host's own route\n // (`createCanvasRoute`'s `wrap`, EC-524). EC-499: WHICH field is the body, and\n // what shape it holds, is a declaration (`presentation.body`) with the EC-458\n // value-shape picker as the fallback — and one entry point renders a\n // composition body and a rich-text body alike. With no document, a visible\n // fallback.\n if (document) {\n return <>{renderDocumentBodyItems(bodyItemsOf(document, props.presentation), ctx)}</>\n }\n\n return <MissingDocumentFallback />\n}\n\n/**\n * The ordered body items of a resolved document (EC-499): the values of the\n * collection's declared `presentation.body` fields concatenated in declaration\n * order, or — when the collection declares nothing — the EC-458 value-shape\n * pick, unchanged. A declared field the document has no value for contributes\n * nothing (an empty body is legitimate); a declared field holding something that\n * is neither composition nor rich text contributes a visible fallback.\n */\nexport function bodyItemsOf(\n document: Record<string, unknown> | null | undefined,\n presentation?: DeliveryPresentation,\n): DocumentBodyItem[] {\n if (!document) return []\n const fields = presentation?.body ?? []\n if (fields.length > 0) return fields.flatMap((name) => documentBodyItems(document[name]))\n return documentBodyItems(compositionOf(document))\n}\n\n/**\n * Select the delivery composition from a resolved document (EC-187) — the\n * FALLBACK, used only when the collection declares no `presentation.body`\n * (EC-499). Kept exactly as it was so every undeclared collection renders\n * byte-for-byte what it rendered before: declaration beats shape-guessing, but\n * shape-guessing keeps working where nothing was declared.\n *\n * Its known limit is why the declaration exists: it can only ever select a\n * COMPOSITION, so a document whose body is a `richText` field was unrenderable\n * through the catch-all and needed a hand-written Next route.\n *\n * The\n * composition field can be named anything the host's collection schema chooses:\n * the conventional `body` (a `page`) or `content` (a routed content collection\n * like a `post`), but ALSO a host-specific name such as Konditorei's\n * `pageBuilder` (migrated faithfully from Sanity, EC-263). So the field is chosen\n * by VALUE — a composition is a `ComponentNode` or an array of them — NEVER by\n * name: a `body ?? content` name heuristic both picks a post's rich-text `body`\n * string over its `content` composition AND misses a `pageBuilder` entirely,\n * rendering nothing.\n *\n * `body` then `content` keep priority (so a content document's unrelated rich-text\n * `body` never shadows the conventional `content` composition); any other\n * composition-valued field is the generalized fallback.\n */\nfunction compositionOf(document: Record<string, unknown>): unknown {\n const body = document['body']\n // EC-547: a v2 value qualifies for the shape-pick only when it CARRIES\n // component roots — a prose-only sequence is renderable through a declared\n // `presentation.body`, but the undeclared fallback keeps its EC-458 promise\n // byte-identical: a document's prose `body` never shadows its conventional\n // `content` composition.\n if (isCompositionishSequence(body) || Array.isArray(body) || isComponentNodeValue(body)) return body\n const content = document['content']\n if (isCompositionishSequence(content) || Array.isArray(content) || isComponentNodeValue(content)) return content\n // Generalized (EC-263): the first field whose value is a composition — a node or\n // a non-empty array of nodes. `body`/`content` already took priority above.\n for (const value of Object.values(document)) {\n if (isCompositionishSequence(value)) return value\n if (isComponentNodeValue(value)) return value\n if (Array.isArray(value) && value.length > 0 && value.every(isComponentNodeValue)) return value\n }\n return undefined\n}\n\n/**\n * The routed document's COMPOSITION nodes as a `ComponentNode[]` (EC-205): the\n * same body selection the renderer uses, normalized to an array so a delivery\n * host can walk it (e.g. to page-scope baked assets, or to read the first\n * block's `presentation.fullBleed` hint). Non-node values are dropped.\n * `null`/no-document yields an empty array.\n *\n * EC-499: takes the collection's declared presentation when there is one, and\n * still returns COMPOSITION nodes only — a rich-text body yields `[]`, which is\n * the right answer for both of its readers (nothing to full-bleed; nothing that\n * `collectReferencedAssetIds` could walk). The rich-text half of a body is baked\n * separately in `route-core`.\n */\nexport function compositionNodesOf(\n document: Record<string, unknown> | null | undefined,\n presentation?: DeliveryPresentation,\n): ComponentNode[] {\n return documentBodyCompositionNodes(bodyItemsOf(document, presentation))\n}\n\n/** A v2 sequence that carries at least one component root (the shape-pick bar). */\nfunction isCompositionishSequence(value: unknown): boolean {\n return (\n isBlockSequenceValueShape(value) &&\n value.content.some(\n (node) =>\n typeof node === 'object' &&\n node !== null &&\n (node as { type?: unknown }).type === 'elytra/component',\n )\n )\n}\n\n/** Structural `ComponentNode` guard — a resolved field value is opaque at the type level. */\nfunction isComponentNodeValue(value: unknown): boolean {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { id?: unknown }).id === 'string' &&\n typeof (value as { componentId?: unknown }).componentId === 'string'\n )\n}\n","import type { CollectionDef, Locale, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport { cacheTags } from '@elytracms/core/content'\nimport type { CacheTags, ContentClient, Perspective, ResolvedAsset } from '@elytracms/core/content'\n\n/**\n * The content seam of the embedded runtime (EC-144, vision AD-1/AD-4): the\n * catch-all route helper consumes this interface, so where the content comes\n * from is swappable. EC-144 ships the in-memory snapshot bridge\n * (`createContentSnapshot` over a `PersistenceAdapter`); EC-145/146 plug a\n * Convex-backed source into the exact same seam.\n *\n * Accessors are synchronous over a prefetched `ContentLookup` (vision AD-4);\n * any fetching happens when the source is loaded, before clients are minted.\n *\n * EC-574: the `CanvasDataSource` shape and the `sources` member (the data-binding\n * subsystem's per-section query definitions, EC-166) are gone with that subsystem.\n */\nexport interface ContentSource {\n /** The project's locale configuration (default + supported set, EC-018). */\n readonly locales: LocaleConfig\n /**\n * Mint a typed accessor client (EC-143) bound to a perspective. The locale\n * defaults to the project default; the route helper overrides it per\n * request from the URL's locale prefix.\n */\n client(init: { perspective: Perspective; locale?: Locale }): ContentClient\n /**\n * The route records the source's clients resolve against (EC-171, additive).\n * Lets enumerating consumers — the sitemap helper — walk the same route\n * table `resolvePage` uses, instead of keeping a second list. Optional so\n * existing custom sources stay valid; without it the sitemap helper needs\n * explicit routes.\n */\n readonly routes?: readonly RouteRecord[]\n /**\n * The project's collection definitions (EC-499, additive) — config-owned\n * (AD-11), already prefetched by every source that reads a schema. The route\n * helper reads the resolved document's collection from here to honour its\n * declared `presentation` (which field(s) hold the delivery body, and the\n * optional detail wrapper). Optional so existing custom sources stay valid;\n * without it every collection is treated as undeclared and the EC-458 shape\n * picker decides, exactly as before.\n */\n readonly collections?: readonly CollectionDef[]\n /**\n * The project's delivery-shaped assets (EC-195, additive). Lets the route\n * helper bake the assets a page may reference into the (serializable) render\n * outcome, so the renderer can resolve an `asset`-typed Image prop to a usable\n * url + intrinsic dimensions — and a cached page keeps its images during a\n * backend outage. Optional so existing custom sources stay valid; without it,\n * asset-typed props fall back to their raw value (the renderer's visible\n * degradation), never a crash.\n */\n assets?(): readonly ResolvedAsset[]\n}\n\n/**\n * Merge accessor cache tags into one deterministic set (sorted, deduplicated)\n * — used by the route helper to combine the `resolvePage` tags with tags\n * added by the host's payload factory (e.g. a `listDocuments` call). The\n * merged set is what the EC-146 `onTags` hook receives.\n */\nexport function mergeCacheTags(first: CacheTags, ...rest: readonly CacheTags[]): CacheTags {\n const all = [first, ...rest]\n return cacheTags({\n docs: all.flatMap((t) => t.docs),\n collections: all.flatMap((t) => t.collections),\n routes: all.flatMap((t) => t.routes),\n assets: all.flatMap((t) => t.assets),\n locale: first.locale,\n })\n}\n","import { documentKey, mergeRouteRecords, parseLocaleConfig } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfigInput,\n RedirectRecord,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport {\n createContentClient,\n createContentContext,\n isDocumentHistory,\n resolvedAsset,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n Perspective,\n} from '@elytracms/core/content'\nimport type { AssetRecord, PersistenceAdapter } from '@elytracms/core/persistence'\nimport type { ContentSource } from './source'\n\n/**\n * Bridge from a `PersistenceAdapter` to the synchronous `ContentLookup` the\n * accessors (EC-143) read through: prefetch the project's collections,\n * documents, assets, and graphs ONCE (async), then serve every accessor call\n * synchronously from memory. This is how `apps/example-site` reads the seeded\n * in-memory fixture project; a Convex-backed `ContentSource` (EC-145/146)\n * replaces this bridge without touching the route helper.\n *\n * The assembly half is exposed separately as {@link createStaticContentSource}\n * (EC-156): a host that already holds the prefetched data as plain values —\n * e.g. a delivery-snapshot payload fetched over Convex's public HTTP query\n * API — assembles the exact same `ContentSource` without faking an adapter.\n */\n\nexport interface ContentSnapshotOptions {\n /** Route records served by `resolvePage` (EC-019). */\n routes?: readonly RouteRecord[]\n /** Redirect records served by `resolvePage` (EC-019/020). */\n redirects?: readonly RedirectRecord[]\n /**\n * Locale configuration — **required**, never defaulted (EC-530). The default\n * locale is the storage key every localized value is written under, so a\n * fabricated `en` is a data migration to undo, not a config edit: content\n * authored under one locale and served under another, silently (EC-512\n * measured exactly that). EC-490 made a config declaring no locales an error\n * and EC-519 collapsed the two declarations into one; this was the last place\n * in the chain still inventing the value, and it sits in the package that\n * resolves the URLs. Hand it the project's `LocaleConfig` — the one the\n * config declares. Parsed here (EC-580): a `default` outside `locales` throws.\n */\n locales: LocaleConfigInput\n}\n\nexport interface ContentSnapshot extends ContentSource {\n readonly projectId: string\n}\n\n/**\n * Plain prefetched data for {@link createStaticContentSource} (EC-156): the\n * same inputs `createContentSnapshot` reads from a `PersistenceAdapter`, as\n * already-loaded values. Everything content-bearing is optional — an absent\n * piece simply resolves to \"not visible\", with the runtime's explicit\n * fallbacks (never a crash).\n */\nexport interface StaticContentData {\n projectId: string\n /** Locale configuration — **required**, never defaulted (see EC-530 above); parsed (EC-580). */\n locales: LocaleConfigInput\n /** Route records served by `resolvePage` (EC-019). */\n routes?: readonly RouteRecord[]\n /** Redirect records served by `resolvePage` (EC-019/020). */\n redirects?: readonly RedirectRecord[]\n collections?: readonly CollectionDef[]\n /**\n * Stored document sources. Either a bare `CmsDocument` (gated by its own\n * `state`) or a `DocumentHistory` carrying the live draft + the pinned\n * published snapshot (EC-224). Perspective gating happens in the pure\n * accessors (`selectPerspective`, EC-142): a `published` client sees a\n * history's pinned snapshot (or nothing), and never a bare `draft` row.\n */\n documents?: readonly ContentDocumentSource[]\n assets?: readonly AssetRecord[]\n /**\n * Resolve a serve URL for assets without a stored `url`. Defaults to an\n * explicit unresolved-placeholder scheme (never a silent empty string).\n */\n assetUrl?: (asset: AssetRecord) => string\n}\n\n// `mergeRouteRecords` moved to `@elytracms/core/cms-core` (EC-384) so the studio's\n// document-URL preview can merge stored routes with the home fallback through the\n// exact same rule the delivery resolver below uses — re-exported here (imported\n// above) to keep this module's/`@elytracms/next`'s existing public surface.\nexport { mergeRouteRecords }\n\n/**\n * Assemble a `ContentSource` from plain prefetched data — the pure half of\n * {@link createContentSnapshot}, shared with hosts that fetched the data\n * themselves (e.g. the EC-156 live delivery snapshot). Accessor semantics are\n * byte-identical across both paths: the same `ContentLookup` feeds the same\n * pure `@elytracms/core/content` client.\n */\nexport function createStaticContentSource(data: StaticContentData): ContentSnapshot {\n // EC-580: THE delivery parse of the declared locale config into the branded\n // `LocaleConfig` every reader below holds — every source (empty, seeded, live)\n // is assembled here, so a `default` outside `locales` cannot reach a client. It\n // throws: a host builds its empty source at boot, so that is where it fails.\n const locales = parseLocaleConfig(data.locales)\n const routes = data.routes ?? []\n const redirects = data.redirects ?? []\n const assetUrl =\n data.assetUrl ?? ((asset: AssetRecord) => `asset://unresolved/${asset.storageKey}`)\n\n const collections = new Map<string, CollectionDef>()\n for (const collection of data.collections ?? []) collections.set(collection.id, collection)\n\n const documents = new Map<string, ContentDocumentSource>()\n const byCollection = new Map<string, ContentDocumentSource[]>()\n for (const source of data.documents ?? []) {\n const doc = isDocumentHistory(source) ? source.draft : source\n documents.set(documentKey(doc), source)\n const list = byCollection.get(doc.collection) ?? []\n list.push(source)\n byCollection.set(doc.collection, list)\n }\n\n const assets = new Map<string, AssetRecord>()\n for (const record of data.assets ?? []) assets.set(record.id, record)\n\n const lookupFor = (_perspective: Perspective): ContentLookup => ({\n collection: (id) => collections.get(id),\n document: (ref: DocumentRef) => documents.get(documentKey(ref)),\n documents: (collectionId) => byCollection.get(collectionId) ?? [],\n asset: (id) => assets.get(id),\n assetUrl: (asset) => asset.url ?? assetUrl(asset),\n })\n\n const client = (init: { perspective: Perspective; locale?: Locale }): ContentClient =>\n createContentClient({\n lookup: lookupFor(init.perspective),\n context: createContentContext({\n locale: init.locale ?? locales.default,\n perspective: init.perspective,\n locales,\n }),\n routes,\n redirects,\n })\n\n return {\n projectId: data.projectId,\n locales,\n client,\n routes,\n // EC-195: the delivery-shaped assets, so the renderer can resolve an\n // `asset`-typed Image prop (an asset id) to a usable url + intrinsic\n // dimensions. Baked into the (serializable) render outcome by the route\n // helper, so a cached page keeps its images during a backend outage.\n assets: () =>\n [...assets.values()].map((record) => resolvedAsset(record, record.url ?? assetUrl(record))),\n // EC-499: the schema the route helper reads a collection's declared\n // `presentation` from (which field is the delivery body, which component\n // wraps it). Already prefetched for the lookup above — exposing it costs\n // nothing and keeps the declaration reachable without a second wiring seam.\n ...(data.collections ? { collections: data.collections } : {}),\n }\n}\n\n/**\n * Prefetch a project's content from a `PersistenceAdapter` into an in-memory\n * `ContentSource`. The snapshot is a point-in-time view: load once per\n * request (or memoize across requests for fixture-backed sites) — accessors\n * never reach back to the adapter.\n */\nexport async function createContentSnapshot(\n adapter: PersistenceAdapter,\n projectId: string,\n options: ContentSnapshotOptions,\n): Promise<ContentSnapshot> {\n // --- Prefetch (the only async part) ------------------------------------\n const schema = await adapter.cms.findSchema(projectId)\n const documentRecords = await adapter.cms.listDocuments(projectId)\n const versionRecords = await adapter.cms.listAllDocumentVersions(projectId)\n const assetRecords = await adapter.assets.listAssets(projectId)\n // Redirects are editorial DATA (EC-303): read the ENABLED ones from the adapter's\n // redirects rail (projected to the routing `RedirectRecord`), like documents/assets\n // — NOT from config. An explicit `options.redirects` still overrides (fixtures/tests).\n const redirectRecords = await adapter.redirects.listRedirects(projectId)\n const redirects: RedirectRecord[] = redirectRecords\n .filter((record) => record.enabled)\n .map((record) => ({\n id: record.id,\n from: record.from,\n to: record.to,\n permanent: record.permanent,\n }))\n\n // Hybrid sources (EC-224): a `publishedVersion` pointer yields a\n // `DocumentHistory` (draft = the live row, published = the pinned snapshot);\n // an unpinned record stays a bare `CmsDocument` gated by its `state`.\n // Backward-compatible — pre-pointer data resolves exactly as before.\n const versionsByKey = new Map<string, Map<number, CmsDocument>>()\n for (const record of versionRecords) {\n const key = `${record.collection}:${record.docId}`\n let byVersion = versionsByKey.get(key)\n if (!byVersion) {\n byVersion = new Map()\n versionsByKey.set(key, byVersion)\n }\n byVersion.set(record.version, record.snapshot)\n }\n const documents: ContentDocumentSource[] = documentRecords.map((record) => {\n if (record.publishedVersion === undefined) return record.document\n // A dangling pin under-serves to `null` for `published` — never the draft.\n const published = versionsByKey\n .get(`${record.document.collection}:${record.document.id}`)\n ?.get(record.publishedVersion)\n return { draft: record.document, published, versions: [] }\n })\n\n // --- Assemble via the shared pure path ----------------------------------\n // Captured once: `BlobStorageAdapter.url` is synchronous by contract.\n return createStaticContentSource({\n projectId,\n locales: options.locales,\n ...(options.routes ? { routes: options.routes } : {}),\n redirects: options.redirects ? [...options.redirects] : redirects,\n ...(schema ? { collections: schema.collections } : {}),\n documents,\n assets: assetRecords,\n assetUrl: (asset) => adapter.blobs.url(asset.storageKey),\n })\n}\n","import { cacheTags } from '@elytracms/core/content'\nimport { splitLocalePath } from './locale-url'\nimport type {\n CacheTags,\n ContentClient,\n ListDocumentsQuery,\n Perspective,\n ResolvedAsset,\n ResolvedDocument,\n ResolvePageResult,\n} from '@elytracms/core/content'\nimport type { Clock } from '@elytracms/core/determinism'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport {\n collectReferencedAssetIds,\n collectReferencedListings,\n collectReferencedRelations,\n collectReferencedRichTextLinks,\n collectRichTextRefsInValue,\n documentBodyCompositionNodes,\n documentBodyRichTextValues,\n listingRequestOf,\n richTextEmbedsInValue,\n} from '@elytracms/core/runtime-renderer'\nimport type { ListingQuery, RelationTarget, ResolveRelation } from '@elytracms/core/runtime-renderer'\nimport { presentationBodyFields } from '@elytracms/core/cms-core'\nimport { mergeCacheTags } from './source'\nimport type { ContentSource } from './source'\nimport { bodyItemsOf } from './canvas-renderer'\nimport type { BakedListing, DeliveryPresentation } from './canvas-renderer'\n\n/**\n * The pure half of the catch-all route helper (EC-144): URL → outcome, with\n * no Next.js imports — so redirects, 404s, locale handling, asset/relation/\n * listing/link baking, and tag merging are all unit-testable as plain\n * functions. `route.tsx` maps\n * each outcome onto `redirect()` / `permanentRedirect()` / `notFound()` /\n * `<CanvasRenderer />`.\n */\n\n/** What the route helper tells a host callback about the request it is serving. */\nexport interface CanvasRequestInfo {\n /** The raw request path (including any locale prefix). */\n url: string\n perspective: Perspective\n}\n\n/**\n * What a host's `wrap` function is told about the request it is wrapping\n * (EC-524 — see `CanvasRouteOptions.wrap`).\n *\n * Every field is something the route already had in hand at the wrap point:\n * nothing is gathered for this seam, and nothing was invented for it.\n */\nexport interface CanvasWrapContext extends CanvasRequestInfo {\n /**\n * The collection of the document this URL resolved to — **the branch key**.\n * `undefined` when the resolution carried no document, so a host branching on\n * a collection id naturally wraps nothing there (which is what the removed\n * `presentation.wrapper` declaration did too: it only applied with a document).\n */\n collection?: string\n /**\n * The resolved document. Verbatim the `document` prop the removed wrapper\n * component received, so a wrapper ported to this seam needs no other source\n * for its field-level chrome (the title, a byline, a date).\n */\n document: ResolvedDocument | null\n /**\n * The whole resolution — the same value the `metadata` callback already\n * receives, so this is the established host-callback argument on this route\n * rather than a new surface. It carries `dynamicParams`, `route`,\n * `localeFallback` and `issues` for a wrapper that needs more than the\n * document (EC-499 recorded wanting exactly that and could not have it across\n * a serializable declaration).\n */\n result: ResolvePageResult\n}\n\n/**\n * PURE core (EC-524): build the wrap context from a render outcome's resolution\n * and the request info. Extracted from `route.tsx` so the derivation — above all\n * \"no document ⇒ no collection ⇒ a collection-branching host wraps nothing\" — is\n * testable without Next's request APIs.\n */\nexport function canvasWrapContext(\n result: ResolvePageResult,\n info: CanvasRequestInfo,\n): CanvasWrapContext {\n const collection = result.document?.collection\n return {\n ...info,\n ...(collection !== undefined ? { collection } : {}),\n document: result.document,\n result,\n }\n}\n\nexport type CanvasPageOutcome =\n | { kind: 'redirect'; target: string; permanent: boolean; tags: CacheTags }\n | { kind: 'not-found'; tags: CacheTags }\n | {\n kind: 'render'\n result: ResolvePageResult\n /**\n * The delivery-shaped assets the page may reference (EC-195). Baked into\n * the outcome (serializable) so the renderer resolves `asset`-typed Image\n * props to a url + intrinsic dimensions, and a cached page keeps its\n * images during a backend outage.\n */\n assets: readonly ResolvedAsset[]\n /**\n * The composition-node `relation` props this page references, populated to\n * depth-1 delivery shape and keyed `collection:id` (EC-254). Baked into the\n * outcome (serializable) so `<CanvasRenderer />` rebuilds\n * `RenderContext.resolveRelation` from it inside the RSC — a function (the\n * content client) cannot ride the data cache or cross the RSC boundary, so\n * the data is baked exactly as the page-scoped `assets` list is.\n */\n relations: Record<string, RelationTarget>\n /**\n * The documents each `listing` block on this page resolved to (EC-255),\n * keyed by serialized query. Baked into the outcome (serializable) so\n * `<CanvasRenderer />` rebuilds `RenderContext.resolveListing` from it inside\n * the RSC — like `relations`, because the content client that runs the query\n * cannot ride the data cache or cross the RSC boundary. A listing depends on\n * collection MEMBERSHIP (a product joining/leaving the category), so its\n * invalidation rides the per-query `listDocuments` tags merged into `tags`.\n *\n * EC-581 6a: a query `listDocuments` refused (a non-filterable field, an\n * operand of the wrong type, a bad limit) is baked as `{ errors }` — the\n * structured `ContentQueryError`s — and `<CanvasRenderer />` shows them above\n * the block (`data-ec-fallback=\"invalid-listing\"`), never an empty grid\n * indistinguishable from \"no matching content\".\n */\n listings: Record<string, BakedListing>\n /**\n * EC-581 6a: whether resolving this page READ THE CLOCK — a listing on it\n * binds an operand to `now` (e.g. \"events whose date is still ahead\"). Such a\n * page's content changes as time passes, not only on publish: a cached entry\n * of it is correct for the instant it was resolved and goes stale when a\n * listed document crosses its `now` boundary, which no publish tag covers. A\n * cache layer must bound that entry's life (the delivery cache — EC-515 —\n * reads this). `false` for every page with no `now`-bound listing.\n */\n readsNow: boolean\n /**\n * The public URL path of every document a rich-text prop on this page LINKS\n * to (EC-291), keyed `collection:id`. Baked into the outcome (serializable) so\n * `<CanvasRenderer />` rebuilds `RenderContext.resolveLink` from it inside the\n * RSC — like `relations`, because the content client that derives the URL\n * (`resolveLink` → `urlForDocument`) cannot ride the data cache or cross the\n * RSC boundary. A link depends on the target's IDENTITY (its slug/parent →\n * path), so its invalidation rides the per-link doc tags merged into `tags`:\n * renaming, re-parenting, or first-publishing a linked document re-bakes the\n * embedding page's href.\n */\n links: Record<string, string>\n /**\n * The resolved document's collection-declared delivery presentation\n * (EC-499): which field(s) are the render body. Plain, serializable data —\n * handed to `<CanvasRenderer />` so ONE catch-all renders every\n * collection's body, whatever its shape. Absent when the collection\n * declares nothing (or the source exposes no collections), and the EC-458\n * value-shape picker then decides exactly as before.\n */\n presentation?: DeliveryPresentation\n tags: CacheTags\n }\n\n/** Build the request path from a Next catch-all `slug` param. */\nexport function pathFromSlug(slug: readonly string[] | undefined): string {\n return '/' + (slug ?? []).join('/')\n}\n\n/**\n * Derive the request locale from a leading URL segment (`/de/about` → locale\n * `de`, path `/about`). EC-486 moved the implementation to `./locale-url`, so\n * this direction and the sitemap's inverse are one definition rather than two\n * that can drift; re-exported here because it is a published API surface.\n */\nexport { splitLocalePath }\n\nexport interface ResolveCanvasPageOptions {\n /**\n * Component registry (EC-205): when provided, the baked {@link CanvasPageOutcome}\n * `assets` are PAGE-SCOPED to exactly the asset ids this page's composition +\n * layout resolve through `resolveAsset` — not the whole project asset list,\n * which otherwise rides in every page's cache entry + RSC payload. Omitted →\n * all source assets (back-compat). Scoping mirrors the renderer's asset\n * resolution exactly, so no referenced image is ever dropped.\n */\n registry?: ComponentRegistry\n /**\n * The clock a `{ now: true }` listing operand reads (EC-581 6a) — an ISO-8601\n * UTC instant, read at most once per resolution so every listing on the page\n * compares against the SAME instant. Defaults to the system clock; tests and\n * fixtures inject a fixed one. Taken as a FUNCTION, read during resolution, so a\n * `now` value is never an argument a cache keys on.\n */\n clock?: Clock\n}\n\n/** The real clock, used only at this boundary when the caller injects none. */\nconst systemClock: Clock = () => new Date().toISOString()\n\n/**\n * Page-scope the baked assets to those the page actually resolves (EC-205): the\n * static `asset`-typed props of its composition + selected layout tree. Document\n * asset FIELDS resolve into the document separately and do not use this list, so\n * they do not widen it.\n */\nfunction scopeAssetsToPage(\n allAssets: readonly ResolvedAsset[],\n registry: ComponentRegistry,\n result: ResolvePageResult,\n body: PageBody,\n resolveRelation: ResolveRelation,\n): readonly ResolvedAsset[] {\n if (allAssets.length === 0) return allAssets\n const ids = new Set<string>()\n // EC-270: pass `resolveRelation` so the collector follows a Reusable block's\n // `element` pick into the referenced document and page-scopes the asset ids\n // inside its transcluded `content` — otherwise a transcluded image is baked\n // nowhere and renders empty on the host (the relation bake already transcludes).\n const seen = new Set<string>()\n for (const node of body.nodes) {\n collectReferencedAssetIds(node, registry, ids, resolveRelation, seen)\n }\n // EC-499: a rich-text BODY (not just a component's rich-text prop) pins assets\n // too — an inline image carries `attrs.ref = { type:'asset', id }`. Its\n // component-node siblings are already in `body.nodes`; this collects the pins\n // that sit directly in the prose, so the host bakes them instead of the page\n // rendering an unresolved-ref placeholder where the image should be.\n for (const value of body.richTexts) {\n collectRichTextRefsInValue(value, { onAsset: (id) => ids.add(id) })\n }\n // EC-202: the OG image is an asset the page uses (for its `og:image` meta), so\n // it joins the page-scoped set — (EC-227) tagged for invalidation when that\n // asset record is edited. EC-266: a `type:'asset'` seoOgImage field is delivered\n // as a ResolvedAsset OBJECT ({ id, url }); a string is a raw id (or a passthrough\n // URL, which has no asset id to tag). Take the id from whichever shape.\n const ogImage = result.document?.['seoOgImage']\n const ogImageId =\n typeof ogImage === 'string'\n ? ogImage\n : ogImage && typeof ogImage === 'object' && typeof (ogImage as { id?: unknown }).id === 'string'\n ? (ogImage as { id: string }).id\n : null\n if (ogImageId) ids.add(ogImageId)\n return ids.size === 0 ? [] : allAssets.filter((asset) => ids.has(asset.id))\n}\n\n/**\n * One request's resolved delivery body (EC-499), split into what each baker\n * needs: the composition NODES to walk (EC-254 relation scan, EC-205 asset\n * scoping, EC-255 listings, EC-291 links) and the rich-text REGIONS whose own\n * refs must be baked alongside them.\n */\ninterface PageBody {\n /**\n * Every composition node the page renders: the body's own component nodes AND\n * the components EMBEDDED in its rich-text regions (EC-189/EC-434 — the\n * ambient embed renderer renders those on delivery, so their relations,\n * listings and assets have to be baked or they render empty).\n */\n nodes: ComponentNode[]\n /** The body's rich-text regions, for the pinned-asset / internal-link bake. */\n richTexts: unknown[]\n}\n\n/** Split a resolved document's declared body into the bakers' two views (EC-499). */\nfunction pageBodyOf(result: ResolvePageResult, presentation?: DeliveryPresentation): PageBody {\n const items = bodyItemsOf(result.document, presentation)\n const richTexts = documentBodyRichTextValues(items)\n return {\n nodes: [\n ...documentBodyCompositionNodes(items),\n ...richTexts.flatMap((value) => richTextEmbedsInValue(value)),\n ],\n richTexts,\n }\n}\n\n/**\n * The collection-declared delivery presentation for the document this URL\n * resolved to (EC-499), normalized for the render outcome — `undefined` when the\n * source exposes no collections, the collection is unknown, or it declares\n * nothing (the EC-458 shape picker then decides, unchanged).\n *\n * EC-524: body fields only. The detail wrapper this used to carry alongside them\n * is host code now, applied by `createCanvasRoute`'s `wrap` around the whole\n * renderer, so nothing chrome-shaped crosses the outcome.\n */\nfunction presentationFor(\n source: ContentSource,\n result: ResolvePageResult,\n): DeliveryPresentation | undefined {\n const collectionId = result.document?.collection\n if (!collectionId || !source.collections) return undefined\n const collection = source.collections.find((candidate) => candidate.id === collectionId)\n if (!collection?.presentation) return undefined\n const body = presentationBodyFields(collection)\n return body.length > 0 ? { body: [...body] } : undefined\n}\n\n/**\n * Populate every composition-node `relation` prop the page references to depth-1,\n * keyed `collection:id` (EC-254). Walks the page composition + layout (and, through\n * `collectReferencedRelations`, transcluded reusable content) and resolves each ref\n * through the request's client. Empty when no registry is supplied (the collector\n * needs manifests to know which props are relations) — relation props then pass\n * through raw, the graceful degradation a host without a registry already gets.\n *\n * `docKeys` carries the `collection:id` of every referenced pick (resolved or not),\n * so the caller cache-tags the page by them (EC-146): editing/publishing a picked\n * document — including a previously-unpublished one that should now appear — drops\n * exactly the pages that embed it. A fixed pick depends on the document's identity,\n * not on its collection's membership, so no coarse collection tag is added.\n */\nfunction populatePageRelations(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n): { relations: Record<string, RelationTarget>; docKeys: string[] } {\n if (!registry) return { relations: {}, docKeys: [] }\n const refKeys = new Set<string>()\n const relations = collectReferencedRelations(\n body.nodes,\n registry,\n (ref) => client.resolveRelation(ref),\n {},\n refKeys,\n )\n return { relations, docKeys: [...refKeys] }\n}\n\n/**\n * Resolve every `listing` block on the page to its matching documents (EC-255),\n * keyed by serialized query for `<CanvasRenderer />` to rebuild `resolveListing`.\n * Runs each listing's query through the request's client (so perspective + locale\n * match the rest of the resolution — published-only on a live page, per AD-5), and\n * returns the per-query `listDocuments` `CacheTags` so the caller folds them into\n * the page's tag set: those carry both the COLLECTION tag (membership — a product\n * joining/leaving the category re-renders the grid) and the listed documents' own\n * tags (a member's content edit re-renders it). Empty when no registry is supplied\n * (the collector needs manifests to know which blocks declare a listing).\n *\n * EC-581 6a: each query executes through `listingRequestOf` — the same phase the\n * studio canvas runs — with `clock` read at most once for the whole page\n * (`readsNow` reports whether it was). A query `listDocuments` refuses is baked as\n * its structured errors, so the page shows them instead of a silent empty grid.\n */\nfunction populatePageListings(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n clock: Clock,\n): { listings: Record<string, BakedListing>; tags: CacheTags[]; readsNow: boolean } {\n if (!registry) return { listings: {}, tags: [], readsNow: false }\n const tags: CacheTags[] = []\n let instant: string | undefined\n const now = (): string => (instant ??= clock())\n const listings = collectReferencedListings<BakedListing>(\n body.nodes,\n registry,\n (query: ListingQuery) => {\n const listed = client.listDocuments(query.collection, listDocumentsQueryOf(query, now))\n tags.push(listed.tags)\n return listed.ok ? (listed.documents as RelationTarget[]) : { errors: listed.errors }\n },\n // EC-255: reach listings nested inside transcluded reusable content (the same\n // depth-1 populate the relation bake uses), so a grid inside a reusable bakes —\n // and its membership tag is folded in — instead of rendering empty on the host.\n (ref) => client.resolveRelation(ref),\n )\n return { listings, tags, readsNow: instant !== undefined }\n}\n\n/**\n * A listing query as `listDocuments` input (EC-581 6a). The operands are JSON\n * bound from authored props and `now`; the cast hands them to `listDocuments`,\n * whose `validateWhere`/`validateLimit` judge them — the one validator — and\n * return a structured error for anything that is not a valid operand.\n */\nfunction listDocumentsQueryOf(query: ListingQuery, now: () => string): ListDocumentsQuery {\n const request = listingRequestOf(query, now)\n return {\n where: request.where as ListDocumentsQuery['where'],\n ...(request.sort ? { sort: request.sort } : {}),\n ...(request.limit !== undefined ? { limit: request.limit as number } : {}),\n }\n}\n\n/**\n * Resolve every internal rich-text LINK on the page to its target document's URL\n * path (EC-291), keyed `collection:id` for `<CanvasRenderer />` to rebuild\n * `resolveLink`. Collects the referenced doc refs from the page's rich-text props\n * (transclusion-aware) and derives each URL through the request's client\n * (`resolveLink` → `urlForDocument`), so the path matches what the same routes serve.\n * A ref that resolves to no live URL (unpublished target / no addressing route) is\n * simply omitted — the renderer then shows the EC-290 `unresolved-ref` issue.\n *\n * `docKeys` carries EVERY referenced target (resolved or not), so the caller cache-\n * tags the page by them (EC-146): publishing or renaming a linked document — including\n * one not yet published when the page was baked — drops exactly the embedding pages.\n * Empty when no registry is supplied (the collector needs no manifest, but the bake\n * stays consistent with its relation/listing siblings, which do).\n */\nfunction populatePageLinks(\n body: PageBody,\n client: ContentClient,\n registry: ComponentRegistry | undefined,\n): { links: Record<string, string>; docKeys: string[] } {\n if (!registry) return { links: {}, docKeys: [] }\n const refs = collectReferencedRichTextLinks(\n body.nodes,\n registry,\n (ref) => client.resolveRelation(ref),\n )\n // EC-499: a rich-text BODY carries link marks directly, not only inside a\n // component's rich-text prop — collect those too, so an internal link in a\n // post's prose resolves to a real href instead of the `unresolved-ref` fallback.\n for (const value of body.richTexts) {\n collectRichTextRefsInValue(value, {\n onLink: (ref) => {\n refs[`${ref.collection}:${ref.id}`] = ref\n },\n })\n }\n const links: Record<string, string> = {}\n for (const [key, ref] of Object.entries(refs)) {\n const url = client.resolveLink(ref)\n if (url !== null) links[key] = url\n }\n return { links, docKeys: Object.keys(refs) }\n}\n\n/**\n * Resolve one request URL against a content source: split the locale, run\n * `resolvePage` in the requested perspective, assemble binding payloads, and\n * merge every accessor's `CacheTags` into one deterministic set. Pure — all\n * data was prefetched by the source.\n */\nexport async function resolveCanvasPage(\n source: ContentSource,\n url: string,\n perspective: Perspective,\n options: ResolveCanvasPageOptions = {},\n): Promise<CanvasPageOutcome> {\n const { locale, path } = splitLocalePath(url, source.locales)\n const client = source.client({ perspective, locale })\n const result = client.resolvePage(path)\n\n if (result.status === 'redirect' && result.redirect) {\n return {\n kind: 'redirect',\n target: result.redirect.target,\n permanent: result.redirect.permanent,\n tags: result.tags,\n }\n }\n\n if (result.status !== 'ok') {\n return { kind: 'not-found', tags: result.tags }\n }\n\n // EC-499: read the collection's declared delivery presentation ONCE, and split\n // the resolved body by it — every baker below and the renderer then agree on\n // exactly which fields are the body and which of its items is which kind.\n const presentation = presentationFor(source, result)\n const body = pageBodyOf(result, presentation)\n\n const allAssets = source.assets?.() ?? []\n // EC-205: page-scope the baked assets when a registry is supplied (the route\n // helper passes it); else keep the full list (back-compat).\n const bakedAssets = options.registry\n ? scopeAssetsToPage(allAssets, options.registry, result, body, (ref) =>\n client.resolveRelation(ref),\n )\n : allAssets\n // EC-227: tag the cache entry with every asset it baked, so editing an asset\n // record (alt/dimensions/re-upload) drops exactly the pages carrying it. The\n // locale is irrelevant for asset tags (an asset record is locale-agnostic) —\n // `cacheTags` just needs one; `result.tags.locale` is the page's.\n const assetTags = cacheTags({\n assets: bakedAssets.map((asset) => asset.id),\n locale: result.tags.locale,\n })\n // EC-254: populate the relation props of the nodes inside the page's blocks\n // (e.g. a ProductGrid's picked products), keyed for `<CanvasRenderer />` to\n // rebuild `resolveRelation`. Reuses the request's client, so perspective +\n // locale match the rest of the resolution.\n const { relations, docKeys: relationDocKeys } = populatePageRelations(\n body,\n client,\n options.registry,\n )\n // EC-146: tag the page by every picked document so editing/publishing one drops\n // the embedding page (the locale is the page's; doc tags are locale-agnostic).\n const relationTags = cacheTags({ docs: relationDocKeys, locale: result.tags.locale })\n // EC-255: resolve every listing block's query (e.g. a ProductGrid keyed to a\n // category) and bake the result keyed by serialized query. The per-query\n // `listDocuments` tags (collection membership + listed docs) join the merged set,\n // so adding/removing a member or editing one invalidates the embedding page.\n const {\n listings,\n tags: listingTags,\n readsNow,\n } = populatePageListings(body, client, options.registry, options.clock ?? systemClock)\n // EC-291: resolve every internal rich-text link to its target's URL path and bake\n // it for `<CanvasRenderer />` to rebuild `resolveLink`. Tag the page by each linked\n // document so renaming/re-parenting/first-publishing a target re-bakes the href.\n const { links, docKeys: linkDocKeys } = populatePageLinks(body, client, options.registry)\n const linkTags = cacheTags({ docs: linkDocKeys, locale: result.tags.locale })\n return {\n kind: 'render',\n result,\n assets: bakedAssets,\n relations,\n listings,\n readsNow,\n links,\n ...(presentation ? { presentation } : {}),\n tags: mergeCacheTags(\n result.tags,\n ...listingTags,\n assetTags,\n relationTags,\n linkTags,\n ),\n }\n}\n","import type { CacheTags } from '@elytracms/core/content'\nimport type { RevalidationEvent } from '@elytracms/core/revalidation'\n\n/**\n * Cache-tag pipeline (EC-146, vision AD-5) — the pure half.\n *\n * This module defines (a) the **canonical Next.js tag string format** mapping\n * the accessor-level `CacheTags` (EC-143) onto `cacheTag` / `revalidateTag` keys,\n * (b) the **tag derivation for a revalidation event** ({@link revalidationTags},\n * EC-579 — the receiver's half of the one emitter), and (c) the **pure half of the\n * route's cache entry** (EC-515): its key ({@link canvasCacheKey}), its `cacheTag`\n * calls ({@link entryTagCalls}), its life ({@link CANVAS_CACHE_LIFE}) and the\n * resolver registry the module-level `'use cache'` function reads its loader from\n * ({@link registerCanvasResolver}). No Next.js imports — everything here is\n * unit-testable; `route.tsx` holds the one `'use cache'` function that calls\n * `cacheTag` / `cacheLife` with these values.\n *\n * This is the ONE tag module: the entries are tagged here and the invalidations\n * are derived here, from the same formatters and the same scope. Before EC-579 the\n * format was mirrored twice more (the studio's browser dispatcher and an inline\n * literal in the Convex emitter), and each emitter brought its own scope.\n *\n * ## Tag format\n *\n * - `doc:<collection>:<id>` — one per document whose content was actually in\n * the response (fetch-time tagging: includes depth-1 populated relation\n * targets, and documents inside filtered/dynamic lists *after* the filter\n * ran).\n * - `collection:<name>:<locale>` — the coarse membership tag: every response\n * that read from a collection carries it, so create/delete/newly-matching\n * documents sweep cached lists without read-set tracking.\n * - `route:<pattern>:<locale>` — the resolved URL path plus every hop of a\n * redirect chain.\n * - `asset:<id>` — one per asset whose delivery shape the page *baked*\n * (page-scoped images + `seoOgImage`, EC-227); editing that asset record\n * (alt/dimensions/re-upload) drops exactly the pages that baked it. Not\n * locale-scoped — an asset record is locale-agnostic.\n * - `project` — the whole-project sweep tag attached to **every** cached\n * entry; derived for the events whose effect no narrower tag names (a redirect\n * change, an asset edit — see {@link revalidationTags}).\n *\n * ## Project scope\n *\n * Every tag is prefixed `p:<projectId>:` when a scope is configured (e.g.\n * `p:prj_aurora:doc:post:post-1`). The cost is a few bytes per tag and it\n * keeps multi-project hosts (one Next app serving several builder projects)\n * from cross-invalidating, so the prefix is **on whenever the host knows its\n * project id** — decision: include it. Since EC-579 the scope has ONE owner: the\n * host. The webhook carries an event, not tags, and the receiver derives the\n * tags with the same scope its cache entries are written under (the delivery\n * kit hands both the one `contentScope`), so emitter and host can no longer\n * disagree about it.\n */\n\n/** Apply the optional multi-project scope prefix to one tag. */\nexport function scopeCacheTag(tag: string, scope?: string): string {\n return scope === undefined || scope.length === 0 ? tag : `p:${scope}:${tag}`\n}\n\n/**\n * Tag for one document identity. `docKey` is the EC-143 `documentKey` string\n * (`<collection>:<id>`), exactly as found in `CacheTags.docs`.\n */\nexport function docCacheTag(docKey: string, scope?: string): string {\n return scopeCacheTag(`doc:${docKey}`, scope)\n}\n\n/** The coarse membership tag of one collection in one locale. */\nexport function collectionCacheTag(collection: string, locale: string, scope?: string): string {\n return scopeCacheTag(`collection:${collection}:${locale}`, scope)\n}\n\n/** Tag for one resolved route path (or redirect-chain hop) in one locale. */\nexport function routeCacheTag(pattern: string, locale: string, scope?: string): string {\n return scopeCacheTag(`route:${pattern}:${locale}`, scope)\n}\n\n/**\n * Tag for one baked asset (EC-227). `assetId` is the bare asset id, as found in\n * `CacheTags.assets`. Not locale-scoped: an asset record is one row shared\n * across locales, so an edit invalidates the baking pages in every locale.\n */\nexport function assetCacheTag(assetId: string, scope?: string): string {\n return scopeCacheTag(`asset:${assetId}`, scope)\n}\n\n/**\n * The whole-project sweep tag. Attached to every cached page entry; derived for\n * the events no narrower tag covers ({@link revalidationTags}).\n */\nexport function projectSweepTag(scope?: string): string {\n return scopeCacheTag('project', scope)\n}\n\n/**\n * Map an accessor-level `CacheTags` (what one request actually read) onto the\n * full, deterministic (sorted, deduplicated) set of Next tag strings for the\n * cache entry — including the project sweep tag.\n */\nexport function nextCacheTags(tags: CacheTags, scope?: string): string[] {\n const all = new Set<string>()\n all.add(projectSweepTag(scope))\n for (const docKey of tags.docs) all.add(docCacheTag(docKey, scope))\n for (const collection of tags.collections) {\n all.add(collectionCacheTag(collection, tags.locale, scope))\n }\n for (const route of tags.routes) all.add(routeCacheTag(route, tags.locale, scope))\n for (const assetId of tags.assets) all.add(assetCacheTag(assetId, scope))\n return [...all].sort()\n}\n\n// --- Revalidation event → tags (EC-579) ----------------------------------------\n\n/** The receiver's own project — what its cached entries are tagged with. */\nexport interface RevalidationProject {\n /** The tag scope of this host's cache entries (`p:<scope>:…`), if any. */\n scope?: string | undefined\n /** Every locale this host serves — one `collection:` tag per locale. */\n locales: readonly string[]\n}\n\n/**\n * The tags one revalidation event invalidates on this host — sorted, deduplicated,\n * scoped with the receiver's OWN scope, so the emitter never needs to know it.\n *\n * - `publish` / `unpublish` / `delete` → `doc:<collection>:<id>` (its detail page and\n * every cached list that contained it) + `collection:<collection>:<locale>` for\n * EVERY locale this host serves (list membership may have changed, and a URL\n * cached as not-found for a document that now exists carries it too). **No\n * sweep** — a publish drops exactly the entries that read the document or its\n * collection (EC-515);\n * - `redirects` → the sweep (routing can change any URL; see the `redirects` trigger\n * in the studio's `convex/triggers.ts` for why a targeted `route:` tag is wrong);\n * - `asset` → `asset:<id>` + the sweep (a page reading the asset through a document\n * asset FIELD carries no `asset:` tag — EC-566 D16, EC-589);\n * - `probe` → nothing (the doctor's no-op ping).\n *\n * Why the precise tags are enough now (EC-515, option D): each route entry declares\n * its tags with `cacheTag` from INSIDE the cached function, and Next decides an\n * entry's staleness from the tags it was STORED with — so a `doc:` tag drops the\n * entries that read the document, on a self-hosted host as on any other. Until\n * then the read named only the baseline sweep (`unstable_cache` judged the\n * reading wrapper's tags), every precise tag was inert on a self-hosted host, and\n * every publish carried the sweep to compensate (wave-0091). Measured both ways\n * in EC-515's harness.\n */\nexport function revalidationTags(event: RevalidationEvent, project: RevalidationProject): string[] {\n const { scope } = project\n const tags = new Set<string>()\n switch (event.kind) {\n case 'publish':\n case 'unpublish':\n case 'delete':\n tags.add(docCacheTag(`${event.collection}:${event.id}`, scope))\n for (const locale of project.locales) tags.add(collectionCacheTag(event.collection, locale, scope))\n break\n case 'redirects':\n tags.add(projectSweepTag(scope))\n break\n case 'asset':\n tags.add(assetCacheTag(event.id, scope))\n tags.add(projectSweepTag(scope))\n break\n case 'probe':\n break\n }\n return [...tags].sort()\n}\n\n// --- The route entry: key, tags, life, resolver (EC-515) ------------------------\n\n/** What identifies one cached route resolution — see {@link canvasCacheKey}. */\nexport interface CanvasCacheKeyInput {\n /** The tag scope (the project id the studio publishes under), if configured. */\n scope?: string | undefined\n /**\n * Which content world the loader reads — the delivery kit passes its\n * `contentMode` (`live` / `fallback` / `empty`). A host that drives the route\n * helper directly and has only one world may omit it.\n */\n mode?: string | undefined\n /** The request URL path (locale prefix included). */\n url: string\n}\n\n/** The route entry's arguments: `[scope, mode, url]` (absent = `''`). */\nexport type CanvasCacheKey = readonly [scope: string, mode: string, url: string]\n\n/**\n * The arguments of the route's cached function — which ARE its cache key: Next\n * keys a `'use cache'` entry by (build id, function id, serialized arguments), so\n * whatever identifies an entry has to be an argument, and nothing else may be.\n * Scope, **content mode**, URL.\n *\n * The mode is in the key because an entry can outlive the world that wrote it\n * (a shared or remote cache handler, a host whose env changed under a warm\n * cache) and a hit never asks the loader whether it still applies. Before EC-543\n * the key was `(scope, url)` only, and since `ELYTRA_PROJECT_ID` *is* the config\n * slug, a live host and an empty one keyed the same URL identically — measured on\n * 0.0.85: a warm cache written in live mode kept answering `200` with the live\n * world's bytes after both env vars were blanked and the host itself reported\n * `contentMode: 'empty'`. Keying on the mode makes that cross-read structurally\n * impossible rather than something to detect — a world change is a cold miss,\n * never a stale hit.\n */\nexport function canvasCacheKey({ scope, mode, url }: CanvasCacheKeyInput): CanvasCacheKey {\n return [scope ?? '', mode ?? '', url]\n}\n\n/** The slice of Next's `cacheLife()` profile the route entry sets (seconds). */\nexport interface CanvasCacheLife {\n revalidate: number\n expire: number\n}\n\n/**\n * The life of every route entry in production: **tag-only** (AD-5) — an entry\n * changes when a revalidation event drops one of its tags, never because time\n * passed.\n *\n * - `revalidate: Infinity` — no time-based refresh. Next's `default` profile\n * revalidates every 900 s (`config-shared.js` `cacheLife.default`), and its\n * in-memory handler treats an entry older than `revalidate` as MISSING\n * (`cache-handlers/default.js` `get`), so the default would re-run the loader\n * every 15 minutes per URL — and a cached page would stop surviving a backend\n * outage after 15 minutes. `false` is rejected (`cacheLife` E104); `Infinity`\n * is Next's spelling of \"never\".\n * - `expire: Infinity` — never expired by time either.\n * - `stale` is deliberately NOT set: it is the CLIENT router's reuse window for a\n * prefetched response, not a server lifetime, and Next's default applies.\n *\n * A resolution that depends on the clock (a listing bounded by `now`) is the one\n * entry that must not be tag-only; the route's cached function applies its life\n * AFTER the load, so it picks {@link CANVAS_CLOCK_CACHE_LIFE} for such an entry\n * (`canvasEntryLife`'s second argument, the outcome's `readsNow`). Any such profile\n * keeps `expire` at 300 s or more: below it Next classes the entry \"short-lived\"\n * and the route fails `next build` outside `<Suspense>` (`DYNAMIC_EXPIRE`,\n * measured EC-515).\n */\nexport const CANVAS_CACHE_LIFE: CanvasCacheLife = { revalidate: Infinity, expire: Infinity }\n\n/**\n * The life of a route entry under `next dev`: one second.\n *\n * The instant-publish webhook (the only invalidation, AD-5) cannot reach a\n * localhost host, so a tag-only entry would keep a published change off a dev\n * server until it restarts. Bypassing the entry in dev is not an option under\n * `cacheComponents`: `next dev` then reports the uncached read as a blocking-route\n * error on every render (measured, EC-515). Each dev request renders twice — the\n * response and a validation pass — and the validation pass must HIT what the\n * response filled; a one-second `revalidate` gives exactly that (Next's in-memory\n * handler treats an entry past `revalidate` as missing), so the next reload a\n * second later reads fresh content. `expire` stays at Next's 300 s floor so the\n * entry is not \"short-lived\".\n */\nexport const CANVAS_DEV_CACHE_LIFE: CanvasCacheLife = { revalidate: 1, expire: 300 }\n\n/**\n * The life of a production route entry whose resolution READ THE CLOCK — a listing\n * with a `{ now: true }` operand (EC-581 6a; the outcome's `readsNow`). Such a page\n * changes as time passes, not only on publish, so tag-only would freeze `now` at the\n * moment the entry was written (an \"upcoming events\" grid keeping a past event until\n * the next publish). A minute bounds that staleness; `expire: Infinity` keeps the\n * stale entry servable while it refreshes (and through a backend outage), and stays\n * clear of Next's 300 s \"short-lived\" floor. Tags still invalidate it immediately.\n */\nexport const CANVAS_CLOCK_CACHE_LIFE: CanvasCacheLife = { revalidate: 60, expire: Infinity }\n\n/**\n * The route entry's life for a `NODE_ENV`: one second under `next dev`; else\n * tag-only, or a minute when the resolution read the clock ({@link CANVAS_CLOCK_CACHE_LIFE}).\n */\nexport function canvasEntryLife(nodeEnv: string | undefined, readsClock = false): CanvasCacheLife {\n if (nodeEnv === 'development') return CANVAS_DEV_CACHE_LIFE\n return readsClock ? CANVAS_CLOCK_CACHE_LIFE : CANVAS_CACHE_LIFE\n}\n\n/**\n * Next's per-call tag cap: `cacheTag()` validates each call's tags and stops at\n * `NEXT_CACHE_TAG_MAX_ITEMS` (128), dropping the rest with a console warning\n * (`patch-fetch.js` `validateTags`). The cap is per CALL — the entry accumulates\n * every call's tags (`cache-tag.js`).\n */\nexport const NEXT_CACHE_TAG_CALL_LIMIT = 128\n\n/**\n * The `cacheTag()` calls one route entry makes: the project sweep FIRST and ALONE,\n * then the read set in chunks under {@link NEXT_CACHE_TAG_CALL_LIMIT}.\n *\n * {@link nextCacheTags} is sorted, and `p:<scope>:project` sorts after every\n * `collection:` and `doc:` tag — handed to one `cacheTag` call, a page that read\n * more than ~125 documents would lose the sweep to the per-call cap, silently, and\n * with it every redirect and asset invalidation. Its own first call makes that\n * impossible; the chunking keeps every other tag too.\n */\nexport function entryTagCalls(tags: CacheTags, scope?: string): string[][] {\n const sweep = projectSweepTag(scope)\n const rest = nextCacheTags(tags, scope).filter((tag) => tag !== sweep)\n const calls: string[][] = [[sweep]]\n for (let start = 0; start < rest.length; start += NEXT_CACHE_TAG_CALL_LIMIT) {\n calls.push(rest.slice(start, start + NEXT_CACHE_TAG_CALL_LIMIT))\n }\n return calls\n}\n\n/** What the route's cached function runs on a miss: resolve one URL, published. */\nexport type CanvasEntryResolver<T> = (url: string) => Promise<T>\n\nconst canvasResolvers = new Map<string, CanvasEntryResolver<unknown>>()\n\nfunction resolverKey(scope: string, mode: string): string {\n return JSON.stringify([scope, mode])\n}\n\n/**\n * Register the loader the route's cached function resolves with, for one\n * (scope, mode).\n *\n * Why a registry rather than a closure: Next serializes a `'use cache'` function's\n * closed-over values into its key as bound arguments, and a loader is a function —\n * it cannot be serialized, so a cached function cannot close over it (measured,\n * EC-515). The cached function therefore sits at module level with serializable\n * arguments only ({@link canvasCacheKey}) and reaches its loader here, through the\n * (scope, mode) it was called with — the same identity its entries are keyed by.\n *\n * One resolver per (scope, mode) per server process; registering again replaces\n * it (each route module that mounts the kit's canvas route registers the same\n * host's loader). Two DIFFERENT loaders under one scope and mode would share\n * entries — exactly as they always shared the key.\n */\nexport function registerCanvasResolver<T>(\n scope: string,\n mode: string,\n resolve: CanvasEntryResolver<T>,\n): void {\n canvasResolvers.set(resolverKey(scope, mode), resolve as CanvasEntryResolver<unknown>)\n}\n\n/**\n * The registered resolver for (scope, mode). A miss is a wiring defect (the cached\n * function ran in a process where no canvas route registered for that scope and\n * mode), so it throws — loudly, naming both — rather than resolve against nothing.\n */\nexport function canvasResolverFor<T>(scope: string, mode: string): CanvasEntryResolver<T> {\n const resolve = canvasResolvers.get(resolverKey(scope, mode))\n if (!resolve) {\n throw new Error(\n `Elytra delivery cache: no canvas route registered for scope \"${scope}\" and content mode ` +\n `\"${mode}\" in this server process — the cached route function has no loader to run. ` +\n `This is a wiring defect: create the route with createCanvasRoute / elytra.canvasRoute() ` +\n `before its Page renders.`,\n )\n }\n return resolve as CanvasEntryResolver<T>\n}\n","import type { Metadata, ResolvedMetadata } from 'next'\nimport type { ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\n\n/**\n * Resolved page → Next.js `Metadata` (EC-171, reshaped by EC-187). SEO now\n * lives on the resolved **document**: a `page`-collection document carries flat\n * `seo*` fields (`seoTitle`, `seoDescription`, `seoOgTitle`, `seoOgImage`,\n * `seoNoindex`); a content-collection document falls back to its `title`. There\n * is no separate page-graph `seo` block anymore.\n *\n * ```ts\n * // app/[[...slug]]/page.tsx — generateMetadata already applies this helper:\n * export const generateMetadata = route.generateMetadata\n * ```\n *\n * Mapping (in fallback order):\n * - `title`: `seoTitle` → document `name`/`title`.\n * - `description`: `seoDescription`.\n * - `openGraph`: OG title (`seoOgTitle` → effective title), description, the\n * OG image URL (`seoOgImage`) — and `locale` from the page's resolved locale\n * when, and only when, the declared code carries a territory (EC-531, narrowed by\n * EC-610). The site-wide half of Open Graph (`type`, `siteName`, the site image,\n * a bare-code site's `locale`) is the LAYOUT's: the canvas route merges this\n * object over the layout's resolved one (`mergeMetadataOverParent`), so a page\n * states only what is its own.\n * - `robots`: `seoNoindex` → `{ index: false }`.\n * - `alternates.canonical`: `seoCanonical` (EC-228) — an absolute or\n * root-relative URL, passed through verbatim. Real Sanity sites declare one per\n * page; omitted when unset so Next applies its own default.\n *\n * `hreflang` language alternates are intentionally NOT derived here: correct\n * hreflang needs a per-page translation map (which locale variants actually\n * exist), and auto-deriving from configured locales would emit alternates for\n * translations that don't exist — an SEO anti-pattern. Tracked as a follow-up.\n *\n * `seoOgImage` resolution (EC-202): the field holds either an absolute URL or\n * an **asset id**. An asset id is resolved to its delivery URL against the\n * page's baked `assets` (the same page-scoped set the renderer gets, widened to\n * include the OG asset in `route-core`). An asset id that can't be resolved is\n * **dropped** rather than emitted — an `<meta property=\"og:image\">` must carry a\n * real URL, never leak an internal id.\n *\n * Pure data-in/data-out — safe inside `generateMetadata` on both the cached\n * and uncached paths.\n */\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\n/** An already-resolvable image URL (absolute or root-relative), not an asset id. */\nfunction looksLikeUrl(value: string): boolean {\n return /^(https?:)?\\/\\//.test(value) || value.startsWith('/')\n}\n\n/**\n * Resolve a `seoOgImage` field value to a URL: a delivery-resolved asset OBJECT →\n * its `url` (EC-266); a URL string → passed through; a raw asset-id string →\n * resolved against the baked assets; anything unresolvable → dropped (EC-202).\n */\nfunction resolveOgImage(value: unknown, assets: readonly ResolvedAsset[]): string | null {\n // EC-266: a `seoOgImage` modelled as a `type:'asset'` FIELD (e.g. Konditorei,\n // migrated from Sanity) is resolved by delivery to a ResolvedAsset OBJECT\n // ({ id, url, … }), NOT a string — so read its `url`. A string value is the\n // legacy shape: a passthrough URL, or a raw asset id resolved against the baked\n // assets. A `str()`-only path turned the object into null, dropping og:image.\n if (value && typeof value === 'object') {\n const url = (value as { url?: unknown }).url\n return typeof url === 'string' && url.length > 0 ? url : null\n }\n if (typeof value !== 'string' || value.length === 0) return null\n if (looksLikeUrl(value)) return value\n return assets.find((asset) => asset.id === value)?.url ?? null\n}\n\n/**\n * EC-531, narrowed by EC-610 (owner ruling 3, 2026-10-23): `og:locale` ONLY in the\n * shape Open Graph specifies — `language_TERRITORY` with an underscore (`de_DE`),\n * where BCP 47 writes `de-DE`. The value is the page's RESOLVED locale\n * (`result.tags.locale`, the same fact the router used to pick the content). A\n * bare language code (`de`) is outside that format, so the kit emits nothing for\n * it and the layout's own value stands through the merge: inventing a territory\n * (`de` → `de_DE`) would be wrong exactly where it matters (Austria, the UK,\n * Brazil, Latin America), and a wrong valid value looks deliberate.\n */\nexport function openGraphLocale(locale: string): string | null {\n return locale.includes('-') ? locale.replace(/-/g, '_') : null\n}\n\nexport function canvasPageMetadata(\n result: ResolvePageResult,\n assets: readonly ResolvedAsset[] = [],\n): Metadata {\n const doc = result.document\n\n const seoTitle = str(doc?.['seoTitle'])\n const seoDescription = str(doc?.['seoDescription'])\n const seoOgTitle = str(doc?.['seoOgTitle'])\n const seoOgImage = resolveOgImage(doc?.['seoOgImage'], assets)\n const seoNoindex = doc?.['seoNoindex'] === true\n const seoCanonical = str(doc?.['seoCanonical'])\n\n // Fallback title: the page document's `name`, or a content document's `title`.\n const docTitle = str(doc?.['name']) ?? str(doc?.['title'])\n const title = seoTitle ?? docTitle\n\n const metadata: Metadata = {}\n if (title !== null) metadata.title = title\n if (seoDescription) metadata.description = seoDescription\n\n const ogTitle = seoOgTitle ?? title\n if (doc) {\n const ogLocale = openGraphLocale(result.tags.locale)\n metadata.openGraph = {\n ...(ogTitle !== null ? { title: ogTitle } : {}),\n ...(seoDescription ? { description: seoDescription } : {}),\n ...(seoOgImage ? { images: [{ url: seoOgImage }] } : {}),\n ...(ogLocale !== null ? { locale: ogLocale } : {}),\n }\n }\n\n if (seoNoindex) metadata.robots = { index: false }\n\n // EC-228: a page-declared canonical URL. Passed through verbatim (absolute or\n // root-relative); Next resolves it against `metadataBase` when relative.\n if (seoCanonical) metadata.alternates = { canonical: seoCanonical }\n\n return metadata\n}\n\n/**\n * EC-610 (owner ruling 1, 2026-10-13): **the site provides the default tags, and\n * the page's tags are merged on top** — a value the page states overrides the\n * site's, a value the page leaves empty falls back to the site's.\n *\n * Next merges metadata SHALLOWLY (read: `generate-metadata.md`, \"Merging\"): a\n * page segment's `openGraph` object REPLACES the layout's whole `openGraph`, so a\n * site-wide `type`, `siteName`, site image or `locale` declared once in the root\n * layout never reached a canvas page, and both real sites hand-rolled around it\n * differently (NK-039's page hook, Konditorei's post bypass). This is the one\n * merge, applied by the canvas route to whatever builds the page's metadata (the\n * kit's default or the host's `metadata` option): every object-valued key the\n * page sets (`openGraph`, `twitter`, `robots`, `alternates`, …) is merged one\n * level deep over the parent's RESOLVED value; a key the page does not set is left\n * to Next, which inherits it from the parent as before; a scalar or array the\n * page sets wins outright.\n *\n * The parent's resolved shapes spread back cleanly: Next hands `generateMetadata`\n * a `ResolvedMetadata` whose URLs are already strings, whose `images` are\n * descriptor objects and whose titles are `{ absolute, template }` — all of which\n * `Metadata` accepts (Next's own \"extend rather than replace\" example spreads\n * `(await parent).openGraph?.images`).\n */\nexport function mergeMetadataOverParent(\n parent: ResolvedMetadata | null | undefined,\n page: Metadata,\n): Metadata {\n if (!parent) return page\n const merged: Record<string, unknown> = { ...page }\n for (const [key, pageValue] of Object.entries(page)) {\n const parentValue = (parent as Record<string, unknown>)[key]\n if (isPlainObject(pageValue) && isPlainObject(parentValue)) {\n merged[key] = { ...parentValue, ...pageValue }\n }\n }\n return merged as Metadata\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","import type { Metadata, ResolvingMetadata } from 'next'\nimport { cacheLife, cacheTag } from 'next/cache'\nimport { draftMode } from 'next/headers'\nimport { notFound, permanentRedirect, redirect } from 'next/navigation'\nimport type { ReactNode } from 'react'\nimport type { CacheTags, Perspective, ResolvedAsset, ResolvePageResult } from '@elytracms/core/content'\nimport {\n canvasCacheKey,\n canvasEntryLife,\n canvasResolverFor,\n entryTagCalls,\n registerCanvasResolver,\n} from './cache'\nimport { CanvasRenderer } from './canvas-renderer'\nimport { canvasPageMetadata, mergeMetadataOverParent } from './metadata'\nimport type { HostComponents } from './components'\nimport { canvasWrapContext, pathFromSlug, resolveCanvasPage } from './route-core'\nimport type { CanvasPageOutcome, CanvasRequestInfo, CanvasWrapContext } from './route-core'\nimport type { ContentSource } from './source'\n\n/**\n * Catch-all route helper (EC-144): the factory a host app calls once in\n * `app/[[...slug]]/page.tsx` to wire `resolvePage` → `<CanvasRenderer />`,\n * with redirects answered by Next's `redirect()`/`permanentRedirect()` (per\n * the route's `permanent` flag), unknown URLs answered by `notFound()` (the\n * host's 404 renders), and the perspective chosen by Next `draftMode()` —\n * draft when enabled (via the token-gated preview route), published\n * otherwise.\n *\n * With `cache` configured (EC-146, EC-515), the published-perspective resolution\n * is ONE `'use cache'` entry per (scope, content mode, URL), and the entry declares\n * its own tags — the fetch-time tag set of what the response actually contained —\n * with `cacheTag` from inside the cached function (see `cache.ts`). Publishing in\n * the studio then invalidates exactly the affected entries via the signed\n * `/api/revalidate` webhook, on Vercel and on a self-hosted `next start` alike —\n * and while the content backend is unreachable, a cached entry keeps serving\n * because a hit never invokes `loadContent`. The host must enable\n * `cacheComponents: true` (`elytra doctor` checks it).\n */\n\n// EC-524: both context shapes and the wrap-context derivation live in the PURE\n// half (`route-core.ts`) — no Next request APIs are involved in either, and the\n// derivation earns a unit test there. Re-exported here because this module is\n// the published API surface hosts import from.\nexport type { CanvasRequestInfo, CanvasWrapContext } from './route-core'\n\nexport interface CanvasCacheOptions {\n /**\n * Project scope prefix for every tag (`p:<scope>:…`) — must be the SAME scope\n * the revalidate receiver derives its tags with (`createRevalidateRoute`'s\n * `scope`; the delivery kit hands its one `contentScope` to both, EC-579).\n */\n scope?: string\n /**\n * Which content world `loadContent` reads — the delivery kit passes its\n * `contentMode` (`'live' | 'fallback' | 'empty'`), so entries written in one\n * world can never be served in another (EC-543, see `canvasCacheKey`). A host\n * driving this helper directly with a single world may omit it.\n */\n mode?: string\n}\n\n/**\n * What the route helper tells the content loader about the request it is\n * loading for (EC-156). Loaders that serve both perspectives from one\n * prefetched source (the fixture snapshot) can ignore it; a live loader uses\n * it to fetch the draft-perspective snapshot only for draft-mode requests —\n * it must NOT call request APIs like `draftMode()` itself, because on the\n * cached published path the loader runs inside a `'use cache'` function, where\n * request APIs are unavailable.\n */\nexport interface CanvasContentRequest {\n perspective: Perspective\n}\n\nexport interface CanvasRouteOptions {\n /**\n * Async content loader — the seam EC-145/146 plug Convex into. For the\n * fixture-backed example this memoizes a `createContentSnapshot` call.\n * Invoked per request; cache/memoize inside the loader as appropriate.\n * With `cache` configured it only runs on a cache miss (once per\n * revalidation cycle) — cache hits never touch the content backend.\n * Receives the request's perspective (see {@link CanvasContentRequest});\n * zero-arg loaders remain valid.\n */\n loadContent: (request: CanvasContentRequest) => Promise<ContentSource>\n /** Host component surface from `defineHostComponents`. */\n components: HostComponents\n /**\n * EC-146 instant publish: cache the published-perspective resolution as a\n * `'use cache'` entry tagged with the response's own fetch-time tags,\n * invalidated by the `/api/revalidate` webhook (EC-515). Draft mode and\n * `next dev` always bypass it. Tag-driven only — no time-based revalidation\n * (the AD-5 model, `CANVAS_CACHE_LIFE`). Requires `cacheComponents: true` in\n * the host's `next.config`.\n */\n cache?: CanvasCacheOptions\n /**\n * EC-146 hook: receives the merged `CacheTags` of everything this request\n * read (the `resolvePage` tags plus the baked relations/listings/links). On a\n * cache hit these are the stored tags of the run that wrote the entry.\n */\n onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void\n /** Override the generated `<head>` metadata for a resolved page. */\n metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata\n /**\n * Wrap the rendered page in the host's own chrome (EC-524) — a detail\n * template, a per-collection layout, a draft ribbon. Receives the rendered\n * canvas as `children` and returns whatever should stand in its place:\n *\n * ```tsx\n * elytra.canvasRoute({\n * wrap: (children, ctx) =>\n * ctx.collection === 'post' ? <PostDetail document={ctx.document}>{children}</PostDetail> : children,\n * })\n * ```\n *\n * **This is deliberately host code, not config** (owner ruling 2026-08-01):\n * *\"this sounds very much like page-templates … templates belong in code, so\n * the wrapper is something the developer has to care about … the page-content\n * (Canvas or Rich-Text) should render agnostically.\"* A collection's\n * `presentation.body` still names WHICH field is the content — that is content\n * selection, and it is what lets one catch-all render a canvas page and a\n * rich-text post through the same call. What surrounds that content is the\n * developer's implementation decision, branched here in their own file.\n *\n * It replaced a `presentation.wrapper` component-id STRING resolved from the\n * canvas registry, which forced a delivery-only component to register as a\n * pseudo-block (EC-514). A function beats that on every axis that matters: it\n * cannot dangle, the compiler checks it, and it can branch on anything in\n * {@link CanvasWrapContext} rather than on collection identity alone.\n *\n * Applied to the `render` outcome only — a redirect or a 404 never reaches it.\n * Returning `children` unchanged is the no-op, and omitting `wrap` entirely is\n * the default.\n */\n wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode\n}\n\nexport interface CanvasRouteProps {\n params: Promise<{ slug?: string[] }>\n}\n\nexport interface CanvasRoute {\n /** The page component: `export default route.Page`. */\n Page: (props: CanvasRouteProps) => Promise<ReactNode>\n /**\n * Next metadata hook: `export const generateMetadata = route.generateMetadata`.\n * Next passes the parent segment's resolving metadata as the second argument;\n * the route merges the page's tags over it (EC-610). A caller that invokes this\n * by hand (the scaffold's `app/page.tsx`) forwards its own `parent`.\n *\n * `parent` is REQUIRED, not optional (EC-614): Next's webpack build validates a\n * page's `generateMetadata` with `SecondArg<…> extends ResolvingMetadata`, and an\n * optional parameter reads as `ResolvingMetadata | undefined`, which fails that\n * constraint — a consumer re-exporting this hook could not `next build --webpack`\n * on 0.0.88 (the Turbopack validator is lenient, which is how the gate missed it).\n */\n generateMetadata: (props: CanvasRouteProps, parent: ResolvingMetadata) => Promise<Metadata>\n /**\n * `export const generateStaticParams = route.generateStaticParams` — REQUIRED in\n * the catch-all page file under `cacheComponents` (EC-515, measured on Next\n * 16.2.10): without it, awaiting the catch-all's `params` fails `next build`\n * (\"Uncached data was accessed outside of <Suspense>\"). Wrapping the page in\n * `<Suspense>` (or a root `loading.tsx`) instead also builds, but answers every\n * 404 and every redirect with HTTP 200 — the status is committed before the\n * content resolves. Declaring params keeps the page blocking, so `notFound()` is a\n * real 404 and a redirect a real 308/307.\n *\n * It names ONE param set: {@link CANVAS_VALIDATION_SLUG}, a sample that Next\n * prerenders to validate the route and that resolves to not-found without\n * reading content (the build never touches the backend for it). It fits both\n * catch-all shapes (`[[...slug]]` and the scaffold's required `[...slug]`, which\n * rejects an empty slug), and it is never empty (Next rejects an empty list).\n * Every real URL renders on its first request and is cached from then on.\n */\n generateStaticParams: () => Promise<Array<{ slug: string[] }>>\n}\n\n/**\n * The catch-all's build-time validation sample (see\n * {@link CanvasRoute.generateStaticParams}). The path is reserved: the canvas\n * route answers it with not-found and never resolves it against content.\n */\nexport const CANVAS_VALIDATION_SLUG: readonly string[] = ['_elytra']\nconst CANVAS_VALIDATION_PATH = pathFromSlug([...CANVAS_VALIDATION_SLUG])\n\n/**\n * Default metadata (EC-171): the page's canonical SEO fields — title,\n * description, OG tags, robots noindex — with document-title / page-name\n * fallbacks. `export const generateMetadata = route.generateMetadata` is the\n * one line a host needs; a custom `metadata` option replaces this builder.\n * Either way the result is merged over the layout's resolved metadata (EC-610).\n */\nfunction defaultMetadata(result: ResolvePageResult, assets: readonly ResolvedAsset[]): Metadata {\n return canvasPageMetadata(result, assets)\n}\n\nasync function requestPerspective(): Promise<Perspective> {\n const { isEnabled } = await draftMode()\n return isEnabled ? 'draft' : 'published'\n}\n\n/**\n * THE delivery cache entry (EC-515, option D): one published resolution per\n * (scope, content mode, URL) — the arguments, which are the whole key\n * (`canvasCacheKey`).\n *\n * At module level with serializable arguments only: a `'use cache'` function's\n * closed-over values become part of its key and must serialize, and the loader is\n * a function — so the loader is registered per (scope, mode) by\n * {@link createCanvasRoute} and looked up here (`canvasResolverFor`).\n *\n * The entry declares its own tags from inside the function, AFTER the load —\n * exactly the fetch-time tag set of what the resolution read — and Next judges an\n * entry by the tags it was stored with. That is the whole of the fix for EC-515's\n * defect 1: the probe/bail two-pass that `unstable_cache` forced (the reading\n * wrapper had to name tags it could not know before loading) is gone, because\n * there is no longer a reading wrapper with tags of its own.\n *\n * Its life is applied after the load (`canvasEntryLife`): tag-only in production,\n * one second under `next dev`, a minute when the resolution read the clock (a\n * `now`-bound listing, EC-581 6a — `readsNow`).\n */\nasync function cachedCanvasOutcome(scope: string, mode: string, url: string): Promise<CanvasPageOutcome> {\n 'use cache'\n const outcome = await canvasResolverFor<CanvasPageOutcome>(scope, mode)(url)\n cacheLife(canvasEntryLife(process.env.NODE_ENV, outcome.kind === 'render' && outcome.readsNow))\n for (const tags of entryTagCalls(outcome.tags, scope)) cacheTag(...tags)\n return outcome\n}\n\n/** Create the `{ Page, generateMetadata }` pair for `app/[[...slug]]/page.tsx`. */\nexport function createCanvasRoute(options: CanvasRouteOptions): CanvasRoute {\n const resolveFresh = async (url: string, perspective: Perspective): Promise<CanvasPageOutcome> => {\n const source = await options.loadContent({ perspective })\n return resolveCanvasPage(source, url, perspective, {\n // EC-205: page-scope the baked assets to what the page actually uses.\n registry: options.components.registry,\n })\n }\n\n const cache = options.cache\n const entryKey = (url: string) => canvasCacheKey({ scope: cache?.scope, mode: cache?.mode, url })\n if (cache) {\n // The cached function reaches this route's loader through the registry (it\n // cannot close over it) — keyed by the same (scope, mode) its entries are.\n const [scope, mode] = entryKey('')\n registerCanvasResolver(scope, mode, (url) => resolveFresh(url, 'published'))\n }\n\n /**\n * Resolve one request, through the cache entry when it applies. Shared by\n * `Page` and `generateMetadata`, so both read (and on a miss, write) the same\n * entry.\n */\n const loadOutcome = async (url: string, perspective: Perspective): Promise<CanvasPageOutcome> => {\n // Draft mode BYPASSES the cache entirely: draft content is per-editor,\n // changes on every keystroke, and must never be persisted into (or served\n // from) the shared published-data cache. (Next would not persist a\n // `'use cache'` entry under draft mode either — this keeps the draft read off\n // that path altogether.)\n if (!cache || perspective !== 'published') return resolveFresh(url, perspective)\n // `next dev` reads through the entry too — with a one-second life instead of a\n // bypass (`canvasEntryLife`).\n return cachedCanvasOutcome(...entryKey(url))\n }\n\n const Page = async (props: CanvasRouteProps): Promise<ReactNode> => {\n const { slug } = await props.params\n const url = pathFromSlug(slug)\n if (url === CANVAS_VALIDATION_PATH) return notFound()\n const perspective = await requestPerspective()\n\n const outcome = await loadOutcome(url, perspective)\n options.onTags?.(outcome.tags, { url, perspective })\n\n switch (outcome.kind) {\n case 'redirect':\n // 301 vs 302 per the redirect record's `permanent` flag (EC-019/020).\n return outcome.permanent ? permanentRedirect(outcome.target) : redirect(outcome.target)\n case 'not-found':\n return notFound()\n case 'render': {\n const rendered = (\n <CanvasRenderer\n result={outcome.result}\n components={options.components}\n assets={outcome.assets}\n relations={outcome.relations}\n listings={outcome.listings}\n links={outcome.links}\n // EC-499: the resolved collection's declared body, so this ONE\n // catch-all renders a composition page and a rich-text post detail\n // alike — no per-collection Next file route needed.\n presentation={outcome.presentation}\n />\n )\n // EC-524: the host's own chrome goes AROUND the renderer, never inside\n // it. `children` is the (async) canvas element — React composes it in\n // place, so a wrapper may be a server or a client component.\n if (!options.wrap) return rendered\n return options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective }))\n }\n }\n }\n\n const pageMetadata = async (props: CanvasRouteProps): Promise<Metadata> => {\n const { slug } = await props.params\n const url = pathFromSlug(slug)\n if (url === CANVAS_VALIDATION_PATH) return {}\n const perspective = await requestPerspective()\n\n // Cached path: shares `Page`'s entry (same function, same arguments), so\n // metadata is a cache read, not a second resolution. Uncached path: resolve\n // without the payload factory — metadata only needs the resolution itself,\n // and side effects (addTags/onTags) must fire exactly once per request, in Page.\n if (cache && perspective === 'published') {\n const outcome = await loadOutcome(url, perspective)\n if (outcome.kind !== 'render') return {}\n return options.metadata\n ? options.metadata(outcome.result, { url, perspective })\n : defaultMetadata(outcome.result, outcome.assets)\n }\n\n const source = await options.loadContent({ perspective })\n const outcome = await resolveCanvasPage(source, url, perspective)\n if (outcome.kind !== 'render') return {}\n return options.metadata\n ? options.metadata(outcome.result, { url, perspective })\n : defaultMetadata(outcome.result, outcome.assets)\n }\n\n // EC-610: the page's tags over the site's. `parent` is the layout chain's\n // resolved metadata, which Next otherwise merges only one key deep — a\n // page-level `openGraph` would erase the layout's `type`, `siteName`, site\n // image and `locale` on every canvas page.\n const generateMetadata = async (\n props: CanvasRouteProps,\n parent: ResolvingMetadata,\n ): Promise<Metadata> => {\n const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent])\n return mergeMetadataOverParent(resolvedParent, page)\n }\n\n const generateStaticParams = async (): Promise<Array<{ slug: string[] }>> => [\n { slug: [...CANVAS_VALIDATION_SLUG] },\n ]\n\n return { Page, generateMetadata, generateStaticParams }\n}\n","import type { MetadataRoute } from 'next'\nimport { composePath, hierarchyMount, splitPath } from '@elytracms/core/cms-core'\nimport type { CmsDocument, Locale, RouteRecord } from '@elytracms/core/cms-core'\nimport { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph'\nimport type { ContentClient } from '@elytracms/core/content'\nimport { localeUrlPath } from './locale-url'\nimport type { ContentSource } from './source'\n\n/**\n * `sitemap.xml` from published routes (EC-171): walk the same route records\n * `resolvePage` resolves against, resolve every candidate URL in the\n * **published** perspective, and emit `MetadataRoute.Sitemap` entries per\n * locale — Next's `app/sitemap.ts` convention. Pages marked `noindex` (and\n * URLs that do not resolve, e.g. a page that was never published) are\n * omitted.\n *\n * Dynamic routes (`/blog/:slug`): a route record does not declare which\n * collection feeds its params, so enumeration is delegated to the host's\n * {@link SitemapParamsProvider} — it gets a published-perspective accessor\n * client and returns one param record per concrete URL (typically from\n * `listDocuments`). **Honest limit:** parameterized routes without a provider\n * (or provider entries missing a param) are skipped and reported in\n * `skipped`, never guessed. `lastModified` is omitted — the delivery shape\n * carries no per-page timestamps today.\n *\n * Hierarchy mounts (`/:path*`, EC-218): a page-tree mount self-describes its\n * URLs, so it is enumerated automatically from the collection's published\n * documents (composing each page's nested path from its parent chain) — no\n * params provider entry needed.\n *\n * **Enumeration is a candidate list, not the output (EC-486).** Every branch\n * above — static, provider-fed, and mount — only proposes paths; each one is\n * then put back through `resolvePage`, and a path the resolver refuses is\n * dropped. So the sitemap cannot claim a URL the delivery host would 404, even\n * though the mount branch enumerates its collection independently of route\n * precedence. Do not read the enumeration in isolation: the gate below it is\n * what makes the two agree. What a mount enumerated and the resolver then\n * refused is reported in {@link CanvasSitemapResult.skipped}, never dropped in\n * silence.\n *\n * Host usage (`app/sitemap.ts`):\n *\n * ```ts\n * import { createCanvasSitemap } from '@elytracms/next'\n * import { loadContent } from '../lib/content'\n *\n * export default createCanvasSitemap({\n * loadContent,\n * baseUrl: 'https://example.com',\n * params: ({ route, client }) =>\n * route.id === 'r-post'\n * ? (client.listDocuments('post').ok ? … : []) // slug per published post\n * : null,\n * })\n * ```\n */\n\nexport interface SitemapRouteContext {\n route: RouteRecord\n locale: Locale\n /** Published-perspective accessor client bound to `locale`. */\n client: ContentClient\n}\n\n/**\n * Enumerate the param sets of one dynamic route — one record per concrete\n * URL (e.g. `[{ slug: 'hello-world' }, …]`). Return `null`/`undefined` to\n * skip the route (reported in `skipped`).\n */\nexport type SitemapParamsProvider = (\n ctx: SitemapRouteContext,\n) => readonly Record<string, string>[] | null | undefined\n\nexport interface CanvasSitemapOptions {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n baseUrl: string\n /** Route records to enumerate; defaults to the source's own route table. */\n routes?: readonly RouteRecord[]\n /** Param enumeration for dynamic routes (see {@link SitemapParamsProvider}). */\n params?: SitemapParamsProvider\n}\n\nexport interface SitemapSkippedRoute {\n routeId: string\n pattern: string\n reason: string\n}\n\nexport interface CanvasSitemapResult {\n entries: MetadataRoute.Sitemap\n /** Routes that could not be enumerated — explicit, never silent. */\n skipped: SitemapSkippedRoute[]\n}\n\nfunction paramNamesOf(pattern: string): string[] {\n return splitPath(pattern)\n .filter((segment) => segment.startsWith(':'))\n .map((segment) => segment.slice(1))\n}\n\n/** Substitute `:name` segments; `null` when a param is missing from the record. */\nfunction fillPattern(pattern: string, params: Record<string, string>): string | null {\n const segments: string[] = []\n for (const segment of splitPath(pattern)) {\n if (!segment.startsWith(':')) {\n segments.push(segment)\n continue\n }\n const value = params[segment.slice(1)]\n if (value === undefined || value.length === 0) return null\n segments.push(encodeURIComponent(value))\n }\n return '/' + segments.join('/')\n}\n\n/** The origin half; the locale ⇄ path half is `localeUrlPath` (EC-486). */\nfunction originOf(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, '')\n}\n\n/**\n * Enumerate the sitemap entries of one content source (published\n * perspective). Pure over the prefetched source — synchronous accessors,\n * deterministic ordering (sorted by URL).\n */\nexport function canvasSitemapEntries(\n source: ContentSource,\n options: CanvasSitemapOptions,\n): CanvasSitemapResult {\n const routes = options.routes ?? source.routes ?? []\n const locales = source.locales\n const skipped: SitemapSkippedRoute[] = []\n const skip = (route: RouteRecord, reason: string): void => {\n if (!skipped.some((s) => s.routeId === route.id && s.reason === reason)) {\n skipped.push({ routeId: route.id, pattern: route.pattern, reason })\n }\n }\n\n const clients = new Map<Locale, ContentClient>()\n const clientFor = (locale: Locale): ContentClient => {\n let client = clients.get(locale)\n if (!client) {\n client = source.client({ perspective: 'published', locale })\n clients.set(locale, client)\n }\n return client\n }\n\n // EC-517 — two maps, because the ENTRY set and the ALTERNATES set are keyed on\n // different facts and conflating them lost `hreflang` exactly where it mattered.\n //\n // `byUrl` one entry per addressable URL, deduped — what gets published.\n // `byGroup` the locales of ONE DOCUMENT, whatever paths they compose — what\n // each entry advertises as its alternates.\n //\n // Until EC-517 both jobs were done by a single map keyed on the composed path.\n // Two locales of one document only share a path while the localized slug is the\n // same string, so a template route (`/blog/hello` in every locale) alternated\n // correctly while a hierarchy mount with a genuinely translated slug composed\n // `/about` and `/ueber-uns`, formed two groups of one, and emitted nothing. The\n // failure was silent and inverted: the feature worked while the localization did\n // nothing and broke for the consumer who used it properly.\n const byUrl = new Map<string, { group: string; locale: Locale }>()\n const byGroup = new Map<string, Map<Locale, string>>()\n\n for (const route of routes) {\n const paramNames = paramNamesOf(route.pattern)\n // EC-218: a hierarchy mount (`/:path*`) serves a collection's pages by their\n // COMPOSED nested path — it self-describes its tree, so it is enumerated from\n // the collection's published documents, not the host's params provider.\n const mount = hierarchyMount(route)\n const routeLocales = route.locale ? [route.locale] : locales.locales\n\n for (const locale of routeLocales) {\n const client = clientFor(locale)\n\n let paths: string[]\n if (mount) {\n const listed = client.listDocuments(mount.collection)\n if (!listed.ok) {\n skip(route, `hierarchy collection \"${mount.collection}\" could not be listed`)\n continue\n }\n // Delivery docs are flat (`{ id, slug, parent, … }`); rebuild the minimal\n // CmsDocument shape composePath reads. Published perspective already\n // filtered to published rows, so a child whose parent is unpublished has a\n // dangling chain and drops out — exactly as live resolution would 404 it.\n const rows: CmsDocument[] = listed.documents.map((doc) => ({\n collection: mount.collection,\n id: doc.id,\n values: { slug: doc['slug'], parent: doc['parent'] },\n }))\n const byId = new Map(rows.map((row) => [row.id, row]))\n paths = []\n for (const row of rows) {\n // Only documents with their own slug are addressed through the mount\n // (a page routed explicitly elsewhere carries none).\n if (typeof row.values['slug'] !== 'string' || row.values['slug'] === '') continue\n const composed = composePath(row, (id) => byId.get(id))\n if (!composed.ok) continue\n paths.push('/' + [...mount.prefix, ...composed.segments].join('/'))\n }\n } else if (paramNames.length === 0) {\n paths = [route.pattern]\n } else {\n const paramSets = options.params?.({ route, locale, client })\n if (paramSets == null) {\n skip(route, 'dynamic route not enumerated (no params provider entry)')\n continue\n }\n paths = []\n for (const paramSet of paramSets) {\n const path = fillPattern(route.pattern, paramSet)\n if (path === null) {\n skip(route, `params entry missing one of :${paramNames.join(', :')}`)\n continue\n }\n paths.push(path)\n }\n }\n\n // EC-486: how many enumerated paths the RESOLVER refused. A mount that\n // composed paths but resolves none of them is a dead declaration (its\n // collection is not hierarchical, or it is shadowed) — the sitemap then\n // publishes nothing for it, and without this it would say nothing either.\n let unresolved = 0\n\n for (const path of paths) {\n // Same resolution the live request performs: only URLs that resolve\n // `ok` in the published perspective — and are not noindex — qualify.\n // Redirect-shadowed paths drop out (their targets are listed via\n // their own routes), and so do routes whose bound page/template is\n // not visible in the published graph (never published).\n const result = client.resolvePage(path, locale)\n // A document-bound route whose document is not published already\n // resolves `notFound` (EC-177 gap 6), so `status !== 'ok'` covers the\n // never-published case. A `page`-collection document carries its own\n // `seoNoindex` flat field (EC-187); honor it for the page sitemap.\n if (result.status !== 'ok') {\n // A `redirect` is a deliberate authoring act, not a dead route.\n if (result.status !== 'redirect') unresolved += 1\n continue\n }\n if (result.document?.['seoNoindex'] === true) continue\n // EC-486: the URL a crawler will actually request. `resolvePage` above\n // was asked for `path` in `locale`, but the host re-derives BOTH from\n // the URL — and for a path whose first segment is another locale's code\n // that derivation lands somewhere else, so no URL addresses this page.\n // Publishing one anyway would submit a guaranteed 404 to search engines.\n const { urlPath, addressable } = localeUrlPath(path, locale, locales)\n if (!addressable) {\n skip(\n route,\n `\"${path}\" (locale \"${locale}\") has no addressable URL — its first segment is read as a locale prefix`,\n )\n continue\n }\n const absolute = originOf(options.baseUrl) + urlPath\n // EC-517: group by the DOCUMENT this URL resolves to — the fact that is\n // actually locale-invariant. Scoped by route id so a document reachable\n // through two routes still yields two groups, each alternating within\n // itself, rather than one route's URL advertising another's. A route that\n // resolves no document (a template-bound pattern) keeps the old path key,\n // which is already locale-invariant for it.\n const group = result.document\n ? `${route.id}${COMPOSITE_KEY_SEPARATOR}doc${COMPOSITE_KEY_SEPARATOR}${result.document.collection}${COMPOSITE_KEY_SEPARATOR}${result.document.id}`\n : `${route.id}${COMPOSITE_KEY_SEPARATOR}path${COMPOSITE_KEY_SEPARATOR}${path}`\n if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale })\n const perLocale = byGroup.get(group) ?? new Map<Locale, string>()\n if (!perLocale.has(locale)) perLocale.set(locale, absolute)\n byGroup.set(group, perLocale)\n }\n\n if (mount && paths.length > 0 && unresolved === paths.length) {\n skip(\n route,\n `hierarchy mount over \"${mount.collection}\" composed ${paths.length} path(s) in locale \"${locale}\", none of which the resolver serves`,\n )\n }\n }\n }\n\n const entries: MetadataRoute.Sitemap = []\n for (const [url, { group }] of byUrl) {\n const perLocale = byGroup.get(group)\n // A single-locale group is a page that exists in one language; `hreflang`\n // between one URL and itself says nothing, so it stays off.\n const languages = perLocale && perLocale.size > 1 ? Object.fromEntries(perLocale) : undefined\n entries.push({ url, ...(languages ? { alternates: { languages } } : {}) })\n }\n entries.sort((a, b) => (a.url < b.url ? -1 : a.url > b.url ? 1 : 0))\n\n return { entries, skipped }\n}\n\nexport interface CreateCanvasSitemapOptions extends CanvasSitemapOptions {\n /** The same content loader the catch-all route uses (published perspective). */\n loadContent: (request: { perspective: 'published' }) => Promise<ContentSource>\n}\n\n/**\n * The `app/sitemap.ts` one-liner: `export default createCanvasSitemap({ … })`.\n * Loads the published content source and returns the enumerated entries\n * (skipped routes are dropped here — use {@link canvasSitemapEntries} to\n * inspect them).\n */\nexport function createCanvasSitemap(\n options: CreateCanvasSitemapOptions,\n): () => Promise<MetadataRoute.Sitemap> {\n return async () => {\n const source = await options.loadContent({ perspective: 'published' })\n return canvasSitemapEntries(source, options).entries\n }\n}\n","import { readRevalidationBody, verifyRevalidationSignature } from '@elytracms/core/revalidation'\nimport type { RevalidationEvent } from '@elytracms/core/revalidation'\n\n/**\n * Pure request logic of the revalidation webhook receiver (EC-146), kept free of\n * Next.js imports so it is unit-testable. `revalidate.ts` wires it into a route\n * handler with `revalidateTag`.\n *\n * The wire — event schema, signer, header — is NOT declared here: it is the one\n * module `@elytracms/core/revalidation`, shared with the emitter (the studio's\n * Convex write chokepoint) and the doctor probe (EC-579). This file only decides\n * the HTTP answer: auth before parsing, then the event.\n */\n\nexport type RevalidateEvaluation =\n | { ok: true; event: RevalidationEvent }\n | { ok: false; status: 400 | 401; message: string }\n\n/**\n * Evaluate one webhook request: verify the HMAC first (auth before parsing), then\n * read the event.\n *\n * - unconfigured (empty) secret → 401 for every request: the endpoint stays closed\n * until a secret is deliberately configured (mirrors EC-144 preview);\n * - missing/malformed/mismatched signature → 401 (constant-time verify);\n * - unparseable or schema-invalid body → 400, nothing revalidated; a body in the\n * pre-EC-579 tag-list shape gets its own named 400 (an older Elytra emitter —\n * deploy the matching studio functions), because a version skew between the\n * Convex deployment and this host is the one way a correctly signed body can\n * arrive in the wrong shape.\n */\nexport async function evaluateRevalidateRequest(\n rawBody: string,\n signature: string | null | undefined,\n secret: string | undefined,\n): Promise<RevalidateEvaluation> {\n if (secret === undefined || secret.length === 0) {\n return { ok: false, status: 401, message: 'Revalidation webhook is not configured.' }\n }\n if (typeof signature !== 'string' || signature.length === 0) {\n return { ok: false, status: 401, message: 'Missing or malformed webhook signature.' }\n }\n if (!(await verifyRevalidationSignature(rawBody, signature, secret))) {\n return { ok: false, status: 401, message: 'Invalid webhook signature.' }\n }\n const read = readRevalidationBody(rawBody)\n if (!read.ok) return { ok: false, status: 400, message: read.message }\n return { ok: true, event: read.event }\n}\n","import { revalidateTag } from 'next/cache'\nimport { REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation'\nimport { revalidationTags } from './cache'\nimport type { RevalidationProject } from './cache'\nimport { evaluateRevalidateRequest } from './revalidate-core'\n\n/**\n * Revalidation webhook route handler (EC-146): the receiving end of the\n * publish → live-in-seconds pipeline. The studio's Convex write chokepoint — the ONE\n * emitter (EC-579) — POSTs a signed EVENT (`{ kind, collection?, id? }`, the wire in\n * `@elytracms/core/revalidation`); this handler verifies the HMAC over the raw body,\n * derives the tags from the event with this host's OWN scope and locales\n * (`revalidationTags`, `cache.ts`) and calls `revalidateTag` for each, dropping the\n * cached entries whose fetch-time tag sets contained them.\n *\n * Server-to-server only: the emitter runs in Convex, so there is no CORS and no\n * `OPTIONS` preflight (the studio's browser dispatcher that needed them is deleted).\n * Server-only by construction — consumed from an App Router route handler file,\n * never from client bundles.\n */\n\nexport interface RevalidateRouteOptions extends RevalidationProject {\n /**\n * Shared HMAC secret. An empty/undefined secret rejects every request with\n * 401 — the endpoint stays closed until deliberately configured.\n */\n secret: string | undefined\n}\n\nexport interface RevalidateRouteHandlers {\n POST(request: Request): Promise<Response>\n}\n\n/**\n * Create the webhook route handler for `app/api/revalidate/route.ts`. Through the\n * delivery kit it is `export const { POST } = elytra.revalidateRoute()`, which hands\n * it the kit's own scope and locales — the ones its cached entries carry. Wiring it\n * by hand, pass the SAME scope your canvas route caches under:\n *\n * ```ts\n * export const { POST } = createRevalidateRoute({\n * secret: process.env.REVALIDATE_SECRET,\n * scope: process.env.ELYTRA_PROJECT_ID,\n * locales: ['en', 'de'],\n * })\n * ```\n *\n * Unsigned/invalid requests are rejected with 401 (constant-time HMAC verify, see\n * `evaluateRevalidateRequest`), signed-but-unreadable ones with 400; a valid event\n * revalidates its derived tags and answers `{ revalidated: tags }` (`[]` for the\n * doctor's `probe`).\n */\nexport function createRevalidateRoute(options: RevalidateRouteOptions): RevalidateRouteHandlers {\n const project: RevalidationProject = { scope: options.scope, locales: [...options.locales] }\n return {\n async POST(request: Request): Promise<Response> {\n const rawBody = await request.text()\n const signature = request.headers.get(REVALIDATE_SIGNATURE_HEADER)\n const evaluation = await evaluateRevalidateRequest(rawBody, signature, options.secret)\n if (!evaluation.ok) {\n return Response.json({ error: evaluation.message }, { status: evaluation.status })\n }\n const tags = revalidationTags(evaluation.event, project)\n // EC-515: `{ expire: 0 }`, NOT `'max'`. Next 16 requires a cache-life\n // profile as the second argument, and EC-219 reached for `'max'` on the\n // claim that it \"preserves the legacy drop-by-tag semantics\". **Measured\n // false on Next 16.2.10** — `'max'` is the *opposite* of a drop:\n //\n // `revalidateTag(tag, profile)` passes `{ expire: <profile>.expire }`\n // down to the cache handler. The `max` profile is `expire: 60*60*24*365`\n // (`server/config-shared.js`), so the default self-hosted handler stamps\n // `{ stale: now, expired: now + 365 days }` on the tag\n // (`FileSystemCache.revalidateTag`; the `'use cache'` handler stamps the\n // same way). `areTagsExpired` requires `expired <= now` — a year in the\n // future never qualifies — so the entry is only marked STALE: the next\n // visitor is served the old page while a background refresh runs. Under\n // the pre-EC-515 `unstable_cache` probe/bail two-pass that refresh could\n // never complete, so the stale value was served forever (measured).\n //\n // `{ expire: 0 }` stamps `expired: now`, which `areTagsExpired` treats as\n // an immediate hard drop — byte-for-byte the behaviour of the deprecated\n // one-argument form: the request after a publish is fresh. `CacheLifeConfig`\n // is a public part of the `revalidateTag` signature, so this is not a cast\n // or a back door. `updateTag` is still not an option — Server-Action-only,\n // throws here.\n //\n // Measured (self-hosted `next start`, Next 16.2.10): with `{ expire: 0 }`\n // every event's entries are fresh on the next request — the EC-515 case\n // table, `'use cache'` entries, Turbopack and webpack.\n for (const tag of tags) revalidateTag(tag, { expire: 0 })\n return Response.json({ revalidated: tags })\n },\n }\n}\n","import NextImage from 'next/image'\nimport type { ImageLoader } from 'next/image'\nimport type { ReactNode } from 'react'\nimport type { ResolvedAsset } from '@elytracms/core/content'\nimport { normalizeImageSource } from '@elytracms/components'\nimport { registerComponents } from '@elytracms/core/runtime-renderer'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\n// TYPE-ONLY by design: importing `./host-image` at runtime would bundle a second\n// singleton instance into the server entry (see host-image.ts module docs).\nimport type { HostImageRenderer } from './host-image'\n\n/**\n * Image delivery for the embedded runtime (EC-152).\n *\n * ## The honest v1 design\n *\n * Stored asset bytes are served from plain blob-storage URLs (Convex file\n * storage serve URLs in production). Those URLs do **not** transform — there\n * is no `?w=`/`?q=` resizing service behind them, and no image CDN build-out\n * in v1. The standard, no-CDN path is therefore `next/image` with its\n * **default loader**: the host app's own Next.js optimizer fetches the remote\n * asset URL, resizes/re-encodes it, and serves the variants same-origin from\n * `/_next/image`. That is exactly what {@link ElytraImage} does.\n *\n * For that to work the host app must allowlist the asset origin in\n * `next.config.mjs`:\n *\n * ```js\n * const nextConfig = {\n * images: {\n * remotePatterns: [\n * // Convex file storage serve URLs:\n * { protocol: 'https', hostname: '*.convex.cloud' },\n * ],\n * },\n * }\n * ```\n *\n * {@link createAssetImageLoader} exists for the day the asset URLs sit behind\n * a CDN that *does* honor width/quality query params — it appends them and\n * nothing more. Pointing it at raw Convex serve URLs would silently serve\n * full-size bytes while pretending to resize, so it is **not** the default.\n */\n\n/**\n * The slice of the {@link ResolvedAsset} delivery shape an image needs:\n * the resolvable URL, intrinsic dimensions for layout stability, and alt\n * text. Structurally satisfied by every `ResolvedAsset`.\n */\nexport type ElytraImageAsset = Pick<\n ResolvedAsset,\n 'url' | 'width' | 'height' | 'alt' | 'focalPoint'\n> & {\n /**\n * LQIP blur-placeholder data URI (EC-420) → `next/image` `blurDataURL`. Optional\n * so existing call sites are unaffected; when present, the optimized image blurs\n * up while loading. `null`/absent = today's behaviour (no placeholder).\n */\n lqip?: string | null\n}\n\nexport interface ElytraImageProps {\n /** The resolved asset (delivery shape). `null` renders nothing. */\n asset: ElytraImageAsset | null | undefined\n /** `sizes` hint forwarded to `next/image` for responsive variants. */\n sizes?: string\n /** Preload hint forwarded to `next/image` (above-the-fold images). */\n priority?: boolean\n /** Quality (1–100) forwarded to `next/image`; Next defaults to 75. */\n quality?: number\n className?: string\n /**\n * Loading strategy forwarded to `next/image` (EC-428/NK-026). `'eager'` opts an\n * image out of lazy-loading without the preload `priority` implies. Next throws\n * on `priority` + `loading='lazy'` together, so {@link ElytraImage} drops `lazy`\n * when `priority` is set (priority wins) — delivery never crashes.\n */\n loading?: 'eager' | 'lazy'\n /**\n * Custom `next/image` loader (e.g. from {@link createAssetImageLoader})\n * when assets sit behind a transforming CDN. Omit for the v1 default:\n * Next's own optimizer.\n */\n loader?: ImageLoader\n}\n\n/** LQIP raster long edge (EC-420). MUST equal studio `LQIP_MAX_EDGE` (thumbnail.ts). */\nconst LQIP_MAX_EDGE = 24\n\n/**\n * Recompute the EC-420 LQIP raster's pixel dimensions from the asset's intrinsic\n * size — the SAME `fitLongEdge` math the generator uses\n * (`packages/studio/src/lib/assets/thumbnail.ts:64-67`), duplicated here (3 lines)\n * because `packages/next` must NOT import studio internals (EC-428 AD-3). Fits the\n * long edge to {@link LQIP_MAX_EDGE}, rounds, floors each edge to ≥1px, and NEVER\n * upscales — so a ≤24px source keeps its own dims (the case the aspect-ratio\n * approximation gets wrong). Deterministic from `width`/`height`, non-null on the\n * NextImage path (the only place the wrap runs).\n */\nfunction fitLqipLongEdge(width: number, height: number): { width: number; height: number } {\n const scale = Math.min(1, LQIP_MAX_EDGE / Math.max(width, height))\n return { width: Math.max(1, Math.round(width * scale)), height: Math.max(1, Math.round(height * scale)) }\n}\n\n/**\n * Pre-wrap a raster LQIP data URI in an SVG that reproduces Next's OWN\n * static-import blur strength (EC-428 AD-2, ported from the NK-029 reference).\n *\n * Next's `getImageBlurSvg` only scales its blur (`blurWidth * 40` viewBox) for\n * **static imports** that carry `blurWidth`/`blurHeight`; a runtime `blurDataURL`\n * falls back to `stdDeviation=20` over the image's INTRINSIC-size viewBox — on a\n * large hero that is effectively no blur, so the ≤24px LQIP shows raw pixel blocks.\n * There is no public API to pass `blurWidth`/`blurHeight` for runtime URLs, so we\n * reproduce the scaled path ourselves: `viewBox = lqipDims * 40`, Next's identical\n * alpha-safe filter chain (feGaussianBlur → alpha-clamp feColorMatrix → feFlood/\n * feComposite `out` → composite over SourceGraphic → feGaussianBlur), and an\n * `<image preserveAspectRatio='none'>` over the original LQIP.\n *\n * Base64-encoded (`data:image/svg+xml;base64,`), NEVER percent-encoding: Next nests\n * this whole URI as its outer SVG's `<image href>`, and the browser percent-decodes\n * the outer data URI exactly once — percent-encoded nested `%xx` would corrupt. The\n * SVG string is pure ASCII (the inner `href` is itself a base64 data URI), so `btoa`\n * (a global in every runtime this targets — browser, Node ≥16, edge) is safe.\n */\nexport function scaledLqipBlur(lqip: string, width: number, height: number): string {\n const { width: lqipW, height: lqipH } = fitLqipLongEdge(width, height)\n const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${lqipW * 40} ${lqipH * 40}'><filter id='b' color-interpolation-filters='sRGB'><feGaussianBlur stdDeviation='20'/><feColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/><feFlood x='0' y='0' width='100%' height='100%'/><feComposite operator='out' in='s'/><feComposite in2='SourceGraphic'/><feGaussianBlur stdDeviation='20'/></filter><image width='100%' height='100%' x='0' y='0' preserveAspectRatio='none' style='filter:url(#b)' href='${lqip}'/></svg>`\n return `data:image/svg+xml;base64,${btoa(svg)}`\n}\n\n/**\n * Render a resolved asset through `next/image` (EC-152).\n *\n * - With known intrinsic dimensions the optimizer serves resized variants\n * and the reserved width/height prevent layout shift.\n * - Without dimensions `next/image` cannot render (it requires `width` +\n * `height` or `fill`), so the component degrades to a plain `<img>` —\n * un-optimized but visible, never a crash. Records written by the EC-151\n * upload flow always carry detected dimensions.\n * - A `null`/empty asset renders nothing (missing assets surface as\n * structured `unknown-asset` validation issues upstream, not here).\n */\nexport function ElytraImage(props: ElytraImageProps): ReactNode {\n const { asset, sizes, priority, quality, className, loader, loading } = props\n if (!asset || asset.url.length === 0) return null\n const alt = asset.alt ?? ''\n // EC-230: a focal point becomes CSS `object-position`, so an art-directed image\n // keeps its subject in frame when a layout crops it (`object-fit: cover`).\n // Inert under the default fit — harmless to set, correct once a layout crops.\n const objectPosition = asset.focalPoint\n ? `${asset.focalPoint.x * 100}% ${asset.focalPoint.y * 100}%`\n : undefined\n if (asset.width === null || asset.height === null) {\n // Honest fallback: next/image requires intrinsic dimensions; a\n // dimensionless record degrades to an unoptimized <img> instead of\n // crashing the page. No placeholder here — a plain <img> can't consume a\n // blurDataURL — and the wrap needs non-null dims, so it stays below this guard.\n return (\n <img\n data-ec-image=\"fallback\"\n src={asset.url}\n alt={alt}\n className={className}\n {...(objectPosition ? { style: { objectPosition } } : {})}\n />\n )\n }\n // EC-420 (AD-2): a resolved LQIP data URI becomes a next/image blur-up\n // placeholder — the image blurs while the optimized bytes stream in.\n // EC-428 (AD-2/AD-3): but Next only scales its blur for static imports; a runtime\n // blurDataURL under-blurs a large image to raw pixel blocks — so pre-wrap the\n // raster LQIP in a viewBox-scaled SVG using the recomputed LQIP dims. AD-4: an\n // lqip that is ALREADY an SVG (a future engine that pre-blurs, or NK's interim\n // consumer wrapper) passes through untouched. Absent lqip = pre-EC-420 behaviour.\n const blurDataURL =\n asset.lqip == null\n ? null\n : asset.lqip.startsWith('data:image/svg')\n ? asset.lqip\n : scaledLqipBlur(asset.lqip, asset.width, asset.height)\n const blur = blurDataURL ? { placeholder: 'blur' as const, blurDataURL } : {}\n // EC-428 (AD-5): forward `loading`, but Next throws when `priority` +\n // `loading='lazy'` arrive together (get-img-props.js). Priority wins; drop the\n // conflicting `lazy` so a priority image never crashes delivery.\n const loadingProp = priority && loading === 'lazy' ? undefined : loading\n return (\n <NextImage\n data-ec-image=\"next\"\n src={asset.url}\n alt={alt}\n width={asset.width}\n height={asset.height}\n {...(sizes !== undefined ? { sizes } : {})}\n {...(priority !== undefined ? { priority } : {})}\n {...(loadingProp !== undefined ? { loading: loadingProp } : {})}\n {...(quality !== undefined ? { quality } : {})}\n {...(className !== undefined ? { className } : {})}\n {...(loader !== undefined ? { loader } : {})}\n {...blur}\n {...(objectPosition ? { style: { objectPosition } } : {})}\n />\n )\n}\n\nexport interface AssetImageLoaderOptions {\n /** Query param name for the requested width. Default `\"w\"`. */\n widthParam?: string\n /** Query param name for the requested quality. Default `\"q\"`. */\n qualityParam?: string\n /** Quality used when `next/image` passes none. Default `75`. */\n defaultQuality?: number\n}\n\n/**\n * A `next/image` loader for asset URLs served through a transforming CDN:\n * it appends width/quality query params to the asset URL and returns it.\n *\n * Be honest about what this does: it only *requests* a transform. Plain\n * Convex file-storage serve URLs ignore these params and return the original\n * bytes, so this loader is only correct once the asset origin actually\n * resizes (e.g. an image CDN in front of storage). Until then, use the\n * default `next/image` loader (see module docs) — that is the v1 path.\n */\nexport function createAssetImageLoader(options: AssetImageLoaderOptions = {}): ImageLoader {\n const widthParam = options.widthParam ?? 'w'\n const qualityParam = options.qualityParam ?? 'q'\n const defaultQuality = options.defaultQuality ?? 75\n return ({ src, width, quality }) => {\n const separator = src.includes('?') ? '&' : '?'\n return `${src}${separator}${widthParam}=${width}&${qualityParam}=${quality ?? defaultQuality}`\n }\n}\n\n/**\n * Per-deployment image-delivery options for {@link withNextImage} (EC-271).\n * The default is the host's own Next.js optimizer (Vercel) — zero config. A host\n * on a different CDN supplies its own `loader` (a plain function) to route variants\n * through that CDN instead, without touching component code.\n */\nexport interface NextImageOptions {\n /**\n * Custom `next/image` loader — the override seam for non-Vercel hosting. Omit\n * for the Vercel default (Next's own `/_next/image` optimizer). Supply a\n * function to delegate resizing to another CDN:\n *\n * - a CDN that honors `?w=&q=` query params → {@link createAssetImageLoader};\n * - Cloudflare Images / Imgix / a bespoke transform → your own `ImageLoader`\n * (e.g. rewrite to `/cdn-cgi/image/width=<w>,quality=<q>/<src>`).\n *\n * The loader receives `{ src, width, quality }` and returns the variant URL.\n */\n loader?: ImageLoader\n /** Default responsive `sizes` hint when a block doesn't set one. */\n sizes?: string\n /** Default quality (1–100) when a block doesn't set one (Next defaults to 75). */\n quality?: number\n}\n\n/**\n * The image-role implementation for Next.js host apps, rendered optimized through\n * {@link ElytraImage}. It reads the image role's contract — the `src` asset prop\n * (`ROLE_CONTRACTS`), plus `alt`/`width`/`height`/`priority` as the shipped\n * `@elytracms/components` Image declares them. The renderer resolves an\n * `asset`-typed `src` (EC-195) to `{ url, width, height, alt }` before this runs,\n * so `normalizeImageSource` yields intrinsic dimensions (→ no layout shift, sized\n * variants) whether the editor picked an uploaded asset or pasted a URL.\n * `priority` opts an above-the-fold image out of lazy-loading.\n */\nfunction createNextImage(options: NextImageOptions) {\n return function ElytraNextImage(props: Record<string, unknown>): ReactNode {\n const { url, width, height, alt, focalPoint, lqip } = normalizeImageSource(props)\n if (url.length === 0) return null\n return (\n <ElytraImage\n asset={{ url, width: width ?? null, height: height ?? null, alt, focalPoint, lqip }}\n {...(props.priority === true ? { priority: true } : {})}\n {...(options.loader !== undefined ? { loader: options.loader } : {})}\n {...(options.sizes !== undefined ? { sizes: options.sizes } : {})}\n {...(options.quality !== undefined ? { quality: options.quality } : {})}\n />\n )\n }\n}\n\n/**\n * Batteries included, opted into (EC-591): return the component list with the\n * implementation of the component that fills the `image` role swapped for\n * `next/image` delivery ({@link ElytraImage}) — the manifest is kept, so the\n * studio and the host still register the same component. Nothing is added to the\n * list and nothing is registered by the engine: no image-role component in the\n * list → the list comes back unchanged.\n *\n * Which entry fills the role is decided by core's one registration rule (the first\n * valid claimant — `ComponentRegistry.roleComponent('image')`), so the swap can\n * never land on a different entry than the one the registry resolves. Use it on\n * the DELIVERY side only (the studio's canvas runs on Vite, where `next/image`\n * does not exist) — the scaffold's host seam file does it in one line:\n *\n * ```ts\n * export const elytra = createElytraHost({ ...site, components: withNextImage(site.components) })\n * ```\n *\n * Pass {@link NextImageOptions} to delegate image delivery to another CDN\n * (EC-271) — the v1 default is Vercel, with room to grow:\n *\n * ```ts\n * withNextImage(site.components, { loader: createAssetImageLoader() }) // ?w=&q= CDN\n * withNextImage(site.components, { loader: cloudflareImageLoader }) // Cloudflare Images\n * ```\n */\nexport function withNextImage(\n components: readonly HostComponentEntry[] | undefined,\n options: NextImageOptions = {},\n): HostComponentEntry[] {\n const list = [...(components ?? [])]\n const image = registerComponents(list).registry.roleComponent('image')\n if (!image) return list\n const implementation = createNextImage(options)\n return list.map((entry) => (entry.manifest === image ? { manifest: entry.manifest, implementation } : entry))\n}\n\n/** Per-deployment options for {@link elytraHostImage} (the EC-271 loader seam). */\nexport interface ElytraHostImageOptions {\n /** Custom `next/image` loader for non-Vercel CDNs (see {@link NextImageOptions}). */\n loader?: ImageLoader\n /** Default quality (1–100) when a call site doesn't set one (Next defaults to 75). */\n quality?: number\n}\n\n/**\n * The `next/image`-backed {@link HostImageRenderer} (EC-368 image defaults): the\n * renderer a Next host registers on the `@elytracms/next/host-image` bridge so\n * DUAL-RENDERED project components (which can't import `next/image` themselves)\n * deliver optimized images — resized/modern-format variants, intrinsic\n * dimensions (no layout shift), focal-point `object-position`. A dimensionless\n * record degrades to a plain `<img>` (honest, never a crash), exactly like\n * {@link ElytraImage}.\n *\n * ```ts\n * import { elytraHostImage } from '@elytracms/next'\n * import { setHostImage } from '@elytracms/next/host-image'\n *\n * setHostImage(elytraHostImage()) // once, at host boot (never in the studio)\n * ```\n */\nexport function elytraHostImage(options: ElytraHostImageOptions = {}): HostImageRenderer {\n // EC-428 (AD-5/NK-026): forward `loading` — before this the renderer destructured\n // only asset/alt/className/priority/sizes, so a non-priority eager image silently\n // fell back to lazy. ElytraImage resolves the priority + loading='lazy' conflict.\n return ({ asset, alt, className, priority, sizes, loading }) => (\n <ElytraImage\n asset={{ ...asset, alt }}\n {...(className !== undefined ? { className } : {})}\n {...(priority ? { priority: true } : {})}\n {...(sizes !== undefined ? { sizes } : {})}\n {...(loading !== undefined ? { loading } : {})}\n {...(options.loader !== undefined ? { loader: options.loader } : {})}\n {...(options.quality !== undefined ? { quality: options.quality } : {})}\n />\n )\n}\n","import type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport { compositionNodesOf } from './canvas-renderer'\nimport type { DeliveryPresentation } from './canvas-renderer'\n\n/**\n * Manifest-driven header clearance (EC-368): does this page OPEN full-bleed?\n *\n * A host with a floating header (occupying no flow space) must pad a page's\n * content down by the header height — unless the page's FIRST canvas block is a\n * structural hero that deliberately bleeds to the viewport top and renders\n * under the header. Which components bleed is declared on their OWN manifest\n * (`presentation: { fullBleed: true }`, `@elytracms/core/component-registry`),\n * so the host derives the decision from the registry instead of keeping a\n * parallel hand-maintained component-id set that silently drifts when a new\n * hero ships.\n *\n * Pure over the delivery-shaped document: the composition field is found by\n * VALUE (the same `compositionNodesOf` selection the renderer uses), so hosts\n * with `body`, `content`, or bespoke composition field names all work. Absent\n * document, empty composition, unknown component, or an undeclared hint all\n * answer `false` — the safe default is clearance.\n *\n * EC-499: pass the collection's resolved `presentation` (from the render\n * outcome) so this reads the SAME body the renderer will render when the\n * collection declares one — otherwise the value-shape pick could name a\n * different field than the declaration does. A body whose first item is prose\n * rather than a block answers `false`, which is right: rich text opens no hero.\n *\n * ```tsx\n * const outcome = client.resolvePage(path)\n * const fullBleed = firstBlockFullBleed(outcome.document, components.registry)\n * return fullBleed ? page : <div className=\"pt-[var(--header-clearance)]\">{page}</div>\n * ```\n *\n * The host kit wires the resolution half for you: `elytra.pageOpensFullBleed(slug)`.\n */\nexport function firstBlockFullBleed(\n document: Record<string, unknown> | null | undefined,\n registry: ComponentRegistry,\n presentation?: DeliveryPresentation,\n): boolean {\n const first = compositionNodesOf(document, presentation)[0]\n if (!first) return false\n return registry.getManifest(first.componentId)?.presentation?.fullBleed === true\n}\n","/**\n * Env resolution for the delivery host kit (EC-368). ONE documented name per\n * concern — every kit surface reads the same resolved bag, so the two-\n * disconnected-secrets class of bug (a preview route gating on one env var\n * while the draft loader reads another) is structurally impossible: there is\n * exactly one `DRAFT_PREVIEW_TOKEN`, consumed by BOTH the preview-enable route\n * and the draft snapshot fetch.\n *\n * | Env var | Concern |\n * | --------------------- | ---------------------------------------------------- |\n * | `ELYTRA_CONVEX_URL` | Convex deployment URL — presence (with the project |\n * | | id) switches the loader to LIVE mode |\n * | `ELYTRA_PROJECT_ID` | The project the studio publishes under (= tag scope) |\n * | `DRAFT_PREVIEW_TOKEN` | The ONE draft secret: gates the preview-enable route |\n * | | AND authenticates the draft snapshot fetch (must |\n * | | equal the Convex deployment's own env var) |\n * | `REVALIDATE_SECRET` | Instant-publish webhook HMAC secret (must equal the |\n * | | deployment's `CANVAS_REVALIDATE_SECRET`) |\n *\n * EC-579 retired `STUDIO_ORIGIN` (the CORS origin of the studio's deleted browser\n * dispatcher): the one emitter is the Convex deployment, server to server.\n */\n\n/** The kit's documented env names (single source of truth, also used in docs/tests). */\nexport const ELYTRA_HOST_ENV_KEYS = {\n convexUrl: 'ELYTRA_CONVEX_URL',\n projectId: 'ELYTRA_PROJECT_ID',\n draftToken: 'DRAFT_PREVIEW_TOKEN',\n revalidateSecret: 'REVALIDATE_SECRET',\n} as const\n\nexport interface ElytraHostEnv {\n convexUrl?: string\n projectId?: string\n draftToken?: string\n revalidateSecret?: string\n}\n\nfunction cleaned(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined\n}\n\n/**\n * Resolve the kit's env bag from a raw env record (`process.env` by default at\n * the factory). Values are trimmed; empty strings resolve to `undefined` — an\n * env var set to `\"\"` must behave exactly like an unset one (endpoints stay\n * closed, live mode stays off), never like a configured empty secret.\n */\nexport function resolveHostEnv(env: Record<string, string | undefined>): ElytraHostEnv {\n const resolved: ElytraHostEnv = {}\n for (const [key, name] of Object.entries(ELYTRA_HOST_ENV_KEYS)) {\n const value = cleaned(env[name])\n if (value !== undefined) resolved[key as keyof ElytraHostEnv] = value\n }\n return resolved\n}\n","import { documentSchema, fallbackHomeRoutes, redirectRecordSchema } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n LocaleConfigInput,\n RedirectRecord,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { ContentDocumentSource, Perspective } from '@elytracms/core/content'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { createStaticContentSource, mergeRouteRecords } from './snapshot'\nimport type { ContentSource } from './source'\n\n/**\n * The LIVE half of the delivery host kit (EC-368): fetch the delivery snapshot\n * from a Convex deployment (`convex/delivery.ts` in the builder app) over\n * Convex's public HTTP query API — a plain `fetch` to\n * `POST {deployment}/api/query`, no Convex npm dependency — and assemble it\n * through the same `createStaticContentSource` path the fixture snapshot uses,\n * so accessor and rendering semantics are byte-identical across seeded, empty,\n * and live modes. This engine-izes the ~300-line `live-content.ts` both real\n * consumers hand-copied (EC-369 finding #5); the fetch + Zod-validation logic\n * now lives in exactly one place.\n *\n * Perspective handling: published requests fetch the PUBLIC `delivery:snapshot`\n * (published documents only — the standing live-is-published-only invariant).\n * Draft-mode requests fetch `delivery:draftSnapshot`, gated by the deployment's\n * `DRAFT_PREVIEW_TOKEN` — the same single secret that gates the kit's\n * preview-enable route (see `host-env.ts`).\n *\n * Routes: config-sourced route records (AD-3/AD-11, EC-207 — the host repo's\n * own `cms/`, passed via options) and the implicit per-locale home fallback are\n * MERGED (EC-177 gap 2), never XOR'd — config records win per pattern+locale.\n * The backend never carries route data.\n */\n\nexport interface LiveContentOptions {\n /** Convex deployment URL, e.g. `https://your-deployment-123.convex.cloud`. */\n convexUrl: string\n projectId: string\n /**\n * Project locale config — config-owned (AD-11), never read from the snapshot.\n * The declared shape; parsed where the source is assembled (EC-580).\n */\n locales: LocaleConfigInput\n /** CMS collection schema — config-owned (AD-11); drives depth-1 relation population. */\n collections: readonly CollectionDef[]\n /** Route records — config-owned (AD-3/AD-11); merged with the home fallback. */\n routes: readonly RouteRecord[]\n /** `DRAFT_PREVIEW_TOKEN` — required for draft-perspective loads. */\n draftToken?: string\n /** Fetch implementation override (tests, custom agents). Defaults to global fetch. */\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * The `delivery.ts` payload shape (validated field-by-field below). A\n * deployment IS one environment (EC-179); the snapshot carries content only\n * (EC-287) — documents, assets, editorial redirects (EC-303). (A `sources`\n * strand from an older deployment is ignored — it belonged to the deleted\n * data-binding subsystem, EC-574.)\n */\ninterface DeliverySnapshotPayload {\n projectId: string\n redirects?: unknown[]\n documents: unknown[]\n assets: unknown[]\n}\n\n/**\n * Run one query through Convex's public HTTP API. Response envelope (verified\n * against live deployments): `{ status: 'success', value }` or\n * `{ status: 'error', errorMessage }`. `cache: 'no-store'` keeps Next's fetch\n * cache out of the loop — the route helper's tagged data cache is the only\n * cache layer, so tag revalidation always reaches the deployment.\n */\nasync function runConvexQuery(\n fetchImpl: typeof globalThis.fetch,\n convexUrl: string,\n path: string,\n args: Record<string, unknown>,\n): Promise<unknown> {\n const response = await fetchImpl(`${convexUrl.replace(/\\/$/, '')}/api/query`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ path, args, format: 'json' }),\n cache: 'no-store',\n })\n const body = (await response.json().catch(() => undefined)) as\n | { status?: string; value?: unknown; errorMessage?: string; message?: string }\n | undefined\n if (!response.ok || body?.status !== 'success') {\n throw new Error(\n `Convex query \"${path}\" failed (${response.status}): ${\n body?.errorMessage ?? body?.message ?? 'no error message'\n }`,\n )\n }\n return body.value\n}\n\n/** Canonical-schema validation: invalid stored rows are skipped, not fatal. */\nfunction parseDocuments(rows: unknown[]): CmsDocument[] {\n const documents: CmsDocument[] = []\n for (const row of rows) {\n const parsed = documentSchema.safeParse(row)\n if (parsed.success) documents.push(parsed.data)\n }\n return documents\n}\n\n/** Validate the snapshot's editorial redirects (EC-303); malformed rows drop out. */\nfunction parseRedirects(rows: unknown[]): RedirectRecord[] {\n const redirects: RedirectRecord[] = []\n for (const row of rows) {\n const parsed = redirectRecordSchema.safeParse(row)\n if (parsed.success) redirects.push(parsed.data)\n }\n return redirects\n}\n\n// `fallbackHomeRoutes` moved to `@elytracms/core/cms-core` (EC-384) so the\n// studio's document-URL preview (`urlForDocument`) can derive the SAME implicit\n// `/` → first-page-document fallback the delivery resolver below applies — one\n// rule, not a studio-side reimplementation. Re-exported here (imported above)\n// to keep this module's/`@elytracms/next`'s existing public surface. The\n// delivery snapshot is already perspective-filtered server-side, so in draft\n// mode this correctly previews the draft home even when unpublished.\nexport { fallbackHomeRoutes }\n\n/**\n * Assemble a fetched delivery-snapshot payload into a `ContentSource` — the\n * pure half of {@link loadLiveContent}, exported so the parsing/assembly rules\n * are directly testable against fixture payloads (no network).\n */\nexport function assembleLiveSource(\n payload: DeliverySnapshotPayload,\n options: Pick<LiveContentOptions, 'locales' | 'collections' | 'routes'>,\n perspective: Perspective,\n): ContentSource {\n // Locales/collections/routes are config-owned (AD-11): from the host repo's\n // own `cms/`, passed in via options — never from the snapshot.\n const locales = options.locales\n const documents = parseDocuments(payload.documents)\n\n // MERGE, not XOR (EC-177 gap 2): config route records win per pattern+locale;\n // the implicit per-locale home fallback fills the rest.\n const routes = mergeRouteRecords([...options.routes], fallbackHomeRoutes(documents, locales))\n\n // EC-224 B-1: the published snapshot ships each document as its PINNED\n // published content (delivery resolved the version pointer server-side). Wrap\n // those rows as a `DocumentHistory` so the pure published gate\n // (`selectPerspective`) serves `.published` directly.\n //\n // TRUST CONTRACT: the wrap treats whatever the published fetch returned AS\n // published and deliberately does NOT re-gate — it cannot, the wire carries\n // no version pointer. Correctness rests ENTIRELY on `delivery:snapshot`\n // shipping published-only content (pinned version snapshots, never a draft —\n // see the builder's `convex/delivery.ts`). Guard the invariant in delivery,\n // not here. Draft-fetch rows stay bare: `selectPerspective('draft')` serves\n // them as-is.\n const documentSources: ContentDocumentSource[] =\n perspective === 'published'\n ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] }))\n : documents\n\n return createStaticContentSource({\n projectId: payload.projectId,\n locales,\n routes,\n // Redirects are editorial DATA (EC-303): the ENABLED ones ship in the\n // snapshot, so they change dynamically (no redeploy). Native next.config\n // redirects run before this.\n redirects: parseRedirects(payload.redirects ?? []),\n collections: [...options.collections],\n documents: documentSources,\n assets: payload.assets as AssetRecord[],\n })\n}\n\n/** Fetch the live delivery snapshot and assemble the `ContentSource` for one request. */\nexport async function loadLiveContent(\n options: LiveContentOptions,\n perspective: Perspective,\n): Promise<ContentSource> {\n const fetchImpl = options.fetch ?? globalThis.fetch\n const args: Record<string, unknown> = { projectId: options.projectId }\n\n let payload: DeliverySnapshotPayload\n if (perspective === 'draft') {\n if (!options.draftToken) {\n // Explicit, visible failure: draft mode without a configured token must\n // never silently fall back to published content.\n throw new Error(\n 'Draft preview requested but DRAFT_PREVIEW_TOKEN is not configured on the host.',\n )\n }\n payload = (await runConvexQuery(fetchImpl, options.convexUrl, 'delivery:draftSnapshot', {\n ...args,\n token: options.draftToken,\n })) as DeliverySnapshotPayload\n } else {\n payload = (await runConvexQuery(\n fetchImpl,\n options.convexUrl,\n 'delivery:snapshot',\n args,\n )) as DeliverySnapshotPayload\n }\n\n return assembleLiveSource(payload, options, perspective)\n}\n","import type { DocumentRef, Locale } from '@elytracms/core/cms-core'\nimport type { ContentClient, Perspective, ResolvedDocument } from '@elytracms/core/content'\nimport type { ContentSource } from './source'\n\n/**\n * Site-chrome globals for delivery hosts (EC-368, from EC-369 finding #5): the\n * layout, header, and footer are NOT rendered through the canvas, so they read\n * the `settings` singleton + `navigation` documents straight from the\n * `ContentSource` and pre-resolve everything — keeping the chrome components\n * pure presentational, with no `ContentClient` or `@elytracms/*` knowledge.\n * Both real consumers hand-authored this ~200-line layer independently; the\n * MECHANISM now lives here, while the per-project projection (which settings\n * fields exist, what the chrome looks like) stays consumer-owned:\n *\n * - **settings**: the singleton collection's lone document (EC-274) — no\n * document id is ever named.\n * - **navigation by slot**: menus are addressed by their EC-284 alias slot\n * (`main`, `footer`), never a document id — delivery shape carries `alias`\n * since EC-364.\n * - **link resolution**: link-object values (`linkType: href | page | file`)\n * resolve through `client.resolveLink` — the same `urlForDocument` authority\n * the router serves, so a nav link to a nested page carries its FULL composed\n * path (`/krippe/tagesablauf`, EC-218/EC-360), and `home → '/'` needs no\n * special-casing.\n * - **asset resolution**: asset field values (stored ids) resolve to serve URLs\n * through the source's delivery-shaped asset list.\n *\n * Everything degrades explicitly: a missing settings document is `null`, a\n * missing slot is `null`/`[]`, an unresolvable link drops out (never a silent\n * `#`), an unknown asset id is `undefined` — the host renders sensible empties\n * for the empty-source build and surfaces gaps honestly.\n */\n\nexport interface GlobalsConventions {\n /** The settings singleton's collection id. Default `'settings'`. */\n settingsCollection?: string\n /** The navigation documents' collection id. Default `'navigation'`. */\n navigationCollection?: string\n /**\n * Field names probed (in order) for a navigation document's entry list.\n * Default `['items', 'links']` — the two shapes the shipped consumers and the\n * starter template use.\n */\n navigationItemsFields?: readonly string[]\n}\n\n/** A fully-resolved link: the composed href plus the new-tab flag. */\nexport interface ResolvedLink {\n href: string\n openInNewTab: boolean\n}\n\n/** A leaf navigation link (children of a dropdown / footer column). */\nexport interface ResolvedNavLink extends ResolvedLink {\n label: string\n /** Optional subline shown under the label (wide header dropdowns). */\n description?: string\n}\n\n/** A top-level navigation entry; `href` is absent for pure group headings. */\nexport interface ResolvedNavEntry {\n label: string\n href?: string\n openInNewTab: boolean\n description?: string\n children: ResolvedNavLink[]\n}\n\n/** The resolved chrome-globals surface `loadGlobals` yields (EC-368). */\nexport interface SiteGlobals {\n /** The settings singleton, delivery-shaped, or `null` (empty build / not published). */\n settings: ResolvedDocument | null\n /** The navigation document filling a slot alias (EC-284/EC-364), or `null`. */\n navigation(slot: string): ResolvedDocument | null\n /**\n * A slot's entry list, fully resolved: labels, composed hrefs (nested paths\n * included), new-tab flags, sublines, one level of children. Entries whose\n * link cannot resolve to an href are kept only when they carry children (a\n * group heading); leaf entries without a resolvable href drop out.\n */\n navigationEntries(slot: string): ResolvedNavEntry[]\n /** Resolve a link-object field value to an href + new-tab flag, or `null`. */\n resolveLink(value: unknown): ResolvedLink | null\n /** Resolve an asset field value (stored id / URL / `{ url }`) to a URL, or `undefined`. */\n assetUrl(value: unknown): string | undefined\n /** The accessor client behind the globals — for bespoke projections. */\n client: ContentClient\n /** The underlying content source (assets(), locales, routes). */\n source: ContentSource\n}\n\nexport interface ResolveGlobalsOptions {\n /** Perspective of the chrome read. Default `'published'` (live is published-only). */\n perspective?: Perspective\n locale?: Locale\n conventions?: GlobalsConventions\n}\n\nconst DEFAULT_ITEMS_FIELDS = ['items', 'links'] as const\n\nfunction record(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\nfunction items(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? value.map(record) : []\n}\n\n/**\n * Resolve the chrome globals over an already-loaded `ContentSource` — the pure\n * core of the host kit's `loadGlobals` (directly testable over fixture\n * sources).\n */\nexport function resolveGlobals(\n source: ContentSource,\n options: ResolveGlobalsOptions = {},\n): SiteGlobals {\n const perspective = options.perspective ?? 'published'\n const conventions = options.conventions ?? {}\n const settingsCollection = conventions.settingsCollection ?? 'settings'\n const navigationCollection = conventions.navigationCollection ?? 'navigation'\n const itemsFields = conventions.navigationItemsFields ?? DEFAULT_ITEMS_FIELDS\n\n const client = source.client({ perspective, ...(options.locale ? { locale: options.locale } : {}) })\n const assets = source.assets?.() ?? []\n const assetById = new Map(assets.map((asset) => [asset.id, asset] as const))\n\n const listDocs = (collection: string): ResolvedDocument[] => {\n const result = client.listDocuments(collection)\n return result.ok ? result.documents : []\n }\n\n // EC-274: `settings` is a singleton — the lone document of its collection.\n const settings = listDocs(settingsCollection)[0] ?? null\n\n // EC-284/EC-364: menus are addressed by slot alias through the content client\n // (delivery shape carries `alias`), never by document id.\n const navDocs = listDocs(navigationCollection)\n const navigation = (slot: string): ResolvedDocument | null =>\n navDocs.find((doc) => (doc as Record<string, unknown>).alias === slot) ?? null\n\n /** Asset field value → serve URL. The stored value is the asset id (or already a URL). */\n const assetUrl = (value: unknown): string | undefined => {\n if (typeof value === 'string') {\n const found = assetById.get(value)\n if (found) return found.url\n // A passthrough URL (external image / public asset path) — not an id.\n return value.startsWith('http') || value.startsWith('/') ? value : undefined\n }\n const url = record(value).url\n return typeof url === 'string' && url.length > 0 ? url : undefined\n }\n\n /** A raw or populated relation value's `{ collection, id }` identity, if present. */\n const refOf = (value: unknown): DocumentRef | null => {\n const ref = record(value)\n return typeof ref.collection === 'string' && typeof ref.id === 'string'\n ? { collection: ref.collection, id: ref.id }\n : null\n }\n\n /** Resolve a link-object value (`linkType: href | page | file`) to an href. */\n const resolveLink = (value: unknown): ResolvedLink | null => {\n const link = record(value)\n const openInNewTab = link.openInNewTab === true\n const href = str(link.href) || str(link.url)\n const linkType = str(link.linkType) || (href ? 'href' : '')\n\n if (linkType === 'href' || linkType === 'external') {\n return href ? { href, openInNewTab } : null\n }\n if (linkType === 'file') {\n const url = assetUrl(link.file)\n return url ? { href: url, openInNewTab } : null\n }\n if (linkType === 'page') {\n const page = link.page\n // The reverse-router authority (EC-291/EC-360): the SAME `urlForDocument`\n // the routes serve — composed nested paths, `home → '/'`, locale prefix.\n const ref = refOf(page)\n if (ref) {\n const path = client.resolveLink(ref)\n if (path !== null) return { href: path, openInNewTab }\n }\n // Degraded fallbacks for partially-shaped values; else explicit null.\n const shaped = record(page)\n if (typeof shaped.path === 'string' && shaped.path.startsWith('/')) {\n return { href: shaped.path, openInNewTab }\n }\n const slug = str(shaped.slug)\n if (slug) return { href: `/${slug}`, openInNewTab }\n return null\n }\n return null\n }\n\n const toNavLink = (entry: Record<string, unknown>): ResolvedNavLink | null => {\n const label = str(entry.label)\n // Structured shape: the link object lives under `link`; flat starter shape:\n // the entry itself carries `href` (resolveLink's href fallback picks it up).\n const resolved = resolveLink('link' in entry ? entry.link : entry)\n if (!label || !resolved) return null\n const description = str(entry.description)\n return { label, ...resolved, ...(description ? { description } : {}) }\n }\n\n const toNavEntry = (entry: Record<string, unknown>): ResolvedNavEntry | null => {\n const label = str(entry.label)\n if (!label) return null\n const resolved = resolveLink('link' in entry ? entry.link : entry)\n const description = str(entry.description)\n const children = items(entry.children)\n .map(toNavLink)\n .filter((child): child is ResolvedNavLink => child !== null)\n // A leaf entry needs a resolvable target; an entry with children stands as a\n // group heading even without one (footer columns, pure dropdowns).\n if (!resolved && children.length === 0) return null\n return {\n label,\n ...(resolved ? { href: resolved.href } : {}),\n openInNewTab: resolved?.openInNewTab ?? false,\n ...(description ? { description } : {}),\n children,\n }\n }\n\n const navigationEntries = (slot: string): ResolvedNavEntry[] => {\n const doc = navigation(slot)\n if (!doc) return []\n const values = doc as Record<string, unknown>\n const field = itemsFields.find((name) => Array.isArray(values[name]))\n if (!field) return []\n return items(values[field])\n .map(toNavEntry)\n .filter((entry): entry is ResolvedNavEntry => entry !== null)\n }\n\n return {\n settings,\n navigation,\n navigationEntries,\n resolveLink,\n assetUrl,\n client,\n source,\n }\n}\n","/**\n * Pure token/target logic of the draft-preview routes (EC-144), kept free of\n * Next.js imports so it is unit-testable. `preview.ts` wires it into route\n * handlers with `draftMode()`.\n */\n\nexport type PreviewEvaluation =\n | { ok: true; redirectTo: string }\n | { ok: false; status: 400 | 401; message: string }\n\n/**\n * The fixed origin redirect targets are resolved against. `.invalid` is reserved\n * (RFC 6761), so it never names a real host; `https:` is a special scheme, so the\n * parser applies the same normalisation a browser applies when it resolves a\n * `Location` header against the site (`\\`→`/`, tab/LF/CR stripped, dot segments\n * removed).\n */\nconst RESOLUTION_ORIGIN = 'https://redirect-target.invalid'\n\n/** Resolve `value` the way a browser would, keeping it only if it stays on the origin. */\nfunction resolveOnOrigin(value: string): URL | null {\n let resolved: URL\n try {\n resolved = new URL(value, RESOLUTION_ORIGIN)\n } catch {\n return null\n }\n return resolved.origin === RESOLUTION_ORIGIN ? resolved : null\n}\n\n/** C0 controls (U+0000–U+001F) and DEL (U+007F): never part of a legitimate target. */\nfunction hasControlCharacter(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code <= 0x1f || code === 0x7f) return true\n }\n return false\n}\n\n/**\n * The ONE same-site check for a preview redirect target, shared by the enable and\n * disable routes, so the preview endpoints can never be used as an open redirect.\n * `null` means rejected; an empty value yields `fallback`.\n *\n * EC-594: the parser a browser uses decides, not a blocklist of shapes (the old\n * blocklist caught `//host` and backslashes — EC-304 B-3 — but not `/<TAB>/host`,\n * which the parser collapses to `//host`):\n *\n * - only a path-absolute reference (`/…`) is accepted — a relative one would be\n * resolved against the route's own path by a browser, so it has no fixed meaning;\n * - any C0 control or DEL is rejected outright;\n * - the value is resolved against a fixed origin and must stay on it;\n * - what is returned is the parser's normalised `pathname + search + hash`, never\n * the raw input — and that returned target must itself stay on the origin, since\n * dot-segment removal can leave a pathname starting with `//` (`/.//host`,\n * `/..//host`, `/%2e//host`), which as a `Location` is scheme-relative.\n *\n * A scheme-relative or absolute value naming the resolution origin itself resolves\n * on it and yields its path — harmless, because only that path is returned.\n */\nexport function safeRedirectTarget(\n value: string | null | undefined,\n fallback = '/',\n): string | null {\n if (value === null || value === undefined || value === '') return fallback\n if (!value.startsWith('/') || hasControlCharacter(value)) return null\n const resolved = resolveOnOrigin(value)\n if (resolved === null) return null\n const target = resolved.pathname + resolved.search + resolved.hash\n return resolveOnOrigin(target) === null ? null : target\n}\n\n/**\n * Evaluate a preview-enable request. Drafts are token-gated (EC-144\n * acceptance: without the token, drafts are never reachable):\n *\n * - an unconfigured (empty) expected token rejects every request — preview\n * cannot be accidentally left open;\n * - a missing or mismatched `token` query param is a 401;\n * - an off-site `redirect` target is a 400.\n */\nexport function evaluatePreviewRequest(\n requestUrl: string | URL,\n expectedToken: string | undefined,\n): PreviewEvaluation {\n if (expectedToken === undefined || expectedToken.length === 0) {\n return { ok: false, status: 401, message: 'Draft preview is not configured.' }\n }\n\n let url: URL\n try {\n url = typeof requestUrl === 'string' ? new URL(requestUrl, 'http://localhost') : requestUrl\n } catch {\n return { ok: false, status: 400, message: 'Malformed preview request URL.' }\n }\n\n const token = url.searchParams.get('token')\n if (token !== expectedToken) {\n return { ok: false, status: 401, message: 'Invalid preview token.' }\n }\n\n const redirectTo = safeRedirectTarget(url.searchParams.get('redirect'))\n if (redirectTo === null) {\n return { ok: false, status: 400, message: 'Preview redirect target must be a same-site path.' }\n }\n\n return { ok: true, redirectTo }\n}\n\n/**\n * The redirect target of a preview-disable request. Disabling needs no token (it\n * only ever reduces visibility), so anyone can reach the route — its `redirect`\n * param goes through the same {@link safeRedirectTarget} as the enable route. An\n * off-site or unreadable target falls back to the site root: leaving draft mode\n * always succeeds.\n */\nexport function previewDisableTarget(requestUrl: string | URL): string {\n let url: URL\n try {\n url = typeof requestUrl === 'string' ? new URL(requestUrl, 'http://localhost') : requestUrl\n } catch {\n return '/'\n }\n return safeRedirectTarget(url.searchParams.get('redirect')) ?? '/'\n}\n","import { draftMode } from 'next/headers'\nimport { redirect } from 'next/navigation'\nimport { evaluatePreviewRequest, previewDisableTarget } from './preview-core'\n\n/**\n * Draft preview route handlers (EC-144): Next.js `draftMode()` integration.\n * Server-only by construction — these factories are consumed from App Router\n * route handler files, never from client bundles.\n */\n\nexport interface PreviewRouteOptions {\n /**\n * The shared secret gating draft preview. An empty/undefined token means\n * every request is rejected with 401 — drafts stay unreachable until a\n * token is configured.\n */\n token: string | undefined\n}\n\n/** The shape an `app/api/.../route.ts` file re-exports. */\nexport interface PreviewRouteHandlers {\n GET(request: Request): Promise<Response>\n}\n\n/**\n * Create the preview-enable route handler for\n * `app/api/preview/route.ts`:\n *\n * ```ts\n * export const { GET } = createPreviewRoute({ token: process.env.PREVIEW_TOKEN })\n * ```\n *\n * `GET /api/preview?token=...&redirect=/some/path` checks the token, enables\n * Next draft mode (the catch-all helper then renders the `draft`\n * perspective), and redirects to the target path. Wrong/missing token → 401.\n */\nexport function createPreviewRoute(options: PreviewRouteOptions): PreviewRouteHandlers {\n return {\n async GET(request: Request): Promise<Response> {\n const evaluation = evaluatePreviewRequest(request.url, options.token)\n if (!evaluation.ok) {\n return new Response(evaluation.message, { status: evaluation.status })\n }\n const draft = await draftMode()\n draft.enable()\n redirect(evaluation.redirectTo)\n },\n }\n}\n\n/**\n * Create the preview-disable route handler (e.g.\n * `app/api/preview/disable/route.ts`). Disabling needs no token — it only\n * ever reduces visibility back to the published perspective. The `redirect`\n * target passes the same same-site check as the enable route; an off-site one\n * redirects to `/` instead.\n */\nexport function createPreviewDisableRoute(): PreviewRouteHandlers {\n return {\n async GET(request: Request): Promise<Response> {\n const draft = await draftMode()\n draft.disable()\n redirect(previewDisableTarget(request.url))\n },\n }\n}\n","import { draftMode } from 'next/headers'\nimport type { ReactNode } from 'react'\nimport type { CollectionDef, LocaleConfigInput, RouteRecord } from '@elytracms/core/cms-core'\nimport type { Perspective } from '@elytracms/core/content'\nimport type { Metadata } from 'next'\nimport { defineHostComponents } from './components'\nimport type { HostComponents } from './components'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\nimport { firstBlockFullBleed } from './full-bleed'\nimport { ELYTRA_HOST_ENV_KEYS, resolveHostEnv } from './host-env'\nimport type { ElytraHostEnv } from './host-env'\nimport { loadLiveContent } from './host-content'\nimport { resolveGlobals } from './host-globals'\nimport type { GlobalsConventions, SiteGlobals } from './host-globals'\nimport { createPreviewDisableRoute, createPreviewRoute } from './preview'\nimport type { PreviewRouteHandlers } from './preview'\nimport { createRevalidateRoute } from './revalidate'\nimport type { RevalidateRouteHandlers, RevalidateRouteOptions } from './revalidate'\nimport { createCanvasRoute } from './route'\nimport type {\n CanvasContentRequest,\n CanvasRequestInfo,\n CanvasRoute,\n CanvasWrapContext,\n} from './route'\nimport { pathFromSlug, splitLocalePath } from './route-core'\nimport { createStaticContentSource } from './snapshot'\nimport type { ContentSource } from './source'\nimport type { CacheTags, ResolvePageResult } from '@elytracms/core/content'\n\n/**\n * The delivery host kit (EC-368): ONE factory over (env, project config) that\n * yields every engine-shaped delivery surface a Next.js host mounts — content\n * loading (live snapshot / build-safe empty / token-gated draft), the catch-all\n * canvas route, the instant-publish revalidate webhook, draft-mode plumbing,\n * chrome globals, and the full-bleed layout hint. A consumer wires env + ~5\n * lines per surface; no engine logic is ever copied into a host repo again\n * (EC-369 findings #4/#5/#6/#11).\n *\n * ```ts\n * // frontend/lib/elytra.ts — the ONE seam file\n * import { createElytraHost } from '@elytracms/next'\n * // Read the project out of the repo's own config — `elytra.config.ts` is the\n * // single contract surface, and importing it costs a delivery bundle nothing\n * // (EC-532). Any other module layout works just as well; this is an example,\n * // not a required path.\n * import { site } from '../../elytra.config'\n *\n * export const elytra = createElytraHost({ ...site })\n * ```\n *\n * ```ts\n * // app/[[...slug]]/page.tsx\n * const route = elytra.canvasRoute()\n * export default route.Page\n * export const generateMetadata = route.generateMetadata\n * export const generateStaticParams = route.generateStaticParams // cacheComponents (EC-515)\n *\n * // app/api/revalidate/route.ts\n * export const { POST } = elytra.revalidateRoute()\n *\n * // app/api/draft/route.ts — GET /api/draft?token=…&redirect=/path\n * export const { GET } = elytra.previewRoute()\n *\n * // app/api/draft/disable/route.ts\n * export const { GET } = elytra.previewDisableRoute()\n * ```\n *\n * Trust boundaries are unchanged: live surfaces read the PUBLISHED snapshot;\n * draft is reachable only through explicit Next draft mode behind the ONE\n * `DRAFT_PREVIEW_TOKEN` (which also authenticates the draft snapshot fetch —\n * see `host-env.ts`); the revalidate webhook stays HMAC-signed. The consumer\n * keeps ownership of project config (AD-11), host components, form endpoints,\n * and anything genuinely bespoke.\n */\n\n/**\n * EC-581 (ratified 2b): the options are generic over the config's `collections`, so a\n * project declared with `defineCollection` keeps its literal types through the host and\n * `InferDocument<typeof host, 'post'>` (`@elytracms/core/cms-core`) is a delivered post.\n */\nexport interface ElytraHostOptions<\n Collections extends readonly CollectionDef[] = readonly CollectionDef[],\n> {\n /**\n * The project slug (config-as-code identity, mirrors `elytra.config.ts`).\n * Used as the content scope when no live backend is configured.\n */\n slug: string\n /**\n * Locale config — config-owned (AD-11), never read from the snapshot.\n *\n * **Import it, do not restate it** (EC-519). This is the *same*\n * {@link LocaleConfigInput} `elytra.config.ts` takes, so a project declares its\n * locales once and hands that one binding to both mounts. It stays a host\n * option because the engine cannot know where a repo keeps the value — but it\n * is no longer an option you have to *retype*: since EC-532 the delivery\n * bundle can import `elytra.config.ts` itself for zero engine modules\n * (measured), so `createElytraHost({ ...site })` over a project exported from\n * the config is the shortest correct answer.\n *\n * This used to be `{ default, locales }` against a config spelling the same\n * fact `locales` + `defaultLocale`, and in our own demo the two drifted — the\n * host said `['en']`, the config said `['en','de']`, and every `/de` URL 404'd\n * silently for weeks (EC-512).\n *\n * The DECLARED shape (EC-580): the kit parses it into the engine's `LocaleConfig`\n * where every content source is assembled (`createStaticContentSource` — the\n * empty source is built at boot), so a `default` outside `locales` stops the host\n * with a located message instead of serving — the same rule the studio's\n * workspace gate applies to the same value.\n */\n locales: LocaleConfigInput\n /** CMS collection schema — config-owned (AD-11). */\n collections: Collections\n /** Routes as code (AD-3/AD-11): records, or the `(collections) => records` derive. */\n routes: readonly RouteRecord[] | ((collections: CollectionDef[]) => RouteRecord[])\n /**\n * The repo's page-builder components (manifest + implementation pairs — the\n * SAME list the studio composes with, usually `site.components` via the\n * `{ ...site }` spread). The host registers exactly this list (EC-591): the\n * engine adds no components of its own — the shipped ones are the opt-in\n * `@elytracms/components` package, listed in the config. For `next/image`\n * delivery of the image-role component, pass the list through\n * `withNextImage(site.components)` (one line, opt-in).\n *\n * Typed as the CONFIG's `HostComponentEntry` (`implementation: unknown`), not\n * the stricter {@link HostComponent}, so that `createElytraHost({ ...site })`\n * — the shape EC-532 makes the contract — compiles however the consumer typed\n * their project entry. It previously demanded `ComponentType`, which meant the\n * recommended spread worked only when the entry's implementation types\n * survived inference: `apps/demo/elytra.config.ts` gets away with it via\n * `satisfies`, and anyone using the engine's own published `HostComponentEntry`\n * / `WorkspaceProjectEntry` annotation did not. Both production consumers hit\n * exactly that and each carried one hand-restated `components` line (EC-539).\n *\n * What the widening costs, and where it is paid (EC-541): a list passed HERE\n * gets no compile-time check that each `implementation` is really a\n * component. The check is RUNTIME instead — `defineHostComponents` takes the\n * same `HostComponentEntry` shape (no cast, one signature) and tests every\n * implementation on registration; a value that is not a component becomes an\n * `invalid-implementation` issue in `components.registry.issues` and is withheld from\n * the implementation map, so its nodes render the visible missing-component\n * fallback rather than crashing the server render. Authoring a component\n * module as the strict {@link HostComponent} keeps the compile-time check at\n * the place the implementation is written.\n */\n components?: readonly HostComponentEntry[]\n /**\n * The public base URL of the delivery site (mirrors the config's\n * `frontendUrl`) — exposed on the handle for sitemap/canonical use.\n */\n frontendUrl?: string\n /**\n * Env bag; defaults to `process.env`. Documented names (see `host-env.ts`):\n * `ELYTRA_CONVEX_URL`, `ELYTRA_PROJECT_ID`, `DRAFT_PREVIEW_TOKEN`,\n * `REVALIDATE_SECRET`.\n */\n env?: Record<string, string | undefined>\n /**\n * The no-backend content fallback, used whenever `ELYTRA_CONVEX_URL` +\n * `ELYTRA_PROJECT_ID` are not both set. Default: a build-safe EMPTY source —\n * consumer CI builds green with no backend, and every URL is a clean 404\n * until content exists — in every cache state, not only a cold one, because\n * the content mode is part of the route's data-cache key (see\n * {@link ElytraContentMode}). The starter template overrides this with its\n * seeded in-memory world.\n */\n fallbackContent?: (request: CanvasContentRequest) => Promise<ContentSource>\n /** Chrome-globals conventions (settings/navigation collection ids, items fields). */\n globals?: GlobalsConventions\n /** Fetch implementation override for the live snapshot loader (tests, proxies). */\n fetch?: typeof globalThis.fetch\n}\n\n/** Pass-through canvas-route options the kit does not own. */\nexport interface HostCanvasRouteOptions {\n /** Tag observation hook (see `CanvasRouteOptions.onTags`). */\n onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void\n /** Metadata override (see `CanvasRouteOptions.metadata`). */\n metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata\n /**\n * Host-owned chrome around the rendered page (EC-524) — the seam a per-\n * collection detail template lives on. Branch on `ctx.collection` in your own\n * route file (see `CanvasRouteOptions.wrap`).\n */\n wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode\n /**\n * Tagged cache entry (instant publish): one `'use cache'` entry per URL, tagged\n * with what its resolution read (EC-515; the host enables `cacheComponents`).\n * Always on and always scoped to the kit's {@link ElytraHost.contentScope}, with\n * the kit's {@link ElytraHost.contentMode} in the entry key (EC-543). `false`\n * disables caching entirely.\n *\n * There is no scope override (EC-579): the kit's revalidate receiver derives its\n * tags with that same `contentScope`, so one value scopes both the entries and\n * their invalidation. An override here was the one way left for the two to\n * disagree — and a disagreement drops nothing, silently.\n */\n cache?: false\n}\n\nexport interface LoadGlobalsOptions {\n /** Perspective of the chrome read. Default `'published'` (live is published-only). */\n perspective?: Perspective\n}\n\n/**\n * Which world {@link ElytraHost.loadContent} reads from.\n *\n * - `live` — the configured Convex deployment (published; token-gated draft).\n * - `fallback` — the host's own `fallbackContent` world (the scaffold's seeded\n * starter, the demo's in-memory fixture). A legitimate, supported mode.\n * - `empty` — no backend and no fallback: this project's structure with no\n * content, so every URL is a clean 404. Correct for a CI build; almost never\n * what a deployed site intends.\n *\n * The mode was decided from env long before EC-527 and readable by **nobody**,\n * so a site serving the fallback looked identical from the outside to one\n * reading the real deployment. Measured on the demo: `/` returned `200` while\n * all four of EC-512's published fixtures 404'd — an agent following AGENTS.md\n * §5 gets no signal that the thing under test was never exercised. Declared\n * once, read here, so a host can *say* which world it is in instead of\n * re-deriving it from `env` and drifting.\n *\n * **The mode is also part of the route's cache-entry key** (EC-543; since EC-515\n * an argument of the cached function). An entry can outlive the world that wrote\n * it and a hit never runs the loader, so until 0.0.85 the \"clean 404\" above held\n * only on a COLD cache: measured, a `next start` with both env vars blanked and a\n * warm `.next/cache` from a live run answered `200` with the live world's bytes\n * on every cached URL while reporting `empty` — the wrong-world failure EC-527\n * removed, one layer down. Keyed on the mode (`canvasCacheKey`, `cache.ts`), a\n * world change is a cold miss per URL, never a stale hit, in either direction.\n */\nexport type ElytraContentMode = 'live' | 'fallback' | 'empty'\n\n/** The delivery host handle {@link createElytraHost} yields. */\nexport interface ElytraHost<\n Collections extends readonly CollectionDef[] = readonly CollectionDef[],\n> {\n /**\n * The config's collections, as passed (EC-581) — typed by their literals when the\n * project declares them with `defineCollection`, so `InferDocument<typeof host, 'post'>`\n * types a delivered post with no generation step.\n */\n readonly collections: Collections\n /**\n * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode, else the config slug.\n * The ONE scope of this host (EC-579): its cache entries are written under it and\n * its revalidate receiver derives every invalidation tag with it.\n */\n contentScope: string\n /**\n * Which world {@link loadContent} reads from — see {@link ElytraContentMode}.\n * Read it to announce the mode (a boot line, a dev-only banner) rather than\n * re-deriving it from {@link env}, which is the drift this field exists to\n * prevent.\n */\n contentMode: ElytraContentMode\n /** The resolved env bag (documented names, trimmed, empty = unset). */\n env: ElytraHostEnv\n /** Materialized route records (a derive is run once, at factory time). */\n routes: RouteRecord[]\n /** The registered component surface — exactly the `components` option's list (EC-591). */\n components: HostComponents\n /** The public base URL of the delivery site, when configured. */\n frontendUrl?: string\n /**\n * The content loader (the seam every kit surface reads through): live\n * delivery snapshot when env is configured — published, or the token-gated\n * draft for draft-mode requests — else the fallback source.\n *\n * An UNCACHED read (EC-515): only the canvas route's entry caches. Under\n * `cacheComponents`, live-mode I/O in a layout or page outside `<Suspense>` fails\n * `next build` — call it inside a `<Suspense>`'d component (route handlers and\n * `sitemap.ts` just render dynamically).\n */\n loadContent(request: CanvasContentRequest): Promise<ContentSource>\n /**\n * Load + resolve the site-chrome globals (settings singleton, navigation by\n * slot alias, link/asset resolution — see `host-globals.ts`). One content\n * load per call; published perspective by default.\n *\n * An UNCACHED read, like {@link loadContent}: under `cacheComponents`, render the\n * chrome that awaits it inside `<Suspense>` — and keep the canvas page OUTSIDE\n * every boundary, or its 404s and redirects answer HTTP 200 (measured, EC-515).\n */\n loadGlobals(options?: LoadGlobalsOptions): Promise<SiteGlobals>\n /** The catch-all canvas route pair for `app/[[...slug]]/page.tsx`. */\n canvasRoute(options?: HostCanvasRouteOptions): CanvasRoute\n /**\n * The instant-publish webhook handler for `app/api/revalidate/route.ts`, wired to\n * `REVALIDATE_SECRET` and to this host's own project — {@link contentScope} and the\n * config locales — which it derives the tags of every event with (EC-579). Unset\n * secret = endpoint closed (401), never a default secret. Only the secret can be\n * overridden: the scope and locales are the ones this kit's cache entries carry.\n */\n revalidateRoute(overrides?: Partial<Pick<RevalidateRouteOptions, 'secret'>>): RevalidateRouteHandlers\n /**\n * The draft-mode ENABLE route for e.g. `app/api/draft/route.ts` —\n * `GET /api/draft?token=…&redirect=/path`, gated by the ONE\n * `DRAFT_PREVIEW_TOKEN` (the same secret the draft snapshot fetch sends, so\n * the two can never disconnect). Unset token = drafts unreachable.\n */\n previewRoute(): PreviewRouteHandlers\n /** The draft-mode DISABLE route (no token — it only reduces visibility). */\n previewDisableRoute(): PreviewRouteHandlers\n /**\n * Does the page for this catch-all `slug` OPEN full-bleed? Manifest-driven\n * (`presentation.fullBleed`, EC-368) and draft-mode aware — the host lays out\n * header clearance from data instead of a hand-kept component-id set. An\n * uncached read (see {@link loadContent}).\n */\n pageOpensFullBleed(slug: readonly string[] | undefined): Promise<boolean>\n}\n\n/** Materialize a config `routes` value (EC-207): run a derive or copy the array. */\nfunction materializeRoutes(\n routes: ElytraHostOptions['routes'],\n collections: readonly CollectionDef[],\n): RouteRecord[] {\n return typeof routes === 'function' ? routes([...collections]) : [...routes]\n}\n\n/** Create the delivery host handle — see the module docs for the full wiring. */\nexport function createElytraHost<const Collections extends readonly CollectionDef[]>(\n options: ElytraHostOptions<Collections>,\n): ElytraHost<Collections> {\n const env = resolveHostEnv(options.env ?? process.env)\n const routes = materializeRoutes(options.routes, options.collections)\n const live = env.convexUrl !== undefined && env.projectId !== undefined\n\n // EC-527: half-configured is never deliberate. Someone set one of the pair\n // meaning to connect, and the site would otherwise serve the fallback world\n // while looking completely healthy — the exact silent-wrong-world failure this\n // ticket exists to remove, except in production. An explicit gap beats a\n // connected-shaped no-op; `revalidateRoute` already makes that same call about\n // its secret. Both unset stays silent: no-backend is a supported mode.\n if (!live && (env.convexUrl !== undefined || env.projectId !== undefined)) {\n const [present, missing] =\n env.convexUrl !== undefined\n ? [ELYTRA_HOST_ENV_KEYS.convexUrl, ELYTRA_HOST_ENV_KEYS.projectId]\n : [ELYTRA_HOST_ENV_KEYS.projectId, ELYTRA_HOST_ENV_KEYS.convexUrl]\n throw new Error(\n `Elytra delivery host: ${present} is set but ${missing} is not — live content needs both. ` +\n `Left as it is, this site would serve fallback content and look healthy. ` +\n `Set ${missing} to go live, or unset ${present} to run the no-backend fallback on purpose.`,\n )\n }\n\n const contentMode: ElytraContentMode = live\n ? 'live'\n : options.fallbackContent\n ? 'fallback'\n : 'empty'\n const contentScope = live ? (env.projectId as string) : options.slug\n\n // EC-591: exactly the given list — no engine-registered components, no default\n // image swap (that is the opt-in `withNextImage`). EC-539: the option is the\n // CONFIG's entry shape (`implementation: unknown`) so the recommended\n // `{ ...site }` spread compiles; EC-541: registration takes that shape too and\n // checks each implementation at runtime — no narrowing cast here.\n const components = defineHostComponents(options.components ?? [])\n\n // The build-safe default fallback: an EMPTY source over the config's own\n // structure. Constructed once — it is fully static.\n const emptySource: ContentSource = createStaticContentSource({\n projectId: contentScope,\n locales: options.locales,\n routes,\n // Redirects are editorial DATA (EC-303) — the no-backend build has none.\n redirects: [],\n collections: [...options.collections],\n documents: [],\n assets: [],\n })\n\n const loadContent = (request: CanvasContentRequest): Promise<ContentSource> => {\n if (live) {\n return loadLiveContent(\n {\n convexUrl: env.convexUrl as string,\n projectId: env.projectId as string,\n locales: options.locales,\n collections: options.collections,\n routes,\n ...(env.draftToken ? { draftToken: env.draftToken } : {}),\n ...(options.fetch ? { fetch: options.fetch } : {}),\n },\n request.perspective,\n )\n }\n if (options.fallbackContent) return options.fallbackContent(request)\n return Promise.resolve(emptySource)\n }\n\n const loadGlobals = async (globalsOptions: LoadGlobalsOptions = {}): Promise<SiteGlobals> => {\n const perspective = globalsOptions.perspective ?? 'published'\n const source = await loadContent({ perspective })\n return resolveGlobals(source, {\n perspective,\n ...(options.globals ? { conventions: options.globals } : {}),\n })\n }\n\n const canvasRoute = (routeOptions: HostCanvasRouteOptions = {}): CanvasRoute => {\n const { cache, ...rest } = routeOptions\n return createCanvasRoute({\n loadContent,\n components,\n // Tag scope = `contentScope`, the SAME value `revalidateRoute` derives its\n // invalidation tags with (EC-579) — one scope, so they cannot disagree.\n // EC-543: the mode is in the entry key so no world ever reads another's entries.\n ...(cache === false ? {} : { cache: { mode: contentMode, scope: contentScope } }),\n ...rest,\n })\n }\n\n const revalidateRoute = (\n overrides: Partial<Pick<RevalidateRouteOptions, 'secret'>> = {},\n ): RevalidateRouteHandlers =>\n createRevalidateRoute({\n // An unset secret keeps the endpoint CLOSED (401) — the kit never ships a\n // default secret (an auth-shaped no-op is worse than an explicit gap).\n secret: 'secret' in overrides ? overrides.secret : env.revalidateSecret,\n // EC-579: the receiver's OWN project — the scope its cache entries are written\n // under (`canvasRoute` above) and every locale it serves. The emitter sends only\n // the event, so this is the one place the scope and locales are read for it.\n scope: contentScope,\n locales: options.locales.locales,\n })\n\n // ONE secret by construction: the preview gate and the draft snapshot fetch\n // both read env.draftToken — they cannot disconnect (EC-369 finding #4).\n const previewRoute = (): PreviewRouteHandlers => createPreviewRoute({ token: env.draftToken })\n const previewDisableRoute = (): PreviewRouteHandlers => createPreviewDisableRoute()\n\n const pageOpensFullBleed = async (slug: readonly string[] | undefined): Promise<boolean> => {\n const { isEnabled } = await draftMode()\n const perspective: Perspective = isEnabled ? 'draft' : 'published'\n const source = await loadContent({ perspective })\n const { locale, path } = splitLocalePath(pathFromSlug(slug), source.locales)\n const client = source.client({ perspective, locale })\n const resolved = client.resolvePage(path)\n return firstBlockFullBleed(resolved.document, components.registry)\n }\n\n return {\n collections: options.collections,\n contentScope,\n contentMode,\n env,\n routes,\n components,\n ...(options.frontendUrl !== undefined ? { frontendUrl: options.frontendUrl } : {}),\n loadContent,\n loadGlobals,\n canvasRoute,\n revalidateRoute,\n previewRoute,\n previewDisableRoute,\n pageOpensFullBleed,\n }\n}\n","/**\n * @elytracms/next — the embedded runtime (EC-144, vision AD-1): render\n * builder-managed pages inside the user's own Next.js App Router repo with\n * the user's own components. Drop `createCanvasRoute` into a catch-all\n * route, register components with `defineHostComponents`, done.\n *\n * Nothing here imports builder-only code (no studio, no operations, no\n * TanStack) — only the runtime packages: content accessors, the component\n * registry, and the runtime renderer.\n */\nexport const PACKAGE = '@elytracms/next'\n\n// Component registration — exactly the config's list (EC-591: the engine registers none).\nexport { defineHostComponents } from './components'\nexport type { HostComponent, HostComponents } from './components'\n\n// The server component. EC-499: `bodyItemsOf` is the body selection it renders\n// (declared `presentation.body`, else the EC-458 value-shape pick) — exported so\n// a host that renders a document body itself splits it the same way delivery does.\nexport { CanvasRenderer, bodyItemsOf } from './canvas-renderer'\nexport type { CanvasRendererProps, DeliveryPresentation } from './canvas-renderer'\n// EC-581 6a: one listing's baked result on the render outcome — documents, or why it could not run.\nexport type { BakedListing } from './canvas-renderer'\n\n// EC-574: the binding payload surface (`createBindingResolver`, `BindingPayloads`,\n// `SourcePayloadError`, `defaultBindingPayloads`, the route `payloads` option) and\n// the EC-166 source materialization (`materializeSourcePayloads`,\n// `collectBindingSourceIds`, `CanvasDataSource`) went with the data-binding\n// subsystem. Props are static values; dynamic data lives in the component (AD-12).\n\n// Content source seam + cache-tag merging (EC-145/146 plug in here).\nexport { mergeCacheTags } from './source'\nexport type { ContentSource } from './source'\n\n// PersistenceAdapter → ContentLookup snapshot bridge, plus the plain-data\n// assembler for hosts that prefetched the content themselves (EC-156).\nexport {\n createContentSnapshot,\n createStaticContentSource,\n mergeRouteRecords,\n} from './snapshot'\nexport type { ContentSnapshot, ContentSnapshotOptions, StaticContentData } from './snapshot'\n\n// Catch-all route helper (pure core + Next wiring).\nexport { pathFromSlug, resolveCanvasPage, splitLocalePath } from './route-core'\n// The locale ⇄ URL mapping, both directions from one definition (EC-486), and\n// the request-side capability over it (EC-531): `resolveLocale` answers which\n// locale a request is for, `textDirection` gives the `dir` a locale renders under.\n// A multi-locale host renders `<html lang dir>` from these; a single-locale host\n// needs neither (its `locales.default` is correct by definition).\nexport { localeUrlPath, resolveLocale, textDirection } from './locale-url'\nexport type { LocaleRequest, LocaleStrategy, LocaleUrlPath } from './locale-url'\nexport type { CanvasPageOutcome, ResolveCanvasPageOptions } from './route-core'\n// EC-515: `CANVAS_VALIDATION_SLUG` is the catch-all's reserved build-time\n// validation sample (`route.generateStaticParams`, required under cacheComponents).\nexport { CANVAS_VALIDATION_SLUG, createCanvasRoute } from './route'\nexport type {\n CanvasCacheOptions,\n CanvasContentRequest,\n CanvasRequestInfo,\n CanvasRoute,\n CanvasRouteOptions,\n CanvasRouteProps,\n // EC-524: what a host's `wrap` function is told — the collection to branch on,\n // the resolved document, and the whole resolution.\n CanvasWrapContext,\n} from './route'\n\n// Per-page SEO → Next metadata, and sitemap.xml from published routes (EC-171).\nexport { canvasPageMetadata, mergeMetadataOverParent, openGraphLocale } from './metadata'\nexport { canvasSitemapEntries, createCanvasSitemap } from './sitemap'\nexport type {\n CanvasSitemapOptions,\n CanvasSitemapResult,\n CreateCanvasSitemapOptions,\n SitemapParamsProvider,\n SitemapRouteContext,\n SitemapSkippedRoute,\n} from './sitemap'\n\n// Cache tags & instant publish (EC-146): tag format, the event → tags derivation,\n// and the signed revalidation webhook receiver. The route's cache entry itself is\n// internal to `createCanvasRoute` (a `'use cache'` function, EC-515 — the host\n// enables `cacheComponents`). EC-579: the wire itself (event schema, signer,\n// header) is `@elytracms/core/revalidation`, re-exported so a host that signs its\n// own event imports one package.\nexport {\n collectionCacheTag,\n docCacheTag,\n nextCacheTags,\n projectSweepTag,\n revalidationTags,\n routeCacheTag,\n scopeCacheTag,\n} from './cache'\nexport type { RevalidationProject } from './cache'\nexport {\n REVALIDATE_SIGNATURE_HEADER,\n revalidationEventSchema,\n signRevalidationBody,\n signedRevalidationRequest,\n} from '@elytracms/core/revalidation'\nexport type { RevalidationEvent, RevalidationEventKind } from '@elytracms/core/revalidation'\nexport { evaluateRevalidateRequest } from './revalidate-core'\nexport type { RevalidateEvaluation } from './revalidate-core'\nexport { createRevalidateRoute } from './revalidate'\nexport type { RevalidateRouteHandlers, RevalidateRouteOptions } from './revalidate'\n\n// Asset-pipeline image delivery (EC-147) + the host-image renderer (EC-368):\n// `withNextImage` (EC-591) swaps the image-role component's implementation for\n// next/image — opt-in, one line in the host seam file; `elytraHostImage` is the\n// next/image-backed renderer a host registers on the `@elytracms/next/host-image`\n// bridge (the bridge itself is its OWN subpath — importing it from here would\n// bundle a second singleton instance).\nexport { ElytraImage, createAssetImageLoader, elytraHostImage, withNextImage } from './image'\nexport type {\n AssetImageLoaderOptions,\n ElytraHostImageOptions,\n ElytraImageAsset,\n ElytraImageProps,\n NextImageOptions,\n} from './image'\n\n// The delivery host kit (EC-368): one factory over (env, project config)\n// yielding every engine-shaped host surface — content loading, the catch-all\n// route, revalidate, draft-mode plumbing, chrome globals, layout hints.\nexport { createElytraHost } from './host-kit'\nexport type {\n ElytraContentMode,\n ElytraHost,\n ElytraHostOptions,\n HostCanvasRouteOptions,\n LoadGlobalsOptions,\n} from './host-kit'\nexport { ELYTRA_HOST_ENV_KEYS, resolveHostEnv } from './host-env'\nexport type { ElytraHostEnv } from './host-env'\nexport { assembleLiveSource, fallbackHomeRoutes, loadLiveContent } from './host-content'\nexport type { LiveContentOptions } from './host-content'\nexport { resolveGlobals } from './host-globals'\nexport type {\n GlobalsConventions,\n ResolveGlobalsOptions,\n ResolvedLink,\n ResolvedNavEntry,\n ResolvedNavLink,\n SiteGlobals,\n} from './host-globals'\nexport { firstBlockFullBleed } from './full-bleed'\n\n// Draft preview (token-gated draftMode integration).\nexport { evaluatePreviewRequest, safeRedirectTarget } from './preview-core'\nexport type { PreviewEvaluation } from './preview-core'\nexport { createPreviewDisableRoute, createPreviewRoute } from './preview'\nexport type { PreviewRouteHandlers, PreviewRouteOptions } from './preview'\n\n// Re-exports so host apps can author manifests, declare their config, and shape\n// content WITHOUT depending on the internal packages (or on zod) directly — a\n// consuming repo installs ONLY `@elytracms/next` for delivery (EC-193).\nexport { defineComponent } from '@elytracms/core/component-registry'\n// EC-190: component props are field-defs (`PropField`), not the retired `PropSpec`.\nexport type { ComponentManifest, PropField, SlotSpec } from '@elytracms/core/component-registry'\nexport type { ComponentImplementations, RenderAsset } from '@elytracms/core/runtime-renderer'\nexport { z } from 'zod'\n\n// Config + content shapes a host declares (collections, locales, routes,\n// documents) — the public surface of the structure that lives in the repo\n// (AD-11). Re-exported here so the host imports one package (EC-193).\nexport {\n documentSchema,\n routeRecordSchema,\n redirectRecordSchema,\n localeConfigSchema,\n parseLocaleConfig,\n} from '@elytracms/core/cms-core'\nexport type {\n CollectionDef,\n CmsDocument,\n FieldDef,\n LocaleConfig,\n LocaleConfigInput,\n Locale,\n RouteRecord,\n RedirectRecord,\n} from '@elytracms/core/cms-core'\n// Composition node shape (a page document's `body` is a ComponentNode list, per AD-12).\nexport type { ComponentNode } from '@elytracms/core/project-graph'\n// The delivery perspective (draft | published).\nexport type { Perspective } from '@elytracms/core/content'\n// The delivery shape of an asset (resolved url + metadata).\nexport type { AssetRecord } from '@elytracms/core/persistence'\n"]}
|