kmcom-nuxt-layers 2.6.2 → 2.7.0

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 (114) hide show
  1. package/docs/ARCHITECTURE.md +21 -17
  2. package/docs/FEEDS.md +42 -34
  3. package/docs/IMPROVE-AUDIT-README.md +9 -10
  4. package/docs/IMPROVE-AUDIT-RESULTS.md +16 -17
  5. package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +29 -29
  6. package/docs/LAYERS-FIXES.md +1 -0
  7. package/docs/LAYOUT.md +91 -86
  8. package/docs/MIGRATION.md +93 -60
  9. package/docs/UPAGE-GRID-INTEGRATION.md +12 -10
  10. package/docs/USAGE.md +29 -38
  11. package/layers/animations/app/utils/pointerMotion.ts +1 -4
  12. package/layers/baseline/app/components/BaselineStatus.vue +13 -13
  13. package/layers/baseline/app/plugins/baseline-status.client.ts +4 -1
  14. package/layers/canvas/app/composables/useRendererCapabilities.ts +2 -2
  15. package/layers/content/app/components/Blog/Card.vue +1 -0
  16. package/layers/content/app/components/Gallery/Card.vue +1 -1
  17. package/layers/content/app/components/Gallery/Detail.vue +8 -1
  18. package/layers/content/app/components/Portfolio/Card.vue +1 -0
  19. package/layers/content/app/components/Portfolio/ColorPalette.vue +8 -1
  20. package/layers/content/app/components/Portfolio/Typography.vue +12 -2
  21. package/layers/content/app/composables/useGalleryItems.ts +3 -2
  22. package/layers/content/app/composables/usePortfolioItems.ts +3 -2
  23. package/layers/core/app/components/DiagnosticsPage.vue +15 -3
  24. package/layers/core/app/utils/featureClasses.test.ts +1 -3
  25. package/layers/core/app/utils/featureClasses.ts +1 -3
  26. package/layers/core/app/utils/helpers.test.ts +6 -2
  27. package/layers/core/nuxt.config.ts +2 -0
  28. package/layers/feeds/app/assets/css/feeds.css +3 -2
  29. package/layers/feeds/app/components/Feeds/Index.vue +36 -6
  30. package/layers/feeds/app/components/Feeds/RouteCard.vue +1 -3
  31. package/layers/feeds/app/plugins/feed-head.ts +2 -2
  32. package/layers/feeds/package.json +1 -1
  33. package/layers/feeds/public/feed/style.css +9 -9
  34. package/layers/feeds/server/utils/content-adapter.test.ts +3 -4
  35. package/layers/feeds/server/utils/content-adapter.ts +1 -1
  36. package/layers/feeds/server/utils/feed-author.test.ts +3 -3
  37. package/layers/feeds/server/utils/feed-config.test.ts +28 -6
  38. package/layers/feeds/server/utils/feed-config.ts +1 -4
  39. package/layers/feeds/server/utils/feed-service.ts +1 -1
  40. package/layers/feeds/server/utils/formats/rss.ts +1 -1
  41. package/layers/feeds/server/utils/formats.test.ts +1 -1
  42. package/layers/forms/app/utils/fieldProps.ts +1 -2
  43. package/layers/forms/server/api/contact.post.ts +4 -1
  44. package/layers/layout/app/assets/css/layout/grids.css +12 -3
  45. package/layers/layout/app/assets/css/layout/modes/fluid.css +9 -0
  46. package/layers/layout/app/components/Layout/Section/Sidebar.vue +11 -4
  47. package/layers/layout/app/utils/gridPlacementStyle.test.ts +3 -9
  48. package/layers/layout/app/utils/gridPlacementStyle.ts +10 -7
  49. package/layers/mailer/server/utils/email.ts +4 -3
  50. package/layers/mailer/server/utils/hooks.ts +1 -1
  51. package/layers/metadata/app/composables/useMetadataItem.ts +6 -2
  52. package/layers/metadata/app/composables/useMetadataSearch.ts +6 -2
  53. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/client.ts +5 -5
  54. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/provider.ts +11 -3
  55. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/client.ts +9 -6
  56. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts +6 -3
  57. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +3 -1
  58. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +4 -2
  59. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -3
  60. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +3 -1
  61. package/layers/metadata/server/api/metadata/status.get.ts +13 -18
  62. package/layers/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
  63. package/layers/metadata/server/utils/metadata/search.test.ts +18 -18
  64. package/layers/metadata/server/utils/metadata/search.ts +16 -8
  65. package/layers/navigation/app/utils/site.test.ts +1 -3
  66. package/layers/scroll/app/components/Motion/PinnedSection.vue +11 -1
  67. package/layers/scroll/app/composables/useSectionProgress.ts +1 -1
  68. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -0
  69. package/layers/scroll/app/utils/scroll.ts +5 -1
  70. package/layers/seo/app/utils/seoConfig.test.ts +1 -1
  71. package/layers/shader/app/assets/css/shader.css +1 -1
  72. package/layers/shader/app/components/Material/AmbientAurora.client.vue +16 -4
  73. package/layers/shader/app/components/Material/AmbientFlow.client.vue +21 -5
  74. package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +21 -5
  75. package/layers/shader/app/components/Material/AmbientNebula.client.vue +21 -5
  76. package/layers/shader/app/components/Material/AmbientOcean.client.vue +16 -4
  77. package/layers/shader/app/components/Material/Gradient.client.vue +1 -1
  78. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  79. package/layers/shader/app/components/Material/Noise.client.vue +11 -3
  80. package/layers/shader/app/composables/useAmbientMaterials.ts +0 -1
  81. package/layers/shader/app/shaders/common/blend.ts +5 -1
  82. package/layers/shader/app/shaders/common/grain.ts +5 -1
  83. package/layers/shader/app/shaders/common/lighting.ts +1 -6
  84. package/layers/shader/app/shaders/common/noise.ts +3 -1
  85. package/layers/shader/app/shaders/common/patterns.ts +1 -1
  86. package/layers/shader/app/shaders/common/shapes.ts +5 -1
  87. package/layers/shader/app/shaders/common/uv.ts +19 -2
  88. package/layers/shader/app/shaders/createMaterial.ts +11 -6
  89. package/layers/shader/app/shaders/layers/shaderGradient.ts +2 -12
  90. package/layers/shader/app/shaders/types.ts +1 -1
  91. package/layers/shader/app/types/uniforms.ts +1 -0
  92. package/layers/shader/app/utils/tsl/animation.ts +34 -36
  93. package/layers/starter/app/components/StarterDesignSystem.vue +120 -120
  94. package/layers/starter/app/components/StarterHome.vue +233 -208
  95. package/layers/theme/app/assets/css/theme.css +1 -1
  96. package/layers/theme/server/utils/accent-css.test.ts +17 -2
  97. package/layers/theme/server/utils/fouc-config.test.ts +7 -3
  98. package/layers/theme/server/utils/fouc-config.ts +24 -12
  99. package/layers/transitions/app/assets/css/transitions.css +6 -0
  100. package/layers/typography/app/assets/css/typography.css +313 -0
  101. package/layers/typography/app/components/Typography/Headline.vue +23 -14
  102. package/layers/typography/app/components/Typography/HeadlineScreen.vue +5 -12
  103. package/layers/typography/app/components/Typography/index.vue +4 -0
  104. package/layers/typography/app/composables/typography.ts +45 -5
  105. package/layers/typography/app/types/typography.ts +36 -7
  106. package/layers/typography/nuxt.config.ts +2 -0
  107. package/layers/visual/app/app.config.ts +1 -1
  108. package/layers/visual/app/assets/css/visual.css +1 -1
  109. package/layers/visual/app/composables/accent.ts +1 -2
  110. package/layers/visual/app/composables/tint.ts +1 -2
  111. package/layers/visual/app/types/media.ts +2 -2
  112. package/layers/visual/app/utils/gradientStyle.test.ts +1 -2
  113. package/layers/visual/app/utils/responsiveSizes.ts +0 -1
  114. package/package.json +36 -16
package/docs/LAYOUT.md CHANGED
@@ -51,7 +51,7 @@ The layout layer requires a dedicated Nuxt layout file and page-level layout dec
51
51
 
52
52
  ```vue
53
53
  <script setup lang="ts">
54
- definePageMeta({ layout: 'grid' })
54
+ definePageMeta({ layout: 'grid' })
55
55
  </script>
56
56
  ```
57
57
 
@@ -65,11 +65,11 @@ definePageMeta({ layout: 'grid' })
65
65
 
66
66
  The grid has three modes controlled via `app.config.ts`:
67
67
 
68
- | Mode | Behaviour |
69
- |---|---|
70
- | `'swiss'` | Default. Full Swiss grid with `mastmain`/`basesection` CSS subgrid. |
71
- | `'fluid'` | Container-query-based auto-fit grid. `basesection` still gets `container-type`. |
72
- | `'disabled'` | `LayoutMain` renders a plain `<main>` without grid CSS. |
68
+ | Mode | Behaviour |
69
+ | ------------ | ------------------------------------------------------------------------------- |
70
+ | `'swiss'` | Default. Full Swiss grid with `mastmain`/`basesection` CSS subgrid. |
71
+ | `'fluid'` | Container-query-based auto-fit grid. `basesection` still gets `container-type`. |
72
+ | `'disabled'` | `LayoutMain` renders a plain `<main>` without grid CSS. |
73
73
 
74
74
  Set in your `app.config.ts`:
75
75
 
@@ -78,7 +78,7 @@ export default defineAppConfig({
78
78
  layoutLayer: {
79
79
  ui: {
80
80
  grid: {
81
- mode: 'swiss', // 'swiss' | 'fluid' | 'disabled'
81
+ mode: 'swiss', // 'swiss' | 'fluid' | 'disabled'
82
82
  },
83
83
  },
84
84
  },
@@ -110,8 +110,8 @@ Renders `<main class="mastmain">`. When `mode === 'disabled'` it renders a plain
110
110
 
111
111
  **Props:**
112
112
 
113
- | Prop | Type | Default |
114
- |---|---|---|
113
+ | Prop | Type | Default |
114
+ | ----- | -------- | -------- |
115
115
  | `tag` | `string` | `'main'` |
116
116
 
117
117
  Place this in your layout file (`layouts/grid.vue`), not in individual pages.
@@ -132,10 +132,10 @@ Constrains content width and centres it. Use inside sections or anywhere you nee
132
132
 
133
133
  **Props:**
134
134
 
135
- | Prop | Type | Default |
136
- |---|---|---|
135
+ | Prop | Type | Default |
136
+ | ------ | ------------------------------------------ | -------- |
137
137
  | `size` | `'content' \| 'wide' \| 'fluid' \| 'full'` | `'wide'` |
138
- | `tag` | `string` | `'div'` |
138
+ | `tag` | `string` | `'div'` |
139
139
 
140
140
  The `full` size uses negative `margin-inline` to escape the grid's `--grid-padding`, reaching the true viewport edge.
141
141
 
@@ -159,11 +159,11 @@ Fragment component (no wrapper element). Handles `useHead()` SEO and optionally
159
159
 
160
160
  **Props:**
161
161
 
162
- | Prop | Type | Default |
163
- |---|---|---|
164
- | `title` | `string` | — (required) |
165
- | `description` | `string` | `''` |
166
- | `showHeader` | `boolean` | `false` |
162
+ | Prop | Type | Default |
163
+ | ------------- | --------- | ------------ |
164
+ | `title` | `string` | — (required) |
165
+ | `description` | `string` | `''` |
166
+ | `showHeader` | `boolean` | `false` |
167
167
 
168
168
  ---
169
169
 
@@ -179,11 +179,11 @@ Full-viewport subgrid section. Spans 12 rows (= 100vh). Inherits the parent `.ma
179
179
 
180
180
  **Props:**
181
181
 
182
- | Prop | Type | Default |
183
- |---|---|---|
184
- | `fullHeight` | `boolean` | `false` |
185
- | `fullWidth` | `boolean` | `false` |
186
- | `tag` | `string` | `'section'` |
182
+ | Prop | Type | Default |
183
+ | ------------ | --------- | ----------- |
184
+ | `fullHeight` | `boolean` | `false` |
185
+ | `fullWidth` | `boolean` | `false` |
186
+ | `tag` | `string` | `'section'` |
187
187
 
188
188
  ---
189
189
 
@@ -200,37 +200,37 @@ Positioned child within a subgrid section. Use `preset` for common layouts or se
200
200
 
201
201
  **Props:**
202
202
 
203
- | Prop | Type | Notes |
204
- |---|---|---|
205
- | `preset` | `string` | Named preset from config |
206
- | `colStart` | `number \| ResponsiveValue` | Grid column start |
207
- | `colSpan` | `number \| ResponsiveValue` | Column span |
208
- | `rowStart` | `number \| ResponsiveValue` | Grid row start |
209
- | `rowSpan` | `number \| ResponsiveValue` | Row span |
210
- | `layer` | `keyof GridLayers` | Z-index layer: `back`, `mid`, `front`, `top` |
211
- | `align` | `string` | `align-self` value: `start`, `center`, `end`, `stretch` |
212
- | `justify` | `string` | `justify-self` value: `start`, `center`, `end`, `stretch` |
213
- | `container` | `string` | Content width constraint: `content` (65ch), `wide` (90rem), `fluid`, `full` |
214
- | `gap` | `string` | Override `--grid-gap` for nested content (e.g. `'1rem'`) |
215
- | `density` | `string` | Rhythm base unit: `compact` (2px), `normal` (4px), `relaxed` (8px) |
216
- | `bleed` | `boolean` | Negative margin to reach viewport edge |
217
- | `as` | `string` | Tag override |
203
+ | Prop | Type | Notes |
204
+ | ----------- | --------------------------- | --------------------------------------------------------------------------- |
205
+ | `preset` | `string` | Named preset from config |
206
+ | `colStart` | `number \| ResponsiveValue` | Grid column start |
207
+ | `colSpan` | `number \| ResponsiveValue` | Column span |
208
+ | `rowStart` | `number \| ResponsiveValue` | Grid row start |
209
+ | `rowSpan` | `number \| ResponsiveValue` | Row span |
210
+ | `layer` | `keyof GridLayers` | Z-index layer: `back`, `mid`, `front`, `top` |
211
+ | `align` | `string` | `align-self` value: `start`, `center`, `end`, `stretch` |
212
+ | `justify` | `string` | `justify-self` value: `start`, `center`, `end`, `stretch` |
213
+ | `container` | `string` | Content width constraint: `content` (65ch), `wide` (90rem), `fluid`, `full` |
214
+ | `gap` | `string` | Override `--grid-gap` for nested content (e.g. `'1rem'`) |
215
+ | `density` | `string` | Rhythm base unit: `compact` (2px), `normal` (4px), `relaxed` (8px) |
216
+ | `bleed` | `boolean` | Negative margin to reach viewport edge |
217
+ | `as` | `string` | Tag override |
218
218
 
219
219
  **Built-in presets:**
220
220
 
221
- | Preset | Columns | Rows |
222
- |---|---|---|
223
- | `hero` | full width | full height |
224
- | `centered` | centre 10 cols | rows 2–10 |
225
- | `fullWidth` | full width | auto |
226
- | `sidebar` | cols 1–4 | full height |
227
- | `content` | cols 5–14 | rows 2–10 |
228
- | `splitLeft` | left half | full |
229
- | `splitRight` | right half | full |
230
- | `quarterLeft` | first quarter | full |
231
- | `threeQuarterRight` | right 3/4 | full |
232
- | `halfTop` | full width | top half |
233
- | `halfBottom` | full width | bottom half |
221
+ | Preset | Columns | Rows |
222
+ | ------------------- | -------------- | ----------- |
223
+ | `hero` | full width | full height |
224
+ | `centered` | centre 10 cols | rows 2–10 |
225
+ | `fullWidth` | full width | auto |
226
+ | `sidebar` | cols 1–4 | full height |
227
+ | `content` | cols 5–14 | rows 2–10 |
228
+ | `splitLeft` | left half | full |
229
+ | `splitRight` | right half | full |
230
+ | `quarterLeft` | first quarter | full |
231
+ | `threeQuarterRight` | right 3/4 | full |
232
+ | `halfTop` | full width | top half |
233
+ | `halfBottom` | full width | bottom half |
234
234
 
235
235
  ---
236
236
 
@@ -258,7 +258,8 @@ Two-column 50/50 layout.
258
258
  <template #right>Right content</template>
259
259
  </LayoutSectionSplit>
260
260
 
261
- <LayoutSectionSplit reverse /> <!-- right renders first visually -->
261
+ <LayoutSectionSplit reverse />
262
+ <!-- right renders first visually -->
262
263
  ```
263
264
 
264
265
  ---
@@ -293,20 +294,20 @@ Column overlay toggled with `Cmd+G`. Place once in your grid layout file, not in
293
294
  const { config, getPreset, isEnabled, mode, layers, useZIndex } = useGridConfig()
294
295
  ```
295
296
 
296
- | Return | Type | Description |
297
- |---|---|---|
298
- | `config` | `Ref<GridConfig>` | Raw config from `app.config` |
299
- | `isEnabled` | `ComputedRef<boolean>` | `true` when `mode !== 'disabled'` |
300
- | `mode` | `ComputedRef<GridMode>` | `'swiss' \| 'fluid' \| 'disabled'` |
301
- | `layers` | `ComputedRef<GridLayers>` | All z-index values |
302
- | `getPreset(name)` | `GridPresetsItem \| undefined` | Look up a preset by name |
303
- | `useZIndex(layer)` | `number` | Get a z-index value by layer name |
297
+ | Return | Type | Description |
298
+ | ------------------ | ------------------------------ | ---------------------------------- |
299
+ | `config` | `Ref<GridConfig>` | Raw config from `app.config` |
300
+ | `isEnabled` | `ComputedRef<boolean>` | `true` when `mode !== 'disabled'` |
301
+ | `mode` | `ComputedRef<GridMode>` | `'swiss' \| 'fluid' \| 'disabled'` |
302
+ | `layers` | `ComputedRef<GridLayers>` | All z-index values |
303
+ | `getPreset(name)` | `GridPresetsItem \| undefined` | Look up a preset by name |
304
+ | `useZIndex(layer)` | `number` | Get a z-index value by layer name |
304
305
 
305
306
  **`useZIndex` example:**
306
307
 
307
308
  ```ts
308
309
  const { useZIndex } = useGridConfig()
309
- const zModal = useZIndex('modal') // 400
310
+ const zModal = useZIndex('modal') // 400
310
311
  const zHeader = useZIndex('header') // 100
311
312
  ```
312
313
 
@@ -331,13 +332,13 @@ All grid CSS is in `layers/layout/app/assets/css/layout/`.
331
332
 
332
333
  CSS custom properties exposed:
333
334
 
334
- | Variable | Value | Purpose |
335
- |---|---|---|
336
- | `--grid-cols` | 6 / 12 / 18 | Active column count |
337
- | `--grid-gap` | `clamp(0.75rem, 1.5vw, 1.5rem)` | Gap between columns and rows |
338
- | `--grid-padding` | `clamp(1rem, 2.5vw, 2rem)` | Outer left/right gutters |
339
- | `--section-height` | `100vh` | Height of one section |
340
- | `--grid-row-height` | computed | Height of one grid row |
335
+ | Variable | Value | Purpose |
336
+ | ------------------- | ------------------------------- | ---------------------------- |
337
+ | `--grid-cols` | 6 / 12 / 18 | Active column count |
338
+ | `--grid-gap` | `clamp(0.75rem, 1.5vw, 1.5rem)` | Gap between columns and rows |
339
+ | `--grid-padding` | `clamp(1rem, 2.5vw, 2rem)` | Outer left/right gutters |
340
+ | `--section-height` | `100vh` | Height of one section |
341
+ | `--grid-row-height` | computed | Height of one grid row |
341
342
 
342
343
  **`.basesection`** — Subgrid section (12 rows = 100vh).
343
344
 
@@ -362,12 +363,12 @@ Tune minimum column width per-instance:
362
363
 
363
364
  **Container size classes** (used by `LayoutContainer`):
364
365
 
365
- | Class | Effect |
366
- |---|---|
367
- | `.layout-container-content` | `max-width: 65ch`, centred |
368
- | `.layout-container-wide` | `max-width: 90rem`, centred |
369
- | `.layout-container-fluid` | `max-width: 100%` |
370
- | `.layout-container-full` | `width: 100vw`, escapes grid padding |
366
+ | Class | Effect |
367
+ | --------------------------- | ------------------------------------ |
368
+ | `.layout-container-content` | `max-width: 65ch`, centred |
369
+ | `.layout-container-wide` | `max-width: 90rem`, centred |
370
+ | `.layout-container-fluid` | `max-width: 100%` |
371
+ | `.layout-container-full` | `width: 100vw`, escapes grid padding |
371
372
 
372
373
  ---
373
374
 
@@ -375,17 +376,17 @@ Tune minimum column width per-instance:
375
376
 
376
377
  The grid manages z-index through named layers. Use `useZIndex()` or the `layer` prop on `LayoutGridItem` — never raw numbers.
377
378
 
378
- | Layer | Value | Use |
379
- |---|---|---|
380
- | `back` | 0 | Background elements |
381
- | `mid` | 10 | Content |
382
- | `front` | 20 | Floating content |
383
- | `top` | 30 | Pinned / sticky |
384
- | `header` | 100 | Site header / nav |
385
- | `dropdown` | 200 | Dropdown menus, popups |
386
- | `overlay` | 300 | Drawers, sidebars |
387
- | `modal` | 400 | Modal dialogs |
388
- | `toast` | 500 | Toasts, notifications |
379
+ | Layer | Value | Use |
380
+ | ---------- | ----- | ---------------------- |
381
+ | `back` | 0 | Background elements |
382
+ | `mid` | 10 | Content |
383
+ | `front` | 20 | Floating content |
384
+ | `top` | 30 | Pinned / sticky |
385
+ | `header` | 100 | Site header / nav |
386
+ | `dropdown` | 200 | Dropdown menus, popups |
387
+ | `overlay` | 300 | Drawers, sidebars |
388
+ | `modal` | 400 | Modal dialogs |
389
+ | `toast` | 500 | Toasts, notifications |
389
390
 
390
391
  ```ts
391
392
  // In a component
@@ -495,10 +496,14 @@ The CSS overflow interaction rule: setting `overflow-x: hidden` on any element f
495
496
 
496
497
  ```css
497
498
  /* Wrong */
498
- .mastmain { overflow-x: hidden; } /* becomes a scroll container */
499
+ .mastmain {
500
+ overflow-x: hidden;
501
+ } /* becomes a scroll container */
499
502
 
500
503
  /* Correct */
501
- .mastmain { overflow-x: clip; } /* clips without scroll container */
504
+ .mastmain {
505
+ overflow-x: clip;
506
+ } /* clips without scroll container */
502
507
  ```
503
508
 
504
509
  ### Locomotive Scroll intercepts wheel events globally
package/docs/MIGRATION.md CHANGED
@@ -25,7 +25,11 @@ Site metadata is no longer namespaced under `feedsLayer.site`. It now lives at t
25
25
  // app/app.config.ts — BEFORE
26
26
  export default defineAppConfig({
27
27
  feedsLayer: {
28
- site: { title: 'My Site', url: 'https://example.com', author: { name: 'Jane', email: 'jane@example.com' } },
28
+ site: {
29
+ title: 'My Site',
30
+ url: 'https://example.com',
31
+ author: { name: 'Jane', email: 'jane@example.com' },
32
+ },
29
33
  },
30
34
  })
31
35
 
@@ -34,7 +38,7 @@ export default defineAppConfig({
34
38
  site: {
35
39
  title: 'My Site',
36
40
  description: 'Short description used in feed channels and meta tags',
37
- url: 'https://example.com', // canonical origin, NO trailing slash
41
+ url: 'https://example.com', // canonical origin, NO trailing slash
38
42
  author: { name: 'Jane', email: 'jane@example.com' },
39
43
  },
40
44
  })
@@ -52,11 +56,11 @@ export default defineAppConfig({
52
56
 
53
57
  ```ts
54
58
  // BEFORE
55
- import type { SiteAuthor } from '#layers/core/app/types/site'
59
+ // AFTER
60
+ import type { Author, SiteAuthor } from '#layers/core/app/types/site'
61
+
56
62
  const author: SiteAuthor = { name: 'Jane' }
57
63
 
58
- // AFTER
59
- import type { Author } from '#layers/core/app/types/site'
60
64
  const author: Author = { name: 'Jane' }
61
65
  ```
62
66
 
@@ -72,9 +76,13 @@ The author `url` field is renamed to `link` to match core's `Author`. This touch
72
76
 
73
77
  ```ts
74
78
  // BEFORE
75
- authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() }))
79
+ authors: z.array(
80
+ z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() })
81
+ )
76
82
  // AFTER
77
- authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() }))
83
+ authors: z.array(
84
+ z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() })
85
+ )
78
86
  ```
79
87
 
80
88
  **3b. Markdown frontmatter** (every content file with authors):
@@ -98,7 +106,10 @@ authors: authors:
98
106
 
99
107
  ```ts
100
108
  import type { BaseContent } from '#layers/content/app/types/content'
101
- interface MyCustomPage extends BaseContent { featuredVideo?: string }
109
+
110
+ interface MyCustomPage extends BaseContent {
111
+ featuredVideo?: string
112
+ }
102
113
  ```
103
114
 
104
115
  ## 5. Next: add feeds (feature, not breaking)
@@ -114,13 +125,13 @@ Once the breaking changes are applied and `site` is set, wire RSS/Atom/JSON feed
114
125
 
115
126
  ## Quick reference
116
127
 
117
- | Change | Type | Action |
118
- |--------|------|--------|
128
+ | Change | Type | Action |
129
+ | -------------------------- | -------- | ----------------------------------------------- |
119
130
  | `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) |
131
+ | `SiteAuthor` → `Author` | Breaking | Rename imports/annotations |
132
+ | Author `url` → `link` | Breaking | Update Zod schema + frontmatter + TS (3 places) |
133
+ | `BaseContent` | Additive | Extend it in custom content types |
134
+ | Add feeds | Feature | Follow [`FEEDS.md`](./FEEDS.md) |
124
135
 
125
136
  The sections below are the detailed, chronological history these summaries were distilled from.
126
137
 
@@ -137,6 +148,7 @@ Four related changes landed together. None are strictly breaking for existing pa
137
148
  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
149
 
139
150
  **Before:**
151
+
140
152
  ```ts
141
153
  // app/app.config.ts
142
154
  export default defineAppConfig({
@@ -151,6 +163,7 @@ export default defineAppConfig({
151
163
  ```
152
164
 
153
165
  **After:**
166
+
154
167
  ```ts
155
168
  // app/app.config.ts
156
169
  export default defineAppConfig({
@@ -171,6 +184,7 @@ The `site` key is typed via `SiteConfig` from the core layer — `useAppConfig()
171
184
  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
185
 
173
186
  **Before:**
187
+
174
188
  ```ts
175
189
  import type { SiteAuthor } from '#layers/core/app/types/site'
176
190
 
@@ -178,6 +192,7 @@ const author: SiteAuthor = { name: 'Jane' }
178
192
  ```
179
193
 
180
194
  **After:**
195
+
181
196
  ```ts
182
197
  import type { Author } from '#layers/core/app/types/site'
183
198
 
@@ -193,15 +208,21 @@ const author: Author = { name: 'Jane' }
193
208
  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
209
 
195
210
  **Zod schema (in your `content.config.ts`):**
211
+
196
212
  ```ts
197
213
  // Before
198
- authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() }))
214
+ authors: z.array(
215
+ z.object({ name: z.string(), avatar: z.string().optional(), url: z.string().optional() })
216
+ )
199
217
 
200
218
  // After
201
- authors: z.array(z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() }))
219
+ authors: z.array(
220
+ z.object({ name: z.string(), avatar: z.string().optional(), link: z.string().optional() })
221
+ )
202
222
  ```
203
223
 
204
224
  **Markdown frontmatter:**
225
+
205
226
  ```yaml
206
227
  # Before
207
228
  authors:
@@ -215,6 +236,7 @@ authors:
215
236
  ```
216
237
 
217
238
  **TypeScript interface** — `ContentAuthor` now extends `Author` instead of being a standalone type:
239
+
218
240
  ```ts
219
241
  import type { Author } from '#layers/core/app/types/site'
220
242
 
@@ -274,20 +296,23 @@ export default defineAppConfig({
274
296
 
275
297
  Available feed URLs for each collection:
276
298
 
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 |
299
+ | URL | Format |
300
+ | ------------------------ | ---------------------------------- |
301
+ | `/feed/:collection/rss` | RSS 2.0 |
302
+ | `/feed/:collection/atom` | Atom 1.0 |
303
+ | `/feed/:collection/json` | JSON Feed 1.1 |
304
+ | `/feed/rss` | RSS for `defaultCollection` |
305
+ | `/feed/atom` | Atom for `defaultCollection` |
306
+ | `/feed/json` | JSON Feed for `defaultCollection` |
307
+ | `/feed` | JSON index of all configured feeds |
286
308
 
287
309
  To expose portfolio items as a feed:
310
+
288
311
  ```ts
289
312
  feedsLayer: {
290
- feed: { collections: ['blog', 'portfolio'] }
313
+ feed: {
314
+ collections: ['blog', 'portfolio']
315
+ }
291
316
  }
292
317
  ```
293
318
 
@@ -318,11 +343,11 @@ The feeds layer automatically injects `<link rel="alternate">` tags so RSS reade
318
343
 
319
344
  **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
345
 
321
- | Page | Links injected |
322
- |------|---------------|
323
- | `/`, `/about`, any non-collection page | 3 main site feeds |
346
+ | Page | Links injected |
347
+ | -------------------------------------------------------------- | ----------------------------------------------- |
348
+ | `/`, `/about`, any non-collection page | 3 main site feeds |
324
349
  | `/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 |
350
+ | `/portfolio`, `/portfolio/project` (non-default collection) | 3 main site feeds + 3 portfolio feeds |
326
351
 
327
352
  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
353
 
@@ -332,14 +357,14 @@ No action required. The collection a page belongs to is inferred from the first
332
357
 
333
358
  ## Quick Reference
334
359
 
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 |
360
+ | Change | Action required |
361
+ | --------------------------- | ------------------------------------------------------------ |
362
+ | Site metadata | Move `feedsLayer.site` → top-level `site` in `app.config.ts` |
363
+ | `SiteAuthor` type import | Rename to `Author` |
364
+ | Author `url` in frontmatter | Rename `url:` → `link:` in blog author blocks |
365
+ | Author `url` in Zod schema | Rename `url` → `link` in `authors` object schema |
366
+ | Collection feeds | Set `feedsLayer.feed.collections` in `app.config.ts` |
367
+ | Feed autodiscovery | Nothing — automatic via the feeds layer plugin |
343
368
 
344
369
  ---
345
370
 
@@ -385,14 +410,14 @@ Each factory defaults to the conventional source glob if you omit the argument.
385
410
  In your app's `content.config.ts`, import the factory and register it under any name you like:
386
411
 
387
412
  ```ts
388
- import { createPortfolioCollection, createBlogCollection } from '#layers/content/content.config'
413
+ import { createBlogCollection, createPortfolioCollection } from '#layers/content/content.config'
389
414
 
390
415
  export default defineContentConfig({
391
416
  collections: {
392
417
  blog: createBlogCollection(),
393
418
  works: createPortfolioCollection('works/**/*.md'),
394
419
  // gallery omitted — not needed
395
- }
420
+ },
396
421
  })
397
422
  ```
398
423
 
@@ -442,8 +467,8 @@ export default defineAppConfig({
442
467
  contentLayer: {
443
468
  sections: {
444
469
  blog: true,
445
- portfolio: false, // /portfolio and /portfolio/[slug] → 404
446
- gallery: false, // /gallery and all nested routes → 404
470
+ portfolio: false, // /portfolio and /portfolio/[slug] → 404
471
+ gallery: false, // /gallery and all nested routes → 404
447
472
  },
448
473
  },
449
474
  })
@@ -457,12 +482,12 @@ All three flags default to `true` in the layer, so you only need to declare the
457
482
 
458
483
  ## Quick Reference
459
484
 
460
- | Goal | How |
461
- |---|---|
485
+ | Goal | How |
486
+ | --------------------------------- | -------------------------------------------------------------------------- |
462
487
  | 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` |
488
+ | Query custom-named collection | `createPortfolioComposables('works')` → `useItems` / `useItem` |
489
+ | Disable the gallery section | `contentLayer: { sections: { gallery: false } }` in `app.config.ts` |
490
+ | Disable multiple sections | List each with `false` in `sections` |
466
491
 
467
492
  ---
468
493
 
@@ -479,12 +504,14 @@ v1.6.0 restructures the layout and content systems. The main theme is **opt-in c
479
504
  Header and footer are no longer rendered by default. Pages that relied on `layout: 'grid'` to get them must now pass props.
480
505
 
481
506
  **Before (v1.5.x):**
507
+
482
508
  ```ts
483
509
  definePageMeta({ layout: 'grid' })
484
510
  // header + footer always rendered
485
511
  ```
486
512
 
487
513
  **After (v1.6.0):**
514
+
488
515
  ```ts
489
516
  // Opt-in to header and footer:
490
517
  definePageMeta({
@@ -500,11 +527,11 @@ definePageMeta({ layout: false })
500
527
 
501
528
  Available props on `grid.vue` (all `boolean`, default `false`):
502
529
 
503
- | Prop | Description |
504
- |---|---|
505
- | `showHeader` | Renders `<MastHeader>` |
506
- | `showNav` | Renders `<MastNav>` |
507
- | `showFooter` | Renders `<MastFooter>` |
530
+ | Prop | Description |
531
+ | --------------- | ----------------------------------- |
532
+ | `showHeader` | Renders `<MastHeader>` |
533
+ | `showNav` | Renders `<MastNav>` |
534
+ | `showFooter` | Renders `<MastFooter>` |
508
535
  | `showGridDebug` | Renders `<LayoutGridDebug>` overlay |
509
536
 
510
537
  ---
@@ -514,12 +541,14 @@ Available props on `grid.vue` (all `boolean`, default `false`):
514
541
  The global Locomotive Scroll wrapper in `default.vue` has been removed. Smooth scroll is now per-page via the `useSmoothScroll()` composable.
515
542
 
516
543
  **Before (v1.5.x):**
544
+
517
545
  ```vue
518
546
  <!-- default.vue automatically wrapped everything in MastScroller -->
519
547
  <!-- No per-page setup required -->
520
548
  ```
521
549
 
522
550
  **After (v1.6.0):**
551
+
523
552
  ```ts
524
553
  // In any page that needs smooth scroll:
525
554
  const { scrollTo, scrollToTop, velocity, progress } = useSmoothScroll()
@@ -534,6 +563,7 @@ Pages that don't call `useSmoothScroll()` will use native browser scroll — no
534
563
  `LayoutPageContainer` has been superseded by `LayoutPage`. The legacy component still exists for backwards compatibility but is no longer maintained.
535
564
 
536
565
  **Before (v1.5.x):**
566
+
537
567
  ```vue
538
568
  <LayoutPageContainer title="My Page" :show-header="false">
539
569
  <LayoutSection>...</LayoutSection>
@@ -541,6 +571,7 @@ Pages that don't call `useSmoothScroll()` will use native browser scroll — no
541
571
  ```
542
572
 
543
573
  **After (v1.6.0):**
574
+
544
575
  ```vue
545
576
  <LayoutPage title="My Page">
546
577
  <LayoutSection>...</LayoutSection>
@@ -581,11 +612,13 @@ definePageMeta({ ssr: false, layout: false })
581
612
  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
613
 
583
614
  **Before (v1.5.x):**
615
+
584
616
  ```ts
585
617
  return useAsyncData('my-key', () => queryCollection('blog').all())
586
618
  ```
587
619
 
588
620
  **After (v1.6.0):**
621
+
589
622
  ```ts
590
623
  return useContentData('my-key', () => queryCollection('blog').all())
591
624
  ```
@@ -617,11 +650,11 @@ export default defineNuxtConfig({
617
650
 
618
651
  ## Quick Reference
619
652
 
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(...)` |
653
+ | Pattern | v1.5.x | v1.6.0 |
654
+ | ------------------------------ | ------------------------------- | ------------------------------------------------------------------------- |
655
+ | Grid page with header + footer | `layout: 'grid'` | `layout: { name: 'grid', props: { showHeader: true, showFooter: true } }` |
656
+ | Grid page, no chrome | `layout: 'grid'` | `layout: 'grid'` |
657
+ | Full-bleed / animation page | `layout: 'grid'` (broken) | `layout: false` |
658
+ | Page wrapper component | `<LayoutPageContainer>` | `<LayoutPage>` |
659
+ | Global smooth scroll | `MastScroller` in layout (auto) | `useSmoothScroll()` per-page |
660
+ | Content async data | `useAsyncData(...)` | `useContentData(...)` |