kmcom-nuxt-layers 2.2.5 → 2.2.8

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.
Files changed (218) hide show
  1. package/docs/FEEDS.md +197 -0
  2. package/docs/LAYERS-FIXES.md +101 -0
  3. package/docs/MIGRATION.md +627 -0
  4. package/docs/feed-layer.md +374 -0
  5. package/docs/patch-picture-provider-type.md +52 -0
  6. package/docs/shaderGuide.md +2071 -0
  7. package/docs/types-architecture.md +234 -0
  8. package/layers/animations/app/components/Motion/CountUp.vue +1 -1
  9. package/layers/animations/app/components/Motion/Magnetic.vue +1 -1
  10. package/layers/animations/app/components/Motion/Marquee.vue +3 -2
  11. package/layers/animations/app/components/Motion/MarqueeText.vue +3 -2
  12. package/layers/animations/app/components/Motion/Tilt.vue +1 -1
  13. package/layers/animations/app/composables/useCountUp.ts +4 -1
  14. package/layers/animations/app/composables/useMagneticElement.ts +2 -4
  15. package/layers/animations/app/composables/useMarqueeCopies.ts +4 -4
  16. package/layers/animations/app/composables/useTiltEffect.ts +1 -1
  17. package/layers/animations/app/types/animations.ts +8 -0
  18. package/layers/animations/app/types/index.ts +1 -0
  19. package/layers/animations/package.json +4 -1
  20. package/layers/canvas/app/components/ShaderCanvas.vue +4 -4
  21. package/layers/canvas/app/composables/useRendererCapabilities.ts +19 -15
  22. package/layers/canvas/app/types/index.ts +1 -0
  23. package/layers/canvas/package.json +2 -1
  24. package/layers/canvas/tsconfig.json +2 -1
  25. package/layers/content/app/components/Blog/Card.vue +5 -5
  26. package/layers/content/app/components/Gallery/AmbientImage.vue +3 -3
  27. package/layers/content/app/components/Gallery/Card.vue +3 -3
  28. package/layers/content/app/components/Gallery/Lightbox.vue +5 -1
  29. package/layers/content/app/components/NuxtContent/Detail.vue +5 -1
  30. package/layers/content/app/components/NuxtContent/Surround.vue +5 -3
  31. package/layers/content/app/components/NuxtContent/Toc.vue +1 -1
  32. package/layers/content/app/components/Portfolio/Card.vue +5 -5
  33. package/layers/content/app/components/content/Figure.vue +3 -3
  34. package/layers/content/app/composables/useBlogPosts.ts +2 -2
  35. package/layers/content/app/composables/useCollectionItem.ts +1 -3
  36. package/layers/content/app/composables/useGalleryItems.ts +2 -2
  37. package/layers/content/app/types/index.ts +1 -0
  38. package/layers/content/package.json +2 -1
  39. package/layers/core/app/composables/useCache.ts +0 -1
  40. package/layers/core/app/composables/useErrorLog.ts +9 -11
  41. package/layers/core/app/plugins/error-handler.ts +36 -36
  42. package/layers/core/app/plugins/feature-detection.client.ts +15 -15
  43. package/layers/core/app/plugins/init.ts +121 -129
  44. package/layers/core/app/plugins/loading.client.ts +27 -27
  45. package/layers/core/app/plugins/scroll-guard.client.ts +26 -26
  46. package/layers/core/app/utils/helpers.ts +14 -12
  47. package/layers/core/nuxt.config.ts +1 -0
  48. package/layers/feeds/app/plugins/feed-head.ts +62 -63
  49. package/layers/feeds/package.json +2 -1
  50. package/layers/feeds/server/routes/feed/discovery.get.ts +1 -2
  51. package/layers/feeds/server/utils/content-adapter.ts +3 -2
  52. package/layers/forms/app/components/Form/Field.vue +4 -4
  53. package/layers/forms/app/types/index.ts +1 -0
  54. package/layers/forms/package.json +2 -1
  55. package/layers/forms/server/api/contact.post.ts +1 -1
  56. package/layers/layout/app/components/Layout/Grid/Item.vue +33 -19
  57. package/layers/layout/app/components/Layout/Page/Container.vue +11 -11
  58. package/layers/layout/app/components/Layout/Page/Header.vue +1 -1
  59. package/layers/layout/app/components/Layout/Page/index.vue +1 -1
  60. package/layers/layout/app/components/Layout/Section/Gallery.vue +6 -1
  61. package/layers/layout/app/components/Layout/Section/Title.vue +1 -1
  62. package/layers/layout/app/types/index.ts +1 -0
  63. package/layers/layout/package.json +2 -1
  64. package/layers/mailer/app/types/index.ts +1 -0
  65. package/layers/mailer/app/types/mailer.ts +25 -0
  66. package/layers/mailer/package.json +2 -1
  67. package/layers/motion/package.json +2 -1
  68. package/layers/navigation/app/components/Links/Group.vue +1 -0
  69. package/layers/navigation/app/components/Links/Named.vue +2 -0
  70. package/layers/navigation/app/types/index.ts +1 -0
  71. package/layers/navigation/package.json +4 -1
  72. package/layers/page-transitions/app/plugins/page-transitions.client.ts +9 -9
  73. package/layers/page-transitions/package.json +4 -1
  74. package/layers/routing/app/plugins/feature-flags.client.ts +9 -9
  75. package/layers/routing/app/types/app-config.d.ts +3 -1
  76. package/layers/routing/app/types/index.ts +1 -0
  77. package/layers/routing/package.json +2 -1
  78. package/layers/scripts/app/composables/useGtm.ts +1 -1
  79. package/layers/scripts/app/types/index.ts +1 -0
  80. package/layers/scripts/app/types/scripts.ts +14 -0
  81. package/layers/scripts/package.json +2 -1
  82. package/layers/scroll/app/components/Motion/ScrollScene.vue +3 -1
  83. package/layers/scroll/app/composables/useScrollSteps.ts +2 -9
  84. package/layers/scroll/app/composables/useSectionProgress.ts +2 -1
  85. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +54 -61
  86. package/layers/scroll/app/types/index.ts +1 -0
  87. package/layers/scroll/app/types/scroll.ts +32 -0
  88. package/layers/scroll/package.json +3 -0
  89. package/layers/seo/package.json +2 -1
  90. package/layers/shader/app/components/Material/Fresnel.client.vue +1 -1
  91. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  92. package/layers/shader/app/components/Material/Node.client.vue +7 -7
  93. package/layers/shader/app/components/Material/Noise.client.vue +1 -1
  94. package/layers/shader/app/components/Node/Color.client.vue +17 -20
  95. package/layers/shader/app/components/Node/Noise.client.vue +31 -34
  96. package/layers/shader/app/components/Pipeline/Aurora.client.vue +4 -8
  97. package/layers/shader/app/components/Pipeline/BilinearGradient.client.vue +8 -11
  98. package/layers/shader/app/components/Pipeline/BillowNoise.client.vue +4 -8
  99. package/layers/shader/app/components/Pipeline/BrightnessContrast.client.vue +6 -2
  100. package/layers/shader/app/components/Pipeline/CellularNoise.client.vue +4 -8
  101. package/layers/shader/app/components/Pipeline/Checkerboard.client.vue +4 -8
  102. package/layers/shader/app/components/Pipeline/Circle.client.vue +5 -8
  103. package/layers/shader/app/components/Pipeline/Clouds.client.vue +4 -8
  104. package/layers/shader/app/components/Pipeline/ColorBurnBlend.client.vue +2 -19
  105. package/layers/shader/app/components/Pipeline/ColorDodgeBlend.client.vue +2 -19
  106. package/layers/shader/app/components/Pipeline/ColourRamp.client.vue +5 -9
  107. package/layers/shader/app/components/Pipeline/ConicGradient.client.vue +5 -8
  108. package/layers/shader/app/components/Pipeline/Cross.client.vue +4 -8
  109. package/layers/shader/app/components/Pipeline/CurlNoise.client.vue +3 -7
  110. package/layers/shader/app/components/Pipeline/DarkenBlend.client.vue +2 -19
  111. package/layers/shader/app/components/Pipeline/DayNightCycle.client.vue +5 -8
  112. package/layers/shader/app/components/Pipeline/DiagonalGradient.client.vue +5 -8
  113. package/layers/shader/app/components/Pipeline/DiamondGradient.client.vue +5 -8
  114. package/layers/shader/app/components/Pipeline/DifferenceBlend.client.vue +2 -19
  115. package/layers/shader/app/components/Pipeline/DomainWarpedNoise.client.vue +4 -8
  116. package/layers/shader/app/components/Pipeline/Dots.client.vue +4 -8
  117. package/layers/shader/app/components/Pipeline/DuoTone.client.vue +5 -9
  118. package/layers/shader/app/components/Pipeline/ExclusionBlend.client.vue +3 -23
  119. package/layers/shader/app/components/Pipeline/ExponentialFog.client.vue +4 -7
  120. package/layers/shader/app/components/Pipeline/FilmBurn.client.vue +4 -7
  121. package/layers/shader/app/components/Pipeline/Flame.client.vue +4 -8
  122. package/layers/shader/app/components/Pipeline/FocalGradient.client.vue +5 -8
  123. package/layers/shader/app/components/Pipeline/GodRays.client.vue +4 -7
  124. package/layers/shader/app/components/Pipeline/GradientNoise.client.vue +4 -8
  125. package/layers/shader/app/components/Pipeline/Grid.client.vue +4 -8
  126. package/layers/shader/app/components/Pipeline/Halation.client.vue +3 -7
  127. package/layers/shader/app/components/Pipeline/HardLightBlend.client.vue +2 -19
  128. package/layers/shader/app/components/Pipeline/Haze.client.vue +4 -7
  129. package/layers/shader/app/components/Pipeline/Hexagon.client.vue +4 -8
  130. package/layers/shader/app/components/Pipeline/LensFlare.client.vue +4 -7
  131. package/layers/shader/app/components/Pipeline/LightenBlend.client.vue +2 -19
  132. package/layers/shader/app/components/Pipeline/LinearGradient4.client.vue +2 -2
  133. package/layers/shader/app/components/Pipeline/Marble.client.vue +4 -8
  134. package/layers/shader/app/components/Pipeline/MonochromeTint.client.vue +3 -7
  135. package/layers/shader/app/components/Pipeline/MultiplyBlend.client.vue +2 -19
  136. package/layers/shader/app/components/Pipeline/NoisyGradient.client.vue +4 -8
  137. package/layers/shader/app/components/Pipeline/NoisyGradientBlend.client.vue +4 -8
  138. package/layers/shader/app/components/Pipeline/OverlayBlend.client.vue +2 -19
  139. package/layers/shader/app/components/Pipeline/Polygon.client.vue +4 -8
  140. package/layers/shader/app/components/Pipeline/RaymarchTunnel.client.vue +4 -7
  141. package/layers/shader/app/components/Pipeline/Rectangle.client.vue +4 -8
  142. package/layers/shader/app/components/Pipeline/RidgedNoise.client.vue +4 -8
  143. package/layers/shader/app/components/Pipeline/Ring.client.vue +4 -8
  144. package/layers/shader/app/components/Pipeline/ScreenBlend.client.vue +2 -19
  145. package/layers/shader/app/components/Pipeline/SkyAtmosphere.client.vue +6 -9
  146. package/layers/shader/app/components/Pipeline/SoftLightBlend.client.vue +2 -19
  147. package/layers/shader/app/components/Pipeline/SplitTone.client.vue +4 -8
  148. package/layers/shader/app/components/Pipeline/Star.client.vue +4 -8
  149. package/layers/shader/app/components/Pipeline/Stripes.client.vue +4 -8
  150. package/layers/shader/app/components/Pipeline/Tint.client.vue +4 -7
  151. package/layers/shader/app/components/Pipeline/Triangle.client.vue +4 -8
  152. package/layers/shader/app/components/Pipeline/ValueNoise.client.vue +4 -8
  153. package/layers/shader/app/components/Pipeline/VoronoiEdges.client.vue +4 -8
  154. package/layers/shader/app/components/Pipeline/Water.client.vue +5 -8
  155. package/layers/shader/app/components/Pipeline/WaveBendLayer.client.vue +4 -7
  156. package/layers/shader/app/components/Pipeline/WaveColourLayer.client.vue +3 -7
  157. package/layers/shader/app/components/Pipeline/Wood.client.vue +4 -8
  158. package/layers/shader/app/components/Preset/Aurora.client.vue +15 -21
  159. package/layers/shader/app/components/Preset/Flow.client.vue +2 -1
  160. package/layers/shader/app/components/Preset/GradientMesh.client.vue +2 -1
  161. package/layers/shader/app/components/Preset/Nebula.client.vue +2 -1
  162. package/layers/shader/app/components/Preset/Ocean.client.vue +2 -1
  163. package/layers/shader/app/components/Preset/ThemeAurora.client.vue +30 -90
  164. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +30 -91
  165. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +30 -90
  166. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +30 -91
  167. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +30 -90
  168. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +30 -90
  169. package/layers/shader/app/components/Preset/ThemeWave.client.vue +30 -90
  170. package/layers/shader/app/components/Shader/Background.client.vue +4 -4
  171. package/layers/shader/app/components/Shader/Host.client.vue +31 -33
  172. package/layers/shader/app/components/Shader/Runtime.client.vue +15 -23
  173. package/layers/shader/app/composables/useAmbientMaterials.ts +53 -51
  174. package/layers/shader/app/composables/useShaderMixBlend.ts +26 -0
  175. package/layers/shader/app/composables/useThemePreset.ts +75 -0
  176. package/layers/shader/app/plugins/shader.client.ts +21 -21
  177. package/layers/shader/app/shaders/common/noise.ts +2 -7
  178. package/layers/shader/app/shaders/types.ts +6 -6
  179. package/layers/shader/app/types/tsl.ts +7 -25
  180. package/layers/shader/app/types/uniforms.ts +2 -1
  181. package/layers/shader/app/utils/tsl/color.ts +7 -1
  182. package/layers/shader/package.json +2 -1
  183. package/layers/theme/app/components/ThemePicker/Colors.vue +1 -3
  184. package/layers/theme/app/plugins/theme.client.ts +54 -51
  185. package/layers/theme/app/types/app-config.d.ts +4 -2
  186. package/layers/theme/app/types/index.ts +1 -0
  187. package/layers/theme/app/types/theme.ts +3 -18
  188. package/layers/theme/package.json +2 -1
  189. package/layers/theme/server/plugins/theme-fouc.ts +1 -1
  190. package/layers/transitions/package.json +4 -1
  191. package/layers/typography/app/components/Typography/CodeBlock.vue +2 -2
  192. package/layers/typography/app/components/Typography/Headline.vue +2 -2
  193. package/layers/typography/app/components/Typography/HeadlineScreen.vue +1 -1
  194. package/layers/typography/app/components/Typography/QuoteBlock.vue +4 -1
  195. package/layers/typography/app/components/Typography/TextStroke.vue +2 -0
  196. package/layers/typography/app/components/Typography/index.vue +36 -27
  197. package/layers/typography/app/composables/typography.ts +27 -21
  198. package/layers/typography/app/types/colors.ts +9 -29
  199. package/layers/typography/app/types/index.ts +2 -0
  200. package/layers/typography/package.json +4 -1
  201. package/layers/ui/package.json +2 -1
  202. package/layers/visual/app/app.config.ts +5 -2
  203. package/layers/visual/app/components/Accent/Blob.vue +20 -20
  204. package/layers/visual/app/components/Accent/Scene.vue +2 -2
  205. package/layers/visual/app/components/Base/Modal.vue +2 -2
  206. package/layers/visual/app/components/Gradient/Background.vue +2 -2
  207. package/layers/visual/app/components/Gradient/Text.vue +2 -2
  208. package/layers/visual/app/components/Media/Picture.vue +3 -1
  209. package/layers/visual/app/components/Progress/Bar.vue +6 -6
  210. package/layers/visual/app/components/Tint/Overlay.vue +14 -14
  211. package/layers/visual/app/composables/accent.ts +10 -8
  212. package/layers/visual/app/composables/gradient.ts +2 -3
  213. package/layers/visual/app/composables/tint.ts +7 -7
  214. package/layers/visual/app/types/index.ts +6 -0
  215. package/layers/visual/app/types/media.ts +4 -2
  216. package/layers/visual/app/types/tint.ts +2 -1
  217. package/layers/visual/package.json +4 -1
  218. package/package.json +5 -2
@@ -0,0 +1,627 @@
1
+ # Agent Migration Guide — Update an app to nuxt-layers v2.2.x
2
+
3
+ **Audience:** an AI coding agent (or developer) updating a downstream Nuxt app that consumes these layers.
4
+ **Goal:** apply the breaking changes from the `feeds` / `site` / content-type work.
5
+
6
+ > Adding feeds to your content sections is a **feature task**, not a breaking change — it has its own guide: [`FEEDS.md`](./FEEDS.md). Do the breaking changes below first, then follow that.
7
+
8
+ Work top to bottom. Each change has a **Detect** step (how to tell if it applies) and an **Apply** step. Skip anything Detect doesn't find. Run the **Verify** checklist before declaring done. Where this summary and the detailed history sections below disagree, the code wins — check the cited files.
9
+
10
+ ## 0. Preconditions
11
+
12
+ - Confirm the app extends these layers (an `extends` array, or a `LAYER_PATHS` / resolver block like the playground's, in `nuxt.config.ts`).
13
+ - Confirm the layer checkout is at **v2.2.x** (`git -C <layers-repo> describe --tags`).
14
+ - The app must use `@nuxt/content` v3 with a `content.config.ts` at the **app root** (Nuxt Content v3 resolves it from the app root, not the layer).
15
+
16
+ ## 1. BREAKING — Site metadata moved to top-level `site`
17
+
18
+ Site metadata is no longer namespaced under `feedsLayer.site`. It now lives at the top-level `site` key in `app.config.ts`, typed by the **core** layer (`SiteConfig` in `layers/core/app/types/site.ts`). Any layer can read it via `useAppConfig().site`.
19
+
20
+ **Detect:** grep the app for `feedsLayer.site` or a `feedsLayer: { site:` block in `app/app.config.ts`.
21
+
22
+ **Apply:** move the object out to a top-level `site` key.
23
+
24
+ ```ts
25
+ // app/app.config.ts — BEFORE
26
+ export default defineAppConfig({
27
+ feedsLayer: {
28
+ site: { title: 'My Site', url: 'https://example.com', author: { name: 'Jane', email: 'jane@example.com' } },
29
+ },
30
+ })
31
+
32
+ // app/app.config.ts — AFTER
33
+ export default defineAppConfig({
34
+ site: {
35
+ title: 'My Site',
36
+ description: 'Short description used in feed channels and meta tags',
37
+ url: 'https://example.com', // canonical origin, NO trailing slash
38
+ author: { name: 'Jane', email: 'jane@example.com' },
39
+ },
40
+ })
41
+ ```
42
+
43
+ `SiteConfig` fields: `title`, `subtitle`, `description`, `url`, `author`, `image`, `favicon`, `copyright`. All optional, but feeds need at least `title` and `url` for valid channel metadata.
44
+
45
+ > Reminder: `app.config.ts` MUST be inside `app/` (the srcDir). A root-level `app.config.ts` is silently ignored in Nuxt 4.
46
+
47
+ ## 2. BREAKING — `SiteAuthor` type renamed to `Author`
48
+
49
+ **Detect:** grep for `SiteAuthor`.
50
+
51
+ **Apply:** rename every import and annotation.
52
+
53
+ ```ts
54
+ // BEFORE
55
+ import type { SiteAuthor } from '#layers/core/app/types/site'
56
+ const author: SiteAuthor = { name: 'Jane' }
57
+
58
+ // AFTER
59
+ import type { Author } from '#layers/core/app/types/site'
60
+ const author: Author = { name: 'Jane' }
61
+ ```
62
+
63
+ Shape is unchanged: `{ name: string; email?: string; link?: string }`.
64
+
65
+ ## 3. BREAKING — Content author `url` → `link`
66
+
67
+ The author `url` field is renamed to `link` to match core's `Author`. This touches **three** places — change all of them or feeds/templates silently drop the author link.
68
+
69
+ **Detect:** grep the app's `content.config.ts` and `content/**/*.md` frontmatter for `url:` inside an `authors` block.
70
+
71
+ **3a. Zod schema** (app `content.config.ts`):
72
+
73
+ ```ts
74
+ // BEFORE
75
+ authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() }))
76
+ // AFTER
77
+ authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() }))
78
+ ```
79
+
80
+ **3b. Markdown frontmatter** (every content file with authors):
81
+
82
+ ```yaml
83
+ # BEFORE # AFTER
84
+ authors: authors:
85
+ - name: Jane Doe - name: Jane Doe
86
+ url: https://... link: https://...
87
+ ```
88
+
89
+ **3c. TypeScript** — `ContentAuthor` now extends `Author`. If the app defines its own author type, extend `Author` rather than redeclaring `url`.
90
+
91
+ > Do NOT pass `schema:` to `defineCollection`. c12 uses jiti with `moduleCache:false`, so the validator never initializes. Define the Zod object inline in the collection, the established pattern in this repo.
92
+
93
+ ## 4. NON-BREAKING — `BaseContent` shared interface
94
+
95
+ `BlogPost`, `PortfolioItem`, `GalleryItem`, and `ContentPage` now extend a shared `BaseContent` (`title`, `description?`, `image?`, `tags?`, `date?`, `draft?`) from `#layers/content/app/types/content`.
96
+
97
+ **Apply only if** the app has custom content types that duplicate those fields — extend `BaseContent` instead:
98
+
99
+ ```ts
100
+ import type { BaseContent } from '#layers/content/app/types/content'
101
+ interface MyCustomPage extends BaseContent { featuredVideo?: string }
102
+ ```
103
+
104
+ ## 5. Next: add feeds (feature, not breaking)
105
+
106
+ Once the breaking changes are applied and `site` is set, wire RSS/Atom/JSON feeds into your content sections by following [`FEEDS.md`](./FEEDS.md).
107
+
108
+ ## 6. Verify
109
+
110
+ 1. **Grep clean:** no remaining `SiteAuthor`, no `feedsLayer.site`, no `url:` inside `authors` blocks (frontmatter + Zod).
111
+ 2. **Typecheck:** `pnpm typecheck` passes (catches `Author`/`BaseContent`/`ContentAuthor` mismatches).
112
+ 3. **Lint:** `pnpm lint` passes (no semicolons, single quotes, ES5 trailing commas, ≤100 cols).
113
+ 4. **Dev smoke:** `pnpm dev` boots and content pages render with author links intact.
114
+
115
+ ## Quick reference
116
+
117
+ | Change | Type | Action |
118
+ |--------|------|--------|
119
+ | `feedsLayer.site` → `site` | Breaking | Move to top-level `site` in `app/app.config.ts` |
120
+ | `SiteAuthor` → `Author` | Breaking | Rename imports/annotations |
121
+ | Author `url` → `link` | Breaking | Update Zod schema + frontmatter + TS (3 places) |
122
+ | `BaseContent` | Additive | Extend it in custom content types |
123
+ | Add feeds | Feature | Follow [`FEEDS.md`](./FEEDS.md) |
124
+
125
+ The sections below are the detailed, chronological history these summaries were distilled from.
126
+
127
+ ---
128
+
129
+ # Migration Guide: Feeds Layer, Site Config & Content Type Normalisation
130
+
131
+ Four related changes landed together. None are strictly breaking for existing pages, but anything that reads `feedsLayer.site`, imports `SiteAuthor`, or uses the `authors[].url` frontmatter field needs updating.
132
+
133
+ ---
134
+
135
+ ## 1. Shared `site` config (replaces `feedsLayer.site`)
136
+
137
+ Site metadata is no longer namespaced under `feedsLayer`. It lives at the top-level `site` key in `app.config.ts`, owned by the core layer's type declarations. Any layer can read it.
138
+
139
+ **Before:**
140
+ ```ts
141
+ // app/app.config.ts
142
+ export default defineAppConfig({
143
+ feedsLayer: {
144
+ site: {
145
+ title: 'My Site',
146
+ url: 'https://example.com',
147
+ author: { name: 'Jane', email: 'jane@example.com' },
148
+ },
149
+ },
150
+ })
151
+ ```
152
+
153
+ **After:**
154
+ ```ts
155
+ // app/app.config.ts
156
+ export default defineAppConfig({
157
+ site: {
158
+ title: 'My Site',
159
+ url: 'https://example.com',
160
+ author: { name: 'Jane', email: 'jane@example.com' },
161
+ },
162
+ })
163
+ ```
164
+
165
+ The `site` key is typed via `SiteConfig` from the core layer — `useAppConfig().site` has full TypeScript completion.
166
+
167
+ ---
168
+
169
+ ## 2. `SiteAuthor` renamed to `Author`
170
+
171
+ The core type previously exported as `SiteAuthor` is now exported as `Author`. It's the base author shape used by `SiteConfig.author`, `ContentAuthor`, and any other layer that deals with authorship.
172
+
173
+ **Before:**
174
+ ```ts
175
+ import type { SiteAuthor } from '#layers/core/app/types/site'
176
+
177
+ const author: SiteAuthor = { name: 'Jane' }
178
+ ```
179
+
180
+ **After:**
181
+ ```ts
182
+ import type { Author } from '#layers/core/app/types/site'
183
+
184
+ const author: Author = { name: 'Jane' }
185
+ ```
186
+
187
+ `SiteConfig.author` still has the same shape — only the type name changed.
188
+
189
+ ---
190
+
191
+ ## 3. Content author `url` renamed to `link`
192
+
193
+ The `url` field on blog post authors has been renamed to `link` to align with core's `Author` type. The Zod schema, TypeScript interface, and markdown frontmatter all need updating.
194
+
195
+ **Zod schema (in your `content.config.ts`):**
196
+ ```ts
197
+ // Before
198
+ authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() }))
199
+
200
+ // After
201
+ authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() }))
202
+ ```
203
+
204
+ **Markdown frontmatter:**
205
+ ```yaml
206
+ # Before
207
+ authors:
208
+ - name: Jane Doe
209
+ url: https://github.com/janedoe
210
+
211
+ # After
212
+ authors:
213
+ - name: Jane Doe
214
+ link: https://github.com/janedoe
215
+ ```
216
+
217
+ **TypeScript interface** — `ContentAuthor` now extends `Author` instead of being a standalone type:
218
+ ```ts
219
+ import type { Author } from '#layers/core/app/types/site'
220
+
221
+ // ContentAuthor from the content layer
222
+ interface ContentAuthor extends Author {
223
+ avatar?: string
224
+ // inherits: name, email?, link?
225
+ }
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 4. `BaseContent` base interface
231
+
232
+ A `BaseContent` interface was extracted from the shared fields across `BlogPost`, `PortfolioItem`, `GalleryItem`, and `ContentPage`. All four now extend it.
233
+
234
+ ```ts
235
+ interface BaseContent {
236
+ title: string
237
+ description?: string
238
+ image?: string
239
+ tags?: string[]
240
+ date?: string
241
+ draft?: boolean
242
+ }
243
+ ```
244
+
245
+ If you have custom types that replicate these fields, you can now extend `BaseContent` directly:
246
+
247
+ ```ts
248
+ import type { BaseContent } from '#layers/content/app/types/content'
249
+
250
+ interface MyCustomPage extends BaseContent {
251
+ featuredVideo?: string
252
+ }
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 5. Feeds layer: collection wiring
258
+
259
+ Feeds are now driven by `feedsLayer.feed.collections` — an array of collection names to expose as feeds. The default is `['blog']`. Add more collections to get automatic feed routes for each.
260
+
261
+ ```ts
262
+ // app/app.config.ts
263
+ export default defineAppConfig({
264
+ site: { ... },
265
+ feedsLayer: {
266
+ feed: {
267
+ limit: 30,
268
+ collections: ['blog'], // expose these collections as feeds
269
+ defaultCollection: 'blog', // used by /feed/rss shorthand routes
270
+ },
271
+ },
272
+ })
273
+ ```
274
+
275
+ Available feed URLs for each collection:
276
+
277
+ | URL | Format |
278
+ |-----|--------|
279
+ | `/feed/:collection/rss` | RSS 2.0 |
280
+ | `/feed/:collection/atom` | Atom 1.0 |
281
+ | `/feed/:collection/json` | JSON Feed 1.1 |
282
+ | `/feed/rss` | RSS for `defaultCollection` |
283
+ | `/feed/atom` | Atom for `defaultCollection` |
284
+ | `/feed/json` | JSON Feed for `defaultCollection` |
285
+ | `/feed` | JSON index of all configured feeds |
286
+
287
+ To expose portfolio items as a feed:
288
+ ```ts
289
+ feedsLayer: {
290
+ feed: { collections: ['blog', 'portfolio'] }
291
+ }
292
+ ```
293
+
294
+ ### Netlify / static deployment: prerender collection routes
295
+
296
+ Feed routes are server routes — they aren't discoverable by the prerender crawler. The feeds layer automatically prerenders the shorthand routes (`/feed`, `/feed/rss`, `/feed/atom`, `/feed/json`), but **collection-specific routes must be listed explicitly** in your app's `nuxt.config.ts`:
297
+
298
+ ```ts
299
+ nitro: {
300
+ prerender: {
301
+ routes: [
302
+ '/feed/blog/rss',
303
+ '/feed/blog/atom',
304
+ '/feed/blog/json',
305
+ // one set per collection in feedsLayer.feed.collections
306
+ ],
307
+ },
308
+ },
309
+ ```
310
+
311
+ Without this, collection routes return 404 on Netlify and other static hosts.
312
+
313
+ ---
314
+
315
+ ## 6. Feed autodiscovery in `<head>`
316
+
317
+ The feeds layer automatically injects `<link rel="alternate">` tags so RSS readers and browsers can discover feeds without cluttering every page with links for every collection.
318
+
319
+ **The rule:** every page gets the main site feeds. Pages that belong to a non-default collection also get that collection's specific feeds.
320
+
321
+ | Page | Links injected |
322
+ |------|---------------|
323
+ | `/`, `/about`, any non-collection page | 3 main site feeds |
324
+ | `/blog`, `/blog/hello-world` (if `blog` = `defaultCollection`) | 3 main site feeds (these already point to blog) |
325
+ | `/portfolio`, `/portfolio/project` (non-default collection) | 3 main site feeds + 3 portfolio feeds |
326
+
327
+ The main site feeds use the shorthand routes (`/feed/rss`, `/feed/atom`, `/feed/json`) which always serve `defaultCollection`. Collection-specific links are only added for non-default collections — where you're actively browsing content not covered by the main feeds.
328
+
329
+ No action required. The collection a page belongs to is inferred from the first path segment matching an entry in `feedsLayer.feed.collections`.
330
+
331
+ ---
332
+
333
+ ## Quick Reference
334
+
335
+ | Change | Action required |
336
+ |--------|----------------|
337
+ | Site metadata | Move `feedsLayer.site` → top-level `site` in `app.config.ts` |
338
+ | `SiteAuthor` type import | Rename to `Author` |
339
+ | Author `url` in frontmatter | Rename `url:` → `link:` in blog author blocks |
340
+ | Author `url` in Zod schema | Rename `url` → `link` in `authors` object schema |
341
+ | Collection feeds | Set `feedsLayer.feed.collections` in `app.config.ts` |
342
+ | Feed autodiscovery | Nothing — automatic via the feeds layer plugin |
343
+
344
+ ---
345
+
346
+ # Migration Guide: v1.6.34 — Content Layer Collection Aliases & Section Disabling
347
+
348
+ Two new capabilities have been added to the content layer:
349
+
350
+ 1. **Collection aliases** — reuse a collection schema under a custom name (e.g. `works` instead of `portfolio`)
351
+ 2. **Section disabling** — suppress content routes you don't need via `app.config.ts`
352
+
353
+ Neither change is breaking. Existing apps using `portfolio`, `blog`, or `gallery` work exactly as before.
354
+
355
+ ---
356
+
357
+ ## 1. Collection Aliases
358
+
359
+ ### The problem
360
+
361
+ The content layer hard-codes the collection names `blog`, `portfolio`, and `gallery`. If your app wants a `works` section that uses the portfolio schema, you previously had to duplicate the entire Zod schema in your own `content.config.ts`.
362
+
363
+ ### The solution
364
+
365
+ The layer now exports named schemas and factory functions from `content.config.ts`:
366
+
367
+ ```ts
368
+ // Named schema exports:
369
+ contentCollectionSchema
370
+ blogCollectionSchema
371
+ portfolioCollectionSchema
372
+ galleryCollectionSchema
373
+
374
+ // Factory functions (return a ready-to-use defineCollection(...) object):
375
+ createContentCollection(source?: string)
376
+ createBlogCollection(source?: string)
377
+ createPortfolioCollection(source?: string)
378
+ createGalleryCollection(source?: string)
379
+ ```
380
+
381
+ Each factory defaults to the conventional source glob if you omit the argument.
382
+
383
+ ### Registering a custom-named collection
384
+
385
+ In your app's `content.config.ts`, import the factory and register it under any name you like:
386
+
387
+ ```ts
388
+ import { createPortfolioCollection, createBlogCollection } from '#layers/content/content.config'
389
+
390
+ export default defineContentConfig({
391
+ collections: {
392
+ blog: createBlogCollection(),
393
+ works: createPortfolioCollection('works/**/*.md'),
394
+ // gallery omitted — not needed
395
+ }
396
+ })
397
+ ```
398
+
399
+ Your markdown files now live at `content/works/`, and `queryCollection('works')` returns portfolio-shaped items.
400
+
401
+ ### Querying the aliased collection
402
+
403
+ The layer also exports `createPortfolioComposables`, a factory that returns fully-wired `useItems` and `useItem` composables bound to your custom collection name:
404
+
405
+ ```ts
406
+ // app/composables/useWorks.ts
407
+ import { createPortfolioComposables } from '#layers/content/app/composables/createPortfolioComposables'
408
+
409
+ const { useItems: useWorksItems, useItem: useWorksItem } = createPortfolioComposables('works')
410
+ export { useWorksItems, useWorksItem }
411
+ ```
412
+
413
+ `useWorksItems` accepts the same options as `usePortfolioItems` (`featured`, `tags`, `limit`). `useWorksItem(slug)` fetches a single item by slug.
414
+
415
+ ### Using Portfolio components with a custom collection
416
+
417
+ The `PortfolioList`, `PortfolioCard`, and `PortfolioDetail` components accept data via props — they don't query the collection directly. Pass your `works` data in to reuse them:
418
+
419
+ ```vue
420
+ <PortfolioList :items="worksItems" />
421
+ <PortfolioDetail :item="worksItem" />
422
+ ```
423
+
424
+ ---
425
+
426
+ ## 2. Section Disabling
427
+
428
+ ### The problem
429
+
430
+ Layer pages at `/blog`, `/portfolio`, and `/gallery` are always registered. Previously there was no way to suppress a route you didn't want without creating a 404 override page in your app.
431
+
432
+ ### The solution
433
+
434
+ All content routes now check `useAppConfig().contentLayer.sections` at runtime and throw a 404 if a section is explicitly set to `false`.
435
+
436
+ ### Disabling sections
437
+
438
+ In your app's `app.config.ts` (must be inside `app/`):
439
+
440
+ ```ts
441
+ export default defineAppConfig({
442
+ contentLayer: {
443
+ sections: {
444
+ blog: true,
445
+ portfolio: false, // /portfolio and /portfolio/[slug] → 404
446
+ gallery: false, // /gallery and all nested routes → 404
447
+ },
448
+ },
449
+ })
450
+ ```
451
+
452
+ All three flags default to `true` in the layer, so you only need to declare the ones you want to disable.
453
+
454
+ **Note:** The routes still exist at the Nuxt router level — they are compiled at build time from the layer's page files. Disabling returns a 404 response at runtime. Links to disabled routes will hit a 404 error page rather than being absent from the router.
455
+
456
+ ---
457
+
458
+ ## Quick Reference
459
+
460
+ | Goal | How |
461
+ |---|---|
462
+ | Reuse portfolio schema as `works` | `works: createPortfolioCollection('works/**/*.md')` in `content.config.ts` |
463
+ | Query custom-named collection | `createPortfolioComposables('works')` → `useItems` / `useItem` |
464
+ | Disable the gallery section | `contentLayer: { sections: { gallery: false } }` in `app.config.ts` |
465
+ | Disable multiple sections | List each with `false` in `sections` |
466
+
467
+ ---
468
+
469
+ # Migration Guide: v1.5.x → v1.6.0
470
+
471
+ v1.6.0 restructures the layout and content systems. The main theme is **opt-in chrome** — header, footer, and nav are no longer always-on, full-bleed pages now use `layout: false`, and the global Locomotive Scroll wrapper is gone.
472
+
473
+ ---
474
+
475
+ ## Breaking Changes
476
+
477
+ ### 1. `grid.vue` layout now requires explicit props for header/footer
478
+
479
+ Header and footer are no longer rendered by default. Pages that relied on `layout: 'grid'` to get them must now pass props.
480
+
481
+ **Before (v1.5.x):**
482
+ ```ts
483
+ definePageMeta({ layout: 'grid' })
484
+ // header + footer always rendered
485
+ ```
486
+
487
+ **After (v1.6.0):**
488
+ ```ts
489
+ // Opt-in to header and footer:
490
+ definePageMeta({
491
+ layout: { name: 'grid', props: { showHeader: true, showFooter: true } },
492
+ })
493
+
494
+ // No chrome needed (content-only page):
495
+ definePageMeta({ layout: 'grid' })
496
+
497
+ // Full-bleed pages (no mastmain grid at all):
498
+ definePageMeta({ layout: false })
499
+ ```
500
+
501
+ Available props on `grid.vue` (all `boolean`, default `false`):
502
+
503
+ | Prop | Description |
504
+ |---|---|
505
+ | `showHeader` | Renders `<MastHeader>` |
506
+ | `showNav` | Renders `<MastNav>` |
507
+ | `showFooter` | Renders `<MastFooter>` |
508
+ | `showGridDebug` | Renders `<LayoutGridDebug>` overlay |
509
+
510
+ ---
511
+
512
+ ### 2. `MastScroller` removed
513
+
514
+ The global Locomotive Scroll wrapper in `default.vue` has been removed. Smooth scroll is now per-page via the `useSmoothScroll()` composable.
515
+
516
+ **Before (v1.5.x):**
517
+ ```vue
518
+ <!-- default.vue automatically wrapped everything in MastScroller -->
519
+ <!-- No per-page setup required -->
520
+ ```
521
+
522
+ **After (v1.6.0):**
523
+ ```ts
524
+ // In any page that needs smooth scroll:
525
+ const { scrollTo, scrollToTop, velocity, progress } = useSmoothScroll()
526
+ ```
527
+
528
+ Pages that don't call `useSmoothScroll()` will use native browser scroll — no behaviour change for those pages.
529
+
530
+ ---
531
+
532
+ ### 3. `LayoutPageContainer` deprecated → use `LayoutPage`
533
+
534
+ `LayoutPageContainer` has been superseded by `LayoutPage`. The legacy component still exists for backwards compatibility but is no longer maintained.
535
+
536
+ **Before (v1.5.x):**
537
+ ```vue
538
+ <LayoutPageContainer title="My Page" :show-header="false">
539
+ <LayoutSection>...</LayoutSection>
540
+ </LayoutPageContainer>
541
+ ```
542
+
543
+ **After (v1.6.0):**
544
+ ```vue
545
+ <LayoutPage title="My Page">
546
+ <LayoutSection>...</LayoutSection>
547
+ </LayoutPage>
548
+ ```
549
+
550
+ **Key difference:** `LayoutPage` defaults `showHeader` to `false`. If you need the page header block, pass it explicitly:
551
+
552
+ ```vue
553
+ <LayoutPage title="My Page" :show-header="true">
554
+ ...
555
+ </LayoutPage>
556
+ ```
557
+
558
+ ---
559
+
560
+ ### 4. Full-bleed / animation pages must use `layout: false`
561
+
562
+ Pages with custom full-screen sections (WebGL canvases, GSAP hero animations, horizontal scroll tracks) must opt out of `LayoutMain` entirely. Using `layout: 'grid'` constrains them to a single `1fr` column which breaks full-width layouts.
563
+
564
+ ```ts
565
+ // Full-bleed or animation-heavy page:
566
+ definePageMeta({ layout: false })
567
+
568
+ // With SSR disabled (e.g. WebGL):
569
+ definePageMeta({ ssr: false, layout: false })
570
+ ```
571
+
572
+ **Rule of thumb:**
573
+
574
+ - Page uses `LayoutSection` + `LayoutGridItem`? → `layout: 'grid'` (with props as needed)
575
+ - Page uses custom full-width `<div>`/`<section>` elements? → `layout: false`
576
+
577
+ ---
578
+
579
+ ### 5. Content composables: `useContentData` replaces bare `useAsyncData`
580
+
581
+ All built-in content composables now use `useContentData` — a `createUseAsyncData` wrapper with `dedupe: 'cancel'` — instead of calling `useAsyncData` directly. If you've extended or wrapped any content composables, update the inner call:
582
+
583
+ **Before (v1.5.x):**
584
+ ```ts
585
+ return useAsyncData('my-key', () => queryCollection('blog').all())
586
+ ```
587
+
588
+ **After (v1.6.0):**
589
+ ```ts
590
+ return useContentData('my-key', () => queryCollection('blog').all())
591
+ ```
592
+
593
+ `useContentData` is exported from the content layer and available as a Nuxt auto-import. The API is identical to `useAsyncData`.
594
+
595
+ ---
596
+
597
+ ### 6. `payloadExtraction: 'client'` (nuxt.config.ts)
598
+
599
+ Add this experimental flag to your app's `nuxt.config.ts` if you use content collections or data-heavy pages. It moves payload extraction to the client, reducing SSR bundle size for content-driven routes.
600
+
601
+ ```ts
602
+ export default defineNuxtConfig({
603
+ experimental: {
604
+ payloadExtraction: 'client',
605
+ },
606
+ })
607
+ ```
608
+
609
+ ---
610
+
611
+ ## Non-Breaking Additions
612
+
613
+ - **`MastNav` component** — renders `<nav><slot /></nav>`. Enable via `showNav: true` on the `grid.vue` layout.
614
+ - **`showGridDebug` prop** — `LayoutGridDebug` is now controlled by the `showGridDebug` prop on `grid.vue` instead of always rendering.
615
+
616
+ ---
617
+
618
+ ## Quick Reference
619
+
620
+ | Pattern | v1.5.x | v1.6.0 |
621
+ |---|---|---|
622
+ | Grid page with header + footer | `layout: 'grid'` | `layout: { name: 'grid', props: { showHeader: true, showFooter: true } }` |
623
+ | Grid page, no chrome | `layout: 'grid'` | `layout: 'grid'` |
624
+ | Full-bleed / animation page | `layout: 'grid'` (broken) | `layout: false` |
625
+ | Page wrapper component | `<LayoutPageContainer>` | `<LayoutPage>` |
626
+ | Global smooth scroll | `MastScroller` in layout (auto) | `useSmoothScroll()` per-page |
627
+ | Content async data | `useAsyncData(...)` | `useContentData(...)` |