kmcom-nuxt-layers 6.3.0 → 6.5.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 (142) hide show
  1. package/docs/MIGRATION-v6.4.0.md +215 -0
  2. package/docs/MIGRATION-v6.5.0.md +87 -0
  3. package/docs/MIGRATION.md +3 -1
  4. package/docs/USAGE.md +35 -10
  5. package/docs/adr/0003-modular-step-system-replaces-fluid-clamps.md +16 -0
  6. package/docs/component-levels.md +1 -0
  7. package/docs/layout-shift-causes.md +18 -0
  8. package/layers/content/app/components/content/Figure.vue +1 -1
  9. package/layers/content/app/composables/useContentSection.ts +1 -1
  10. package/layers/core/app/app.config.ts +9 -5
  11. package/layers/core/app/app.vue +2 -1
  12. package/layers/core/app/components/LoadingScreen.vue +35 -21
  13. package/layers/core/app/composables/useCache.ts +2 -2
  14. package/layers/core/app/composables/useErrorLog.ts +5 -5
  15. package/layers/core/app/composables/useLoading.ts +19 -10
  16. package/layers/core/app/plugins/loading.client.ts +11 -3
  17. package/layers/core/app/types/loading.ts +10 -3
  18. package/layers/core/app/utils/loading.ts +7 -16
  19. package/layers/core/nuxt.config.ts +14 -14
  20. package/layers/core/server/utils/app-config.ts +17 -0
  21. package/layers/data/auth/app/middleware/auth.ts +1 -1
  22. package/layers/data/forms/app/components/Form/Contact.vue +1 -1
  23. package/layers/data/forms/server/api/contact.post.ts +16 -8
  24. package/layers/data/forms/server/api/forms/status.get.ts +3 -0
  25. package/layers/data/forms/{app → shared}/utils/contact-schema.ts +2 -1
  26. package/layers/data/metadata/server/api/metadata/search.get.ts +27 -7
  27. package/layers/data/metadata/server/api/metadata/status.get.ts +4 -1
  28. package/layers/data/metadata/server/api/metadata/sync.post.ts +13 -3
  29. package/layers/data/metadata/server/utils/metadata/resolve-record.ts +3 -3
  30. package/layers/delivery/feeds/nuxt.config.ts +6 -1
  31. package/layers/delivery/feeds/package.json +2 -2
  32. package/layers/delivery/feeds/server/routes/feed/demo.get.ts +2 -2
  33. package/layers/delivery/feeds/server/utils/content-adapter.ts +1 -1
  34. package/layers/delivery/feeds/server/utils/feed-service.ts +2 -2
  35. package/layers/design-system/theming/app/plugins/theme.client.ts +7 -0
  36. package/layers/design-system/theming/nuxt.config.ts +0 -2
  37. package/layers/design-system/typography/app/assets/css/typography.css +41 -5
  38. package/layers/design-system/visual/app/components/HoverDimGroup.vue +11 -3
  39. package/layers/motion/scroll/app/plugins/locomotive-scroll.client.ts +3 -2
  40. package/layers/render/canvas/nuxt.config.ts +0 -4
  41. package/layers/render/shader/nuxt.config.ts +0 -4
  42. package/layers/structure/navigation/app/app.config.ts +37 -14
  43. package/layers/structure/navigation/app/components/Mast/Nav.vue +14 -13
  44. package/layers/structure/navigation/app/components/Mast/NavModal.vue +83 -28
  45. package/layers/structure/navigation/app/components/Site/Logo.vue +35 -0
  46. package/layers/structure/navigation/app/components/Site/Title.vue +3 -3
  47. package/layers/structure/navigation/app/composables/mastNav.ts +38 -13
  48. package/layers/structure/navigation/app/types/nav.ts +34 -0
  49. package/layers/structure/routing/app/middleware/01.maintenance.global.ts +4 -1
  50. package/layers/structure/routing/app/middleware/02.governance.global.ts +1 -1
  51. package/layers/structure/routing/nuxt.config.ts +19 -12
  52. package/layers/structure/routing/server/api/feature-flags.get.ts +4 -2
  53. package/package.json +35 -5
  54. package/docs/FALLOW-COMPLEXITY-DUPLICATION-AUDIT.md +0 -64
  55. package/docs/IMPROVE-AUDIT-README.md +0 -29
  56. package/docs/IMPROVE-AUDIT-RESULTS.md +0 -51
  57. package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +0 -81
  58. package/docs/fallow-refactor/apps-debug.md +0 -27
  59. package/docs/fallow-refactor/apps-playground.md +0 -46
  60. package/docs/fallow-refactor/apps-visual-identity.md +0 -41
  61. package/docs/fallow-refactor/layers-animations.md +0 -34
  62. package/docs/fallow-refactor/layers-canvas.md +0 -32
  63. package/docs/fallow-refactor/layers-content.md +0 -33
  64. package/docs/fallow-refactor/layers-core.md +0 -39
  65. package/docs/fallow-refactor/layers-feeds.md +0 -39
  66. package/docs/fallow-refactor/layers-forms.md +0 -30
  67. package/docs/fallow-refactor/layers-layout.md +0 -42
  68. package/docs/fallow-refactor/layers-mailer.md +0 -32
  69. package/docs/fallow-refactor/layers-motion.md +0 -27
  70. package/docs/fallow-refactor/layers-navigation.md +0 -31
  71. package/docs/fallow-refactor/layers-page-transitions.md +0 -30
  72. package/docs/fallow-refactor/layers-routing.md +0 -33
  73. package/docs/fallow-refactor/layers-scripts.md +0 -35
  74. package/docs/fallow-refactor/layers-scroll.md +0 -38
  75. package/docs/fallow-refactor/layers-seo.md +0 -32
  76. package/docs/fallow-refactor/layers-shader.md +0 -53
  77. package/docs/fallow-refactor/layers-theme.md +0 -33
  78. package/docs/fallow-refactor/layers-transitions.md +0 -27
  79. package/docs/fallow-refactor/layers-typography.md +0 -29
  80. package/docs/fallow-refactor/layers-visual.md +0 -34
  81. package/docs/superpowers/plans/2026-09-03-layout-layer-fluid-rebuild.md +0 -1850
  82. package/docs/superpowers/plans/2026-09-03-vue-element-system.md +0 -1019
  83. package/docs/superpowers/plans/2026-09-04-grid-layer.md +0 -977
  84. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-0-1-design.md +0 -533
  85. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-2-design.md +0 -154
  86. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-3-design.md +0 -109
  87. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-4-design.md +0 -124
  88. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-5-design.md +0 -121
  89. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-6-design.md +0 -77
  90. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-7-design.md +0 -73
  91. package/docs/superpowers/specs/2026-08-31-swiss-grid-layer-design.md +0 -190
  92. package/docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md +0 -312
  93. package/docs/superpowers/specs/2026-09-03-vue-element-system-design.md +0 -342
  94. package/docs/superpowers/specs/2026-09-04-grid-layer-design.md +0 -236
  95. package/layers/content/app/utils/galleryImageMeta.test.ts +0 -30
  96. package/layers/core/app/components/Element.test.ts +0 -90
  97. package/layers/core/app/components/ElementTw.test.ts +0 -64
  98. package/layers/core/app/composables/useLayoutAttrs.test.ts +0 -77
  99. package/layers/core/app/utils/browserInfo.test.ts +0 -81
  100. package/layers/core/app/utils/featureClasses.test.ts +0 -101
  101. package/layers/core/app/utils/loading.test.ts +0 -48
  102. package/layers/core/app/utils/resolveElementStyle.test.ts +0 -99
  103. package/layers/core/app/utils/scrollGuardClamp.test.ts +0 -36
  104. package/layers/core/app/utils/styleToTailwindClasses.test.ts +0 -31
  105. package/layers/data/forms/app/utils/contact-schema.test.ts +0 -75
  106. package/layers/data/forms/app/utils/fieldProps.test.ts +0 -141
  107. package/layers/data/metadata/providers/comicvine/server/utils/metadata-comicvine/normalise.test.ts +0 -201
  108. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/client.test.ts +0 -76
  109. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/normalise.test.ts +0 -144
  110. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/client.test.ts +0 -25
  111. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +0 -144
  112. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +0 -29
  113. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/normalise.test.ts +0 -162
  114. package/layers/data/metadata/server/utils/metadata/cache.test.ts +0 -39
  115. package/layers/data/metadata/server/utils/metadata/provider-fetch.test.ts +0 -27
  116. package/layers/data/metadata/server/utils/metadata/provider-registry.test.ts +0 -54
  117. package/layers/data/metadata/server/utils/metadata/resolve-record.test.ts +0 -87
  118. package/layers/data/metadata/server/utils/metadata/search.test.ts +0 -204
  119. package/layers/delivery/feeds/server/utils/content-adapter.test.ts +0 -67
  120. package/layers/delivery/feeds/server/utils/feed-author.test.ts +0 -71
  121. package/layers/delivery/feeds/server/utils/feed-config.test.ts +0 -184
  122. package/layers/delivery/feeds/server/utils/formats.test.ts +0 -71
  123. package/layers/delivery/seo/app/utils/seoConfig.test.ts +0 -58
  124. package/layers/design-system/theming/server/utils/accent-css.test.ts +0 -71
  125. package/layers/design-system/theming/server/utils/fouc-config.test.ts +0 -78
  126. package/layers/design-system/typography/app/components/Typography/Headline.test.ts +0 -36
  127. package/layers/design-system/typography/app/components/Typography/index.test.ts +0 -31
  128. package/layers/design-system/typography/app/composables/color.test.ts +0 -30
  129. package/layers/design-system/typography/app/composables/typography.test.ts +0 -53
  130. package/layers/design-system/visual/app/composables/tint.test.ts +0 -22
  131. package/layers/design-system/visual/app/utils/colorTokens.test.ts +0 -51
  132. package/layers/design-system/visual/app/utils/gradientStyle.test.ts +0 -75
  133. package/layers/design-system/visual/app/utils/responsiveSizes.test.ts +0 -71
  134. package/layers/motion/animations/app/components/Motion/Staggered.test.ts +0 -40
  135. package/layers/motion/scroll/app/composables/useReducedMotion.test.ts +0 -23
  136. package/layers/motion/scroll/app/utils/parallax.test.ts +0 -24
  137. package/layers/structure/grid/app/composables/useGridTracks.test.ts +0 -94
  138. package/layers/structure/grid/app/types/tracks.test.ts +0 -32
  139. package/layers/structure/grid/app/utils/placement.test.ts +0 -19
  140. package/layers/structure/layout/app/utils/gridPlacementStyle.test.ts +0 -269
  141. package/layers/structure/navigation/app/utils/site.test.ts +0 -39
  142. package/layers/structure/routing/app/utils/resolveRoute.test.ts +0 -47
@@ -1,1019 +0,0 @@
1
- # Vue Element System Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Add a polymorphic `Element` component to `layers/structure/layout` giving Vue templates a small, typed vocabulary (layout/grid/spacing/sizing/surface/interaction) instead of utility-class strings, reusing the layer's existing fluid-token and grid-placement engines.
6
-
7
- **Architecture:** Six small composables, each resolving one property group to a `Partial<CSSProperties>`, merged inside `Element.vue`'s single computed `style`. Grid item-placement (`colStart`/`colSpan`/`rowStart`/`rowSpan`) delegates directly to the existing `buildGridPlacementStyle` from `gridPlacementStyle.ts` — no new placement math. Spacing (`p`/`px`/`py`/`gap`/`align`/`justify`) delegates to core's `useLayoutAttrs`, extended with `m`/`mx`/`my`. Surface/Sizing/Interaction are new, minimal, raw-CSS-passthrough composables.
8
-
9
- **Tech Stack:** Vue 3 `<script setup>`, TypeScript, Vitest, Nuxt 4 layers (`#layers/*` aliases).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-09-03-vue-element-system-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - No changes to `LayoutGridItem`, `gridPlacementStyle.ts` placement logic, or any fluid-rebuild-spec territory — read-only reuse.
16
- - No semantic colour/token system — `bg`/`color`/`border`/`radius`/`shadow` are raw CSS passthrough (`CSSProperties[...]` typed), not token enums.
17
- - Grid item-placement prop names match `LayoutGridItem`'s existing vocabulary: `colStart`/`colSpan`/`rowStart`/`rowSpan` — not the source doc's `span`/`start`/`end`.
18
- - No semicolons, single quotes, ES5 trailing commas, max 100 char lines (`.claude/rules/typescript.md`).
19
- - `<script setup lang="ts">`, script → template → style block order (`.claude/rules/vue-components.md`).
20
- - `Element` is root-level, unprefixed component name, matching `HStack`/`VStack`/`ZStack`/`Spacer`.
21
-
22
- ---
23
-
24
- ## File Structure
25
-
26
- ```
27
- layers/core/app/composables/useLayoutAttrs.ts (modify: add m/mx/my)
28
- layers/core/app/composables/useLayoutAttrs.test.ts (modify: add margin tests, only if file exists — else create)
29
-
30
- layers/structure/layout/app/composables/
31
- ├── useElementLayout.ts (new: display mode resolver)
32
- ├── useElementLayout.test.ts (new)
33
- ├── useElementGrid.ts (new: container-axis template + item-axis delegate)
34
- ├── useElementGrid.test.ts (new)
35
- ├── useElementSizing.ts (new: w/h/min/max/aspect passthrough)
36
- ├── useElementSizing.test.ts (new)
37
- ├── useElementSurface.ts (new: bg/color/border/radius/shadow passthrough)
38
- ├── useElementSurface.test.ts (new)
39
- ├── useElementInteraction.ts (new: cursor/select/pointer passthrough)
40
- └── useElementInteraction.test.ts (new)
41
-
42
- layers/structure/layout/app/types/element.ts (new: ElementProps union)
43
-
44
- layers/structure/layout/app/components/Element.vue (new)
45
-
46
- apps/playground/app/pages/element.vue (new: manual verification page)
47
- ```
48
-
49
- Each composable owns exactly one property group, mirrors the existing `gridPlacementStyle.ts` / `useLayoutAttrs.ts` pattern (plain exported function, `Partial<CSSProperties>` return), and gets its own test file — matching the existing `gridPlacementStyle.test.ts` convention (`describe`/`it`, vitest, no mocking needed since these are pure functions).
50
-
51
- ---
52
-
53
- ### Task 1: Extend `useLayoutAttrs` with margin support
54
-
55
- **Files:**
56
-
57
- - Modify: `layers/core/app/composables/useLayoutAttrs.ts`
58
- - Test: `layers/core/app/composables/useLayoutAttrs.test.ts` (create if it doesn't exist)
59
-
60
- **Interfaces:**
61
-
62
- - Consumes: nothing new — extends existing `LayoutAttrsInput`, `Spacing` type from `../types/tokens`, `ResponsiveValue` from `../types/responsive`.
63
- - Produces: `LayoutAttrsInput` gains `m?`, `mx?`, `my?` fields (same shape as existing `p`/`px`/`py`). `useLayoutAttrs()`'s returned `style` now also sets `margin`/`marginInline`/`marginBlock` when those props are passed.
64
-
65
- - [ ] **Step 1: Check for an existing test file**
66
-
67
- Run: `ls layers/core/app/composables/useLayoutAttrs.test.ts 2>/dev/null && echo EXISTS || echo MISSING`
68
-
69
- If `EXISTS`, read the file first and add new `describe` blocks to it in Step 2 rather than replacing it. If `MISSING`, Step 2 creates the whole file.
70
-
71
- - [ ] **Step 2: Write the failing tests**
72
-
73
- Create/extend `layers/core/app/composables/useLayoutAttrs.test.ts`:
74
-
75
- ```ts
76
- import { describe, expect, it } from 'vitest'
77
-
78
- import { useLayoutAttrs } from './useLayoutAttrs'
79
-
80
- describe('useLayoutAttrs margin support', () => {
81
- it('sets margin from a static token', () => {
82
- const { style } = useLayoutAttrs(() => ({ m: 'md' }))
83
- expect(style.value.margin).toBe('var(--fluid-space-md)')
84
- })
85
-
86
- it('sets marginInline from px-equivalent mx', () => {
87
- const { style } = useLayoutAttrs(() => ({ mx: 'lg' }))
88
- expect(style.value.marginInline).toBe('var(--fluid-space-lg)')
89
- })
90
-
91
- it('sets marginBlock from my', () => {
92
- const { style } = useLayoutAttrs(() => ({ my: 'sm' }))
93
- expect(style.value.marginBlock).toBe('var(--fluid-space-sm)')
94
- })
95
-
96
- it('resolves none to 0px for margin', () => {
97
- const { style } = useLayoutAttrs(() => ({ m: 'none' }))
98
- expect(style.value.margin).toBe('0px')
99
- })
100
-
101
- it('omits margin keys entirely when not passed', () => {
102
- const { style } = useLayoutAttrs(() => ({ gap: 'md' }))
103
- expect(style.value.margin).toBeUndefined()
104
- expect(style.value.marginInline).toBeUndefined()
105
- expect(style.value.marginBlock).toBeUndefined()
106
- })
107
-
108
- it('does not regress existing gap/align/justify/padding behavior', () => {
109
- const { style } = useLayoutAttrs(() => ({
110
- gap: 'md',
111
- align: 'center',
112
- justify: 'between',
113
- p: 'lg',
114
- }))
115
- expect(style.value).toEqual({
116
- gap: 'var(--fluid-space-md)',
117
- padding: 'var(--fluid-space-lg)',
118
- alignItems: 'center',
119
- justifyContent: 'space-between',
120
- })
121
- })
122
- })
123
- ```
124
-
125
- - [ ] **Step 3: Run tests to verify they fail**
126
-
127
- Run: `cd layers/core && npx vitest run app/composables/useLayoutAttrs.test.ts`
128
- Expected: FAIL — `style.value.margin` is `undefined` for the `m: 'md'` case (property not implemented yet).
129
-
130
- - [ ] **Step 4: Implement margin support**
131
-
132
- Edit `layers/core/app/composables/useLayoutAttrs.ts`:
133
-
134
- ```ts
135
- export type LayoutAttrsInput = {
136
- gap?: ResponsiveValue<Spacing> | Spacing | undefined
137
- align?: ResponsiveValue<Align> | Align | undefined
138
- justify?: ResponsiveValue<Justify> | Justify | undefined
139
- p?: ResponsiveValue<Spacing> | Spacing | undefined
140
- px?: ResponsiveValue<Spacing> | Spacing | undefined
141
- py?: ResponsiveValue<Spacing> | Spacing | undefined
142
- m?: ResponsiveValue<Spacing> | Spacing | undefined
143
- mx?: ResponsiveValue<Spacing> | Spacing | undefined
144
- my?: ResponsiveValue<Spacing> | Spacing | undefined
145
- }
146
- ```
147
-
148
- Update the `applySpacing` call signature to accept the margin CSS props, then extend `useLayoutAttrs`'s `computed`:
149
-
150
- ```ts
151
- function applySpacing(
152
- result: CSSProperties,
153
- cssProp:
154
- | 'gap'
155
- | 'padding'
156
- | 'paddingInline'
157
- | 'paddingBlock'
158
- | 'margin'
159
- | 'marginInline'
160
- | 'marginBlock',
161
- value: ResponsiveValue<Spacing> | Spacing | undefined
162
- ): void {
163
- const resolved = resolveDefault(value)
164
- if (resolved) result[cssProp] = spacingVar(resolved)
165
- }
166
-
167
- export function useLayoutAttrs(input: MaybeRefOrGetter<LayoutAttrsInput>): {
168
- style: ComputedRef<CSSProperties>
169
- } {
170
- const style = computed((): CSSProperties => {
171
- const { gap, align, justify, p, px, py, m, mx, my } = toValue(input)
172
- const result: CSSProperties = {}
173
-
174
- applySpacing(result, 'gap', gap)
175
- applySpacing(result, 'padding', p)
176
- applySpacing(result, 'paddingInline', px)
177
- applySpacing(result, 'paddingBlock', py)
178
- applySpacing(result, 'margin', m)
179
- applySpacing(result, 'marginInline', mx)
180
- applySpacing(result, 'marginBlock', my)
181
-
182
- const resolvedAlign = resolveDefault(align)
183
- if (resolvedAlign) result.alignItems = ALIGN_MAP[resolvedAlign]
184
-
185
- const resolvedJustify = resolveDefault(justify)
186
- if (resolvedJustify) result.justifyContent = JUSTIFY_MAP[resolvedJustify]
187
-
188
- return result
189
- })
190
-
191
- return { style }
192
- }
193
- ```
194
-
195
- - [ ] **Step 5: Run tests to verify they pass**
196
-
197
- Run: `cd layers/core && npx vitest run app/composables/useLayoutAttrs.test.ts`
198
- Expected: PASS, all 6 new tests green.
199
-
200
- - [ ] **Step 6: Run the full core test suite to check for regressions**
201
-
202
- Run: `cd layers/core && npx vitest run`
203
- Expected: PASS — no existing test touching `useLayoutAttrs` breaks.
204
-
205
- - [ ] **Step 7: Commit**
206
-
207
- ```bash
208
- git add layers/core/app/composables/useLayoutAttrs.ts layers/core/app/composables/useLayoutAttrs.test.ts
209
- git commit -m "feat(core): add margin support to useLayoutAttrs
210
-
211
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
212
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
213
- ```
214
-
215
- ---
216
-
217
- ### Task 2: `useElementLayout` — display mode composable
218
-
219
- **Files:**
220
-
221
- - Create: `layers/structure/layout/app/composables/useElementLayout.ts`
222
- - Test: `layers/structure/layout/app/composables/useElementLayout.test.ts`
223
-
224
- **Interfaces:**
225
-
226
- - Consumes: nothing (pure function, no imports beyond Vue's `CSSProperties` type).
227
- - Produces: `ElementLayoutProps` type (`{ block?: boolean; flex?: boolean; grid?: boolean; hidden?: boolean }`) and `useElementLayout(props: ElementLayoutProps): Partial<CSSProperties>`. Both are imported by Task 6 (`Element.vue`).
228
-
229
- - [ ] **Step 1: Write the failing tests**
230
-
231
- Create `layers/structure/layout/app/composables/useElementLayout.test.ts`:
232
-
233
- ```ts
234
- import { describe, expect, it } from 'vitest'
235
-
236
- import { useElementLayout } from './useElementLayout'
237
-
238
- describe('useElementLayout', () => {
239
- it('returns no display when no mode prop is set', () => {
240
- expect(useElementLayout({})).toEqual({ display: undefined })
241
- })
242
-
243
- it('resolves block to display: block', () => {
244
- expect(useElementLayout({ block: true })).toEqual({ display: 'block' })
245
- })
246
-
247
- it('resolves flex to display: flex', () => {
248
- expect(useElementLayout({ flex: true })).toEqual({ display: 'flex' })
249
- })
250
-
251
- it('resolves grid to display: grid', () => {
252
- expect(useElementLayout({ grid: true })).toEqual({ display: 'grid' })
253
- })
254
-
255
- it('resolves hidden to display: none', () => {
256
- expect(useElementLayout({ hidden: true })).toEqual({ display: 'none' })
257
- })
258
-
259
- it('prioritizes hidden over grid, flex, and block when multiple are set', () => {
260
- expect(useElementLayout({ hidden: true, grid: true, flex: true, block: true })).toEqual({
261
- display: 'none',
262
- })
263
- })
264
-
265
- it('prioritizes grid over flex and block', () => {
266
- expect(useElementLayout({ grid: true, flex: true, block: true })).toEqual({
267
- display: 'grid',
268
- })
269
- })
270
-
271
- it('prioritizes flex over block', () => {
272
- expect(useElementLayout({ flex: true, block: true })).toEqual({ display: 'flex' })
273
- })
274
- })
275
- ```
276
-
277
- - [ ] **Step 2: Run tests to verify they fail**
278
-
279
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementLayout.test.ts`
280
- Expected: FAIL — module `./useElementLayout` not found.
281
-
282
- - [ ] **Step 3: Write the implementation**
283
-
284
- Create `layers/structure/layout/app/composables/useElementLayout.ts`:
285
-
286
- ```ts
287
- import type { CSSProperties } from 'vue'
288
-
289
- export interface ElementLayoutProps {
290
- block?: boolean
291
- flex?: boolean
292
- grid?: boolean
293
- hidden?: boolean
294
- }
295
-
296
- type Mode = 'hidden' | 'grid' | 'flex' | 'block'
297
-
298
- const DISPLAY_FOR: Record<Mode, CSSProperties['display']> = {
299
- hidden: 'none',
300
- grid: 'grid',
301
- flex: 'flex',
302
- block: 'block',
303
- }
304
-
305
- export function useElementLayout(props: ElementLayoutProps): Partial<CSSProperties> {
306
- const mode = (['hidden', 'grid', 'flex', 'block'] as const).find((m) => props[m])
307
- return {
308
- display: mode && DISPLAY_FOR[mode],
309
- }
310
- }
311
- ```
312
-
313
- - [ ] **Step 4: Run tests to verify they pass**
314
-
315
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementLayout.test.ts`
316
- Expected: PASS, all 8 tests green.
317
-
318
- - [ ] **Step 5: Commit**
319
-
320
- ```bash
321
- git add layers/structure/layout/app/composables/useElementLayout.ts layers/structure/layout/app/composables/useElementLayout.test.ts
322
- git commit -m "feat(layout): add useElementLayout display-mode composable
323
-
324
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
325
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
326
- ```
327
-
328
- ---
329
-
330
- ### Task 3: `useElementSizing` — dimension composable
331
-
332
- **Files:**
333
-
334
- - Create: `layers/structure/layout/app/composables/useElementSizing.ts`
335
- - Test: `layers/structure/layout/app/composables/useElementSizing.test.ts`
336
-
337
- **Interfaces:**
338
-
339
- - Consumes: nothing.
340
- - Produces: `ElementSizingProps` type and `useElementSizing(props: ElementSizingProps): Partial<CSSProperties>`, imported by Task 6.
341
-
342
- - [ ] **Step 1: Write the failing tests**
343
-
344
- Create `layers/structure/layout/app/composables/useElementSizing.test.ts`:
345
-
346
- ```ts
347
- import { describe, expect, it } from 'vitest'
348
-
349
- import { useElementSizing } from './useElementSizing'
350
-
351
- describe('useElementSizing', () => {
352
- it('returns all-undefined style when no props are set', () => {
353
- expect(useElementSizing({})).toEqual({
354
- width: undefined,
355
- height: undefined,
356
- minWidth: undefined,
357
- maxWidth: undefined,
358
- minHeight: undefined,
359
- maxHeight: undefined,
360
- aspectRatio: undefined,
361
- })
362
- })
363
-
364
- it('maps every prop to its CSS property 1:1', () => {
365
- expect(
366
- useElementSizing({
367
- w: '80px',
368
- h: '40px',
369
- minW: '10rem',
370
- maxW: '20rem',
371
- minH: '5rem',
372
- maxH: '15rem',
373
- aspect: '16 / 9',
374
- })
375
- ).toEqual({
376
- width: '80px',
377
- height: '40px',
378
- minWidth: '10rem',
379
- maxWidth: '20rem',
380
- minHeight: '5rem',
381
- maxHeight: '15rem',
382
- aspectRatio: '16 / 9',
383
- })
384
- })
385
- })
386
- ```
387
-
388
- - [ ] **Step 2: Run tests to verify they fail**
389
-
390
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementSizing.test.ts`
391
- Expected: FAIL — module not found.
392
-
393
- - [ ] **Step 3: Write the implementation**
394
-
395
- Create `layers/structure/layout/app/composables/useElementSizing.ts`:
396
-
397
- ```ts
398
- import type { CSSProperties } from 'vue'
399
-
400
- export interface ElementSizingProps {
401
- w?: CSSProperties['width']
402
- h?: CSSProperties['height']
403
- minW?: CSSProperties['minWidth']
404
- maxW?: CSSProperties['maxWidth']
405
- minH?: CSSProperties['minHeight']
406
- maxH?: CSSProperties['maxHeight']
407
- aspect?: CSSProperties['aspectRatio']
408
- }
409
-
410
- export function useElementSizing(props: ElementSizingProps): Partial<CSSProperties> {
411
- return {
412
- width: props.w,
413
- height: props.h,
414
- minWidth: props.minW,
415
- maxWidth: props.maxW,
416
- minHeight: props.minH,
417
- maxHeight: props.maxH,
418
- aspectRatio: props.aspect,
419
- }
420
- }
421
- ```
422
-
423
- - [ ] **Step 4: Run tests to verify they pass**
424
-
425
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementSizing.test.ts`
426
- Expected: PASS, both tests green.
427
-
428
- - [ ] **Step 5: Commit**
429
-
430
- ```bash
431
- git add layers/structure/layout/app/composables/useElementSizing.ts layers/structure/layout/app/composables/useElementSizing.test.ts
432
- git commit -m "feat(layout): add useElementSizing composable
433
-
434
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
435
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
436
- ```
437
-
438
- ---
439
-
440
- ### Task 4: `useElementSurface` and `useElementInteraction` — surface & interaction composables
441
-
442
- **Files:**
443
-
444
- - Create: `layers/structure/layout/app/composables/useElementSurface.ts`
445
- - Create: `layers/structure/layout/app/composables/useElementInteraction.ts`
446
- - Test: `layers/structure/layout/app/composables/useElementSurface.test.ts`
447
- - Test: `layers/structure/layout/app/composables/useElementInteraction.test.ts`
448
-
449
- **Interfaces:**
450
-
451
- - Consumes: nothing.
452
- - Produces: `ElementSurfaceProps`/`useElementSurface()` and `ElementInteractionProps`/`useElementInteraction()`, both imported by Task 6.
453
-
454
- - [ ] **Step 1: Write the failing surface test**
455
-
456
- Create `layers/structure/layout/app/composables/useElementSurface.test.ts`:
457
-
458
- ```ts
459
- import { describe, expect, it } from 'vitest'
460
-
461
- import { useElementSurface } from './useElementSurface'
462
-
463
- describe('useElementSurface', () => {
464
- it('returns all-undefined style when no props are set', () => {
465
- expect(useElementSurface({})).toEqual({
466
- background: undefined,
467
- color: undefined,
468
- border: undefined,
469
- borderRadius: undefined,
470
- boxShadow: undefined,
471
- })
472
- })
473
-
474
- it('maps every prop to its CSS property 1:1', () => {
475
- expect(
476
- useElementSurface({
477
- bg: 'var(--ui-bg-elevated)',
478
- color: 'var(--ui-text)',
479
- border: '1px solid var(--ui-border)',
480
- radius: '0.5rem',
481
- shadow: '0 4px 12px rgb(0 0 0 / 0.15)',
482
- })
483
- ).toEqual({
484
- background: 'var(--ui-bg-elevated)',
485
- color: 'var(--ui-text)',
486
- border: '1px solid var(--ui-border)',
487
- borderRadius: '0.5rem',
488
- boxShadow: '0 4px 12px rgb(0 0 0 / 0.15)',
489
- })
490
- })
491
- })
492
- ```
493
-
494
- - [ ] **Step 2: Write the failing interaction test**
495
-
496
- Create `layers/structure/layout/app/composables/useElementInteraction.test.ts`:
497
-
498
- ```ts
499
- import { describe, expect, it } from 'vitest'
500
-
501
- import { useElementInteraction } from './useElementInteraction'
502
-
503
- describe('useElementInteraction', () => {
504
- it('returns all-undefined style when no props are set', () => {
505
- expect(useElementInteraction({})).toEqual({
506
- cursor: undefined,
507
- userSelect: undefined,
508
- pointerEvents: undefined,
509
- })
510
- })
511
-
512
- it('maps every prop to its CSS property 1:1', () => {
513
- expect(
514
- useElementInteraction({
515
- cursor: 'pointer',
516
- select: 'none',
517
- pointer: 'none',
518
- })
519
- ).toEqual({
520
- cursor: 'pointer',
521
- userSelect: 'none',
522
- pointerEvents: 'none',
523
- })
524
- })
525
- })
526
- ```
527
-
528
- - [ ] **Step 3: Run both tests to verify they fail**
529
-
530
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementSurface.test.ts app/composables/useElementInteraction.test.ts`
531
- Expected: FAIL — modules not found.
532
-
533
- - [ ] **Step 4: Write the surface implementation**
534
-
535
- Create `layers/structure/layout/app/composables/useElementSurface.ts`:
536
-
537
- ```ts
538
- import type { CSSProperties } from 'vue'
539
-
540
- export interface ElementSurfaceProps {
541
- bg?: CSSProperties['background']
542
- color?: CSSProperties['color']
543
- border?: CSSProperties['border']
544
- radius?: CSSProperties['borderRadius']
545
- shadow?: CSSProperties['boxShadow']
546
- }
547
-
548
- export function useElementSurface(props: ElementSurfaceProps): Partial<CSSProperties> {
549
- return {
550
- background: props.bg,
551
- color: props.color,
552
- border: props.border,
553
- borderRadius: props.radius,
554
- boxShadow: props.shadow,
555
- }
556
- }
557
- ```
558
-
559
- - [ ] **Step 5: Write the interaction implementation**
560
-
561
- Create `layers/structure/layout/app/composables/useElementInteraction.ts`:
562
-
563
- ```ts
564
- import type { CSSProperties } from 'vue'
565
-
566
- export interface ElementInteractionProps {
567
- cursor?: CSSProperties['cursor']
568
- select?: CSSProperties['userSelect']
569
- pointer?: CSSProperties['pointerEvents']
570
- }
571
-
572
- export function useElementInteraction(props: ElementInteractionProps): Partial<CSSProperties> {
573
- return {
574
- cursor: props.cursor,
575
- userSelect: props.select,
576
- pointerEvents: props.pointer,
577
- }
578
- }
579
- ```
580
-
581
- - [ ] **Step 6: Run both tests to verify they pass**
582
-
583
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementSurface.test.ts app/composables/useElementInteraction.test.ts`
584
- Expected: PASS, all 4 tests green.
585
-
586
- - [ ] **Step 7: Commit**
587
-
588
- ```bash
589
- git add layers/structure/layout/app/composables/useElementSurface.ts layers/structure/layout/app/composables/useElementSurface.test.ts layers/structure/layout/app/composables/useElementInteraction.ts layers/structure/layout/app/composables/useElementInteraction.test.ts
590
- git commit -m "feat(layout): add useElementSurface and useElementInteraction composables
591
-
592
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
593
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
594
- ```
595
-
596
- ---
597
-
598
- ### Task 5: `useElementGrid` — container-axis template + item-axis delegate
599
-
600
- **Files:**
601
-
602
- - Create: `layers/structure/layout/app/composables/useElementGrid.ts`
603
- - Test: `layers/structure/layout/app/composables/useElementGrid.test.ts`
604
-
605
- **Interfaces:**
606
-
607
- - Consumes: `buildGridPlacementStyle`, `GridPlacementInput` from `../utils/gridPlacementStyle` (existing, unmodified — see `layers/structure/layout/app/utils/gridPlacementStyle.ts:132`).
608
- - Produces: `ElementGridProps` type and `useElementGrid(props: ElementGridProps): Partial<CSSProperties>`, imported by Task 6. `ElementGridProps` shape: `{ grid?: boolean; cols?: number | string; rows?: number | string; colStart?: number | ResponsiveValue<number>; colSpan?: number | 'full' | ResponsiveValue<number>; rowStart?: number | ResponsiveValue<number>; rowSpan?: number | ResponsiveValue<number> }`.
609
-
610
- - [ ] **Step 1: Write the failing tests**
611
-
612
- Create `layers/structure/layout/app/composables/useElementGrid.test.ts`:
613
-
614
- ```ts
615
- import { describe, expect, it } from 'vitest'
616
-
617
- import { useElementGrid } from './useElementGrid'
618
-
619
- describe('useElementGrid container axis', () => {
620
- it('returns no grid-template properties when grid is not set', () => {
621
- const result = useElementGrid({ cols: 12 })
622
- expect(result.gridTemplateColumns).toBeUndefined()
623
- expect(result.gridTemplateRows).toBeUndefined()
624
- })
625
-
626
- it('converts a numeric cols to a repeat() track when grid is set', () => {
627
- const result = useElementGrid({ grid: true, cols: 12 })
628
- expect(result.gridTemplateColumns).toBe('repeat(12, 1fr)')
629
- })
630
-
631
- it('converts a numeric rows to a repeat() track when grid is set', () => {
632
- const result = useElementGrid({ grid: true, rows: 4 })
633
- expect(result.gridTemplateRows).toBe('repeat(4, 1fr)')
634
- })
635
-
636
- it('passes a string cols track through unchanged', () => {
637
- const result = useElementGrid({ grid: true, cols: '1fr 2fr 1fr' })
638
- expect(result.gridTemplateColumns).toBe('1fr 2fr 1fr')
639
- })
640
-
641
- it('sets display: grid when grid is true', () => {
642
- const result = useElementGrid({ grid: true, cols: 12 })
643
- expect(result.display).toBe('grid')
644
- })
645
- })
646
-
647
- describe('useElementGrid item axis (delegates to buildGridPlacementStyle)', () => {
648
- it('sets gridColumn span from colSpan alone', () => {
649
- const result = useElementGrid({ colSpan: 8 })
650
- expect(result.gridColumn).toBe('1 / -1')
651
- })
652
-
653
- it('sets --_ce custom property when colSpan is not full', () => {
654
- const result = useElementGrid({ colStart: 3, colSpan: 6 })
655
- expect(result['--_cs']).toBe('3')
656
- expect(result['--_ce']).toBe('6')
657
- })
658
-
659
- it('produces identical output to buildGridPlacementStyle for the same input', async () => {
660
- const { buildGridPlacementStyle } = await import('../utils/gridPlacementStyle')
661
- const direct = buildGridPlacementStyle({ colStart: 2, colSpan: 4, rowSpan: 2 })
662
- const viaElement = useElementGrid({ colStart: 2, colSpan: 4, rowSpan: 2 })
663
- expect(viaElement).toMatchObject(direct)
664
- })
665
- })
666
- ```
667
-
668
- - [ ] **Step 2: Run tests to verify they fail**
669
-
670
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementGrid.test.ts`
671
- Expected: FAIL — module not found.
672
-
673
- - [ ] **Step 3: Write the implementation**
674
-
675
- Create `layers/structure/layout/app/composables/useElementGrid.ts`:
676
-
677
- ```ts
678
- import type { CSSProperties } from 'vue'
679
-
680
- import type { ResponsiveValue } from '../types/layouts'
681
- import { buildGridPlacementStyle } from '../utils/gridPlacementStyle'
682
-
683
- export interface ElementGridProps {
684
- grid?: boolean
685
- cols?: number | string
686
- rows?: number | string
687
- colStart?: number | ResponsiveValue<number>
688
- colSpan?: number | 'full' | ResponsiveValue<number>
689
- rowStart?: number | ResponsiveValue<number>
690
- rowSpan?: number | ResponsiveValue<number>
691
- }
692
-
693
- function track(value: number | string | undefined): string | undefined {
694
- return typeof value === 'number' ? `repeat(${value}, 1fr)` : value
695
- }
696
-
697
- export function useElementGrid(props: ElementGridProps): Partial<CSSProperties> {
698
- const placement = buildGridPlacementStyle({
699
- colStart: props.colStart,
700
- colSpan: props.colSpan,
701
- rowStart: props.rowStart,
702
- rowSpan: props.rowSpan,
703
- })
704
-
705
- return {
706
- ...placement,
707
- ...(props.grid && {
708
- display: 'grid',
709
- gridTemplateColumns: track(props.cols),
710
- gridTemplateRows: track(props.rows),
711
- }),
712
- }
713
- }
714
- ```
715
-
716
- - [ ] **Step 4: Confirm the `ResponsiveValue` import path**
717
-
718
- Run: `grep -n "export type ResponsiveValue" layers/structure/layout/app/types/layouts.ts`
719
- Expected: one match — confirms `../types/layouts` is the correct import path for the layer's own `ResponsiveValue` (distinct from `core`'s — see spec §13). If the grep finds no match, locate the actual export and adjust the import in Step 3 before proceeding.
720
-
721
- - [ ] **Step 5: Run tests to verify they pass**
722
-
723
- Run: `cd layers/structure/layout && npx vitest run app/composables/useElementGrid.test.ts`
724
- Expected: PASS, all 8 tests green.
725
-
726
- - [ ] **Step 6: Run the full layout test suite to check for regressions**
727
-
728
- Run: `cd layers/structure/layout && npx vitest run`
729
- Expected: PASS — `gridPlacementStyle.test.ts` and all others still green; confirms `buildGridPlacementStyle` itself was not touched.
730
-
731
- - [ ] **Step 7: Commit**
732
-
733
- ```bash
734
- git add layers/structure/layout/app/composables/useElementGrid.ts layers/structure/layout/app/composables/useElementGrid.test.ts
735
- git commit -m "feat(layout): add useElementGrid composable, reusing buildGridPlacementStyle
736
-
737
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
738
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
739
- ```
740
-
741
- ---
742
-
743
- ### Task 6: `Element.vue` + `ElementProps` type + playground verification page
744
-
745
- **Files:**
746
-
747
- - Create: `layers/structure/layout/app/types/element.ts`
748
- - Create: `layers/structure/layout/app/components/Element.vue`
749
- - Test: `layers/structure/layout/app/components/Element.test.ts`
750
- - Create: `apps/playground/app/pages/element.vue`
751
-
752
- **Interfaces:**
753
-
754
- - Consumes: `ElementLayoutProps`/`useElementLayout` (Task 2), `ElementGridProps`/`useElementGrid` (Task 5), `ElementSizingProps`/`useElementSizing` (Task 3), `ElementSurfaceProps`/`useElementSurface` (Task 4), `ElementInteractionProps`/`useElementInteraction` (Task 4), `LayoutAttrsInput`/`useLayoutAttrs` (core, Task 1).
755
- - Produces: `ElementProps` type (exported union) and the `<Element>` component itself — the final deliverable, consumed by application pages.
756
-
757
- - [ ] **Step 1: Write the failing component test**
758
-
759
- Create `layers/structure/layout/app/components/Element.test.ts`:
760
-
761
- ```ts
762
- import { mount } from '@vue/test-utils'
763
- import { describe, expect, it } from 'vitest'
764
-
765
- import Element from './Element.vue'
766
-
767
- describe('Element', () => {
768
- it('renders as a div by default', () => {
769
- const wrapper = mount(Element)
770
- expect(wrapper.element.tagName).toBe('DIV')
771
- })
772
-
773
- it('renders as the tag passed via the as prop', () => {
774
- const wrapper = mount(Element, { props: { as: 'section' } })
775
- expect(wrapper.element.tagName).toBe('SECTION')
776
- })
777
-
778
- it('renders slot content', () => {
779
- const wrapper = mount(Element, { slots: { default: 'hello' } })
780
- expect(wrapper.text()).toBe('hello')
781
- })
782
-
783
- it('merges layout, sizing, and surface styles into one style attribute', () => {
784
- const wrapper = mount(Element, {
785
- props: {
786
- flex: true,
787
- w: '80px',
788
- bg: 'var(--ui-bg-elevated)',
789
- },
790
- })
791
- const style = wrapper.attributes('style') ?? ''
792
- expect(style).toContain('display: flex')
793
- expect(style).toContain('width: 80px')
794
- expect(style).toContain('background: var(--ui-bg-elevated)')
795
- })
796
-
797
- it('merges spacing (via useLayoutAttrs) and grid item-placement styles together', () => {
798
- const wrapper = mount(Element, {
799
- props: {
800
- p: 'md',
801
- colSpan: 6,
802
- },
803
- })
804
- const style = wrapper.attributes('style') ?? ''
805
- expect(style).toContain('padding: var(--fluid-space-md)')
806
- expect(style).toContain('--_ce: 6')
807
- })
808
- })
809
- ```
810
-
811
- - [ ] **Step 2: Run test to verify it fails**
812
-
813
- Run: `cd layers/structure/layout && npx vitest run app/components/Element.test.ts`
814
- Expected: FAIL — `Element.vue` not found.
815
-
816
- - [ ] **Step 3: Write the ElementProps type**
817
-
818
- Create `layers/structure/layout/app/types/element.ts`:
819
-
820
- ```ts
821
- import type { Component } from 'vue'
822
- import type { LayoutAttrsInput } from '#layers/core/app/composables/useLayoutAttrs'
823
-
824
- import type { ElementGridProps } from '../composables/useElementGrid'
825
- import type { ElementInteractionProps } from '../composables/useElementInteraction'
826
- import type { ElementLayoutProps } from '../composables/useElementLayout'
827
- import type { ElementSizingProps } from '../composables/useElementSizing'
828
- import type { ElementSurfaceProps } from '../composables/useElementSurface'
829
-
830
- export type ElementProps = ElementLayoutProps &
831
- ElementGridProps &
832
- LayoutAttrsInput &
833
- ElementSizingProps &
834
- ElementSurfaceProps &
835
- ElementInteractionProps & {
836
- as?: string | Component
837
- }
838
- ```
839
-
840
- - [ ] **Step 4: Write Element.vue**
841
-
842
- Create `layers/structure/layout/app/components/Element.vue`:
843
-
844
- ```vue
845
- <script setup lang="ts">
846
- import type { CSSProperties } from 'vue'
847
-
848
- import type { ElementProps } from '../types/element'
849
-
850
- const props = withDefaults(defineProps<ElementProps>(), {
851
- as: 'div',
852
- })
853
-
854
- const style = computed<CSSProperties>(() => ({
855
- ...useElementLayout(props),
856
- ...useLayoutAttrs(() => props).style.value,
857
- ...useElementGrid(props),
858
- ...useElementSizing(props),
859
- ...useElementSurface(props),
860
- ...useElementInteraction(props),
861
- }))
862
- </script>
863
-
864
- <template>
865
- <component :is="as" :style>
866
- <slot />
867
- </component>
868
- </template>
869
- ```
870
-
871
- `useElementLayout`, `useLayoutAttrs`, `useElementGrid`, `useElementSizing`, `useElementSurface`, `useElementInteraction` are all Nuxt auto-imports (composables in `app/composables/` are auto-imported within the layer, and `useLayoutAttrs` is auto-imported from `core` since `layout` extends `core` — see `layers/structure/layout/nuxt.config.ts:7`). No explicit imports needed in the `<script setup>` block for them.
872
-
873
- - [ ] **Step 5: Run test to verify it passes**
874
-
875
- Run: `cd layers/structure/layout && npx vitest run app/components/Element.test.ts`
876
- Expected: PASS, all 5 tests green.
877
-
878
- If auto-imports aren't resolved in the Vitest environment (composables not globally available outside a running Nuxt instance), explicitly import them at the top of `Element.vue`'s `<script setup>` instead:
879
-
880
- ```ts
881
- import { useLayoutAttrs } from '#layers/core/app/composables/useLayoutAttrs'
882
-
883
- import { useElementGrid } from '../composables/useElementGrid'
884
- import { useElementInteraction } from '../composables/useElementInteraction'
885
- import { useElementLayout } from '../composables/useElementLayout'
886
- import { useElementSizing } from '../composables/useElementSizing'
887
- import { useElementSurface } from '../composables/useElementSurface'
888
- ```
889
-
890
- Re-run the test after adding imports if the auto-import approach fails.
891
-
892
- - [ ] **Step 6: Create the playground verification page**
893
-
894
- Create `apps/playground/app/pages/element.vue`:
895
-
896
- ```vue
897
- <template>
898
- <Element block p="xl">
899
- <h2>Grid container + item placement</h2>
900
- <Element grid :cols="12" gap="lg" p="lg" bg="var(--ui-bg-elevated)" radius="0.5rem">
901
- <Element :col-span="12" bg="var(--color-green-400)" color="white" p="md" radius="0.25rem">
902
- colSpan 12
903
- </Element>
904
- <Element :col-span="8" bg="var(--color-green-500)" color="white" p="md" radius="0.25rem">
905
- colSpan 8
906
- </Element>
907
- <Element :col-span="4" bg="var(--color-green-600)" color="white" p="md" radius="0.25rem">
908
- colSpan 4
909
- </Element>
910
- <Element
911
- :col-start="3"
912
- :col-span="6"
913
- bg="var(--color-green-700)"
914
- color="white"
915
- p="md"
916
- radius="0.25rem"
917
- >
918
- colStart 3 / colSpan 6
919
- </Element>
920
- </Element>
921
- </Element>
922
-
923
- <Element block p="xl">
924
- <h2>Flex + sizing + interaction</h2>
925
- <Element flex gap="md" p="lg" bg="var(--ui-bg-elevated)" radius="0.5rem">
926
- <Element
927
- w="80px"
928
- h="80px"
929
- bg="var(--color-green-400)"
930
- radius="9999px"
931
- cursor="pointer"
932
- select="none"
933
- />
934
- <Element
935
- w="160px"
936
- h="80px"
937
- bg="var(--color-green-500)"
938
- color="white"
939
- p="sm"
940
- radius="0.5rem"
941
- shadow="0 4px 12px rgb(0 0 0 / 0.15)"
942
- >
943
- card
944
- </Element>
945
- <Element
946
- min-w="120px"
947
- max-w="240px"
948
- h="80px"
949
- bg="var(--color-green-600)"
950
- color="white"
951
- p="sm"
952
- radius="0.5rem"
953
- >
954
- min/max width
955
- </Element>
956
- </Element>
957
- </Element>
958
-
959
- <Element block p="xl">
960
- <h2>Layout modes</h2>
961
- <Element flex gap="sm">
962
- <Element block bg="var(--color-green-400)" p="sm" radius="0.25rem" color="white"
963
- >block</Element
964
- >
965
- <Element flex bg="var(--color-green-500)" p="sm" radius="0.25rem" color="white">flex</Element>
966
- <Element hidden bg="var(--color-green-600)" p="sm" radius="0.25rem" color="white">
967
- hidden (not rendered visibly)
968
- </Element>
969
- </Element>
970
- </Element>
971
-
972
- <Element block p="xl">
973
- <h2>Margin (Task 1 verification)</h2>
974
- <Element bg="var(--ui-bg-elevated)" p="md">
975
- <Element bg="var(--color-green-500)" color="white" p="sm" m="lg" radius="0.25rem">
976
- m="lg" pushes this box away from its siblings
977
- </Element>
978
- </Element>
979
- </Element>
980
- </template>
981
- ```
982
-
983
- - [ ] **Step 7: Manually verify in the browser**
984
-
985
- Run: `PLAYGROUND_LAYERS=core,layout pnpm dev` from the repo root, then navigate to `/element`.
986
-
987
- Verify: the 12-column grid row renders with correctly spanned/positioned green blocks, the flex row shows a circle/card/min-max box, the layout-modes row hides the third box, and the margin box visibly indents from its container. Confirm no console errors.
988
-
989
- - [ ] **Step 8: Run full test suite one more time**
990
-
991
- Run: `pnpm --filter layers/core --filter layers/structure/layout test` (or `cd` into each and run `npx vitest run` if the filter form doesn't match this repo's script names — check `package.json`'s `scripts.test` first with `cat layers/core/package.json layers/structure/layout/package.json | grep -A2 '"scripts"'`)
992
- Expected: PASS across both layers, zero regressions.
993
-
994
- - [ ] **Step 9: Run typecheck**
995
-
996
- Run: `pnpm typecheck`
997
- Expected: PASS — no new type errors from `ElementProps`, `Element.vue`, or the modified `LayoutAttrsInput`.
998
-
999
- - [ ] **Step 10: Commit**
1000
-
1001
- ```bash
1002
- git add layers/structure/layout/app/types/element.ts layers/structure/layout/app/components/Element.vue layers/structure/layout/app/components/Element.test.ts apps/playground/app/pages/element.vue
1003
- git commit -m "feat(layout): add Element polymorphic primitive
1004
-
1005
- Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1006
- Claude-Session: https://claude.ai/code/session_017vMSCbNb6g9hyb9sBUsAQN"
1007
- ```
1008
-
1009
- ---
1010
-
1011
- ## Self-Review
1012
-
1013
- **Spec coverage:**
1014
-
1015
- - §3.1 Layout group → Task 2. §3.2 Spacing (incl. margin) → Task 1. §3.3 Grid (both axes) → Task 5. §3.4 Sizing → Task 3. §3.5 Surface → Task 4. §3.6 Interaction → Task 4. §4 `Element.vue` → Task 6. §5 `ElementProps` type → Task 6. §6 out-of-scope items — no task touches them (confirmed: no typography props, no semantic colour, no align/justify/bleed/density/layer exposed on Element, no LayoutGridItem/gridPlacementStyle edits, no new Grid/Section container component). §7 file layout → matches the File Structure section above exactly, plus a playground page for manual verification not explicitly required by the spec but implied by its own worked examples (§4, §5). §8 testing → per-composable unit tests (Task 1–5) plus an `Element.vue` mount test (Task 6) covering `as` passthrough, slot rendering, and style merge order, matching the spec's stated testing requirements. §9 success criteria — criterion 3 ("zero changes to LayoutGridItem/gridPlacementStyle.ts") is enforced by Task 5 Step 6 (full layout suite re-run) and the Global Constraints section; criterion 4 (HStack/VStack unaffected) is enforced by Task 1 Step 6.
1016
-
1017
- **Placeholder scan:** No TBD/TODO/"add appropriate handling" patterns present. Every step has literal code or an exact command.
1018
-
1019
- **Type consistency:** `ElementGridProps`, `ElementSizingProps`, `ElementSurfaceProps`, `ElementInteractionProps`, `ElementLayoutProps`, `LayoutAttrsInput` are named identically everywhere they're referenced across Tasks 2–6. `useElementGrid`/`useElementSizing`/`useElementSurface`/`useElementInteraction`/`useElementLayout` function names match between their defining task and their `Element.vue` usage (Task 6). `ElementProps`'s field list in Task 6 Step 3 is the exact union of every composable's Props type defined in Tasks 1–5, nothing added or missing.