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.
- package/docs/MIGRATION-v6.4.0.md +215 -0
- package/docs/MIGRATION-v6.5.0.md +87 -0
- package/docs/MIGRATION.md +3 -1
- package/docs/USAGE.md +35 -10
- package/docs/adr/0003-modular-step-system-replaces-fluid-clamps.md +16 -0
- package/docs/component-levels.md +1 -0
- package/docs/layout-shift-causes.md +18 -0
- package/layers/content/app/components/content/Figure.vue +1 -1
- package/layers/content/app/composables/useContentSection.ts +1 -1
- package/layers/core/app/app.config.ts +9 -5
- package/layers/core/app/app.vue +2 -1
- package/layers/core/app/components/LoadingScreen.vue +35 -21
- package/layers/core/app/composables/useCache.ts +2 -2
- package/layers/core/app/composables/useErrorLog.ts +5 -5
- package/layers/core/app/composables/useLoading.ts +19 -10
- package/layers/core/app/plugins/loading.client.ts +11 -3
- package/layers/core/app/types/loading.ts +10 -3
- package/layers/core/app/utils/loading.ts +7 -16
- package/layers/core/nuxt.config.ts +14 -14
- package/layers/core/server/utils/app-config.ts +17 -0
- package/layers/data/auth/app/middleware/auth.ts +1 -1
- package/layers/data/forms/app/components/Form/Contact.vue +1 -1
- package/layers/data/forms/server/api/contact.post.ts +16 -8
- package/layers/data/forms/server/api/forms/status.get.ts +3 -0
- package/layers/data/forms/{app → shared}/utils/contact-schema.ts +2 -1
- package/layers/data/metadata/server/api/metadata/search.get.ts +27 -7
- package/layers/data/metadata/server/api/metadata/status.get.ts +4 -1
- package/layers/data/metadata/server/api/metadata/sync.post.ts +13 -3
- package/layers/data/metadata/server/utils/metadata/resolve-record.ts +3 -3
- package/layers/delivery/feeds/nuxt.config.ts +6 -1
- package/layers/delivery/feeds/package.json +2 -2
- package/layers/delivery/feeds/server/routes/feed/demo.get.ts +2 -2
- package/layers/delivery/feeds/server/utils/content-adapter.ts +1 -1
- package/layers/delivery/feeds/server/utils/feed-service.ts +2 -2
- package/layers/design-system/theming/app/plugins/theme.client.ts +7 -0
- package/layers/design-system/theming/nuxt.config.ts +0 -2
- package/layers/design-system/typography/app/assets/css/typography.css +41 -5
- package/layers/design-system/visual/app/components/HoverDimGroup.vue +11 -3
- package/layers/motion/scroll/app/plugins/locomotive-scroll.client.ts +3 -2
- package/layers/render/canvas/nuxt.config.ts +0 -4
- package/layers/render/shader/nuxt.config.ts +0 -4
- package/layers/structure/navigation/app/app.config.ts +37 -14
- package/layers/structure/navigation/app/components/Mast/Nav.vue +14 -13
- package/layers/structure/navigation/app/components/Mast/NavModal.vue +83 -28
- package/layers/structure/navigation/app/components/Site/Logo.vue +35 -0
- package/layers/structure/navigation/app/components/Site/Title.vue +3 -3
- package/layers/structure/navigation/app/composables/mastNav.ts +38 -13
- package/layers/structure/navigation/app/types/nav.ts +34 -0
- package/layers/structure/routing/app/middleware/01.maintenance.global.ts +4 -1
- package/layers/structure/routing/app/middleware/02.governance.global.ts +1 -1
- package/layers/structure/routing/nuxt.config.ts +19 -12
- package/layers/structure/routing/server/api/feature-flags.get.ts +4 -2
- package/package.json +35 -5
- package/docs/FALLOW-COMPLEXITY-DUPLICATION-AUDIT.md +0 -64
- package/docs/IMPROVE-AUDIT-README.md +0 -29
- package/docs/IMPROVE-AUDIT-RESULTS.md +0 -51
- package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +0 -81
- package/docs/fallow-refactor/apps-debug.md +0 -27
- package/docs/fallow-refactor/apps-playground.md +0 -46
- package/docs/fallow-refactor/apps-visual-identity.md +0 -41
- package/docs/fallow-refactor/layers-animations.md +0 -34
- package/docs/fallow-refactor/layers-canvas.md +0 -32
- package/docs/fallow-refactor/layers-content.md +0 -33
- package/docs/fallow-refactor/layers-core.md +0 -39
- package/docs/fallow-refactor/layers-feeds.md +0 -39
- package/docs/fallow-refactor/layers-forms.md +0 -30
- package/docs/fallow-refactor/layers-layout.md +0 -42
- package/docs/fallow-refactor/layers-mailer.md +0 -32
- package/docs/fallow-refactor/layers-motion.md +0 -27
- package/docs/fallow-refactor/layers-navigation.md +0 -31
- package/docs/fallow-refactor/layers-page-transitions.md +0 -30
- package/docs/fallow-refactor/layers-routing.md +0 -33
- package/docs/fallow-refactor/layers-scripts.md +0 -35
- package/docs/fallow-refactor/layers-scroll.md +0 -38
- package/docs/fallow-refactor/layers-seo.md +0 -32
- package/docs/fallow-refactor/layers-shader.md +0 -53
- package/docs/fallow-refactor/layers-theme.md +0 -33
- package/docs/fallow-refactor/layers-transitions.md +0 -27
- package/docs/fallow-refactor/layers-typography.md +0 -29
- package/docs/fallow-refactor/layers-visual.md +0 -34
- package/docs/superpowers/plans/2026-09-03-layout-layer-fluid-rebuild.md +0 -1850
- package/docs/superpowers/plans/2026-09-03-vue-element-system.md +0 -1019
- package/docs/superpowers/plans/2026-09-04-grid-layer.md +0 -977
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-0-1-design.md +0 -533
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-2-design.md +0 -154
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-3-design.md +0 -109
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-4-design.md +0 -124
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-5-design.md +0 -121
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-6-design.md +0 -77
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-7-design.md +0 -73
- package/docs/superpowers/specs/2026-08-31-swiss-grid-layer-design.md +0 -190
- package/docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md +0 -312
- package/docs/superpowers/specs/2026-09-03-vue-element-system-design.md +0 -342
- package/docs/superpowers/specs/2026-09-04-grid-layer-design.md +0 -236
- package/layers/content/app/utils/galleryImageMeta.test.ts +0 -30
- package/layers/core/app/components/Element.test.ts +0 -90
- package/layers/core/app/components/ElementTw.test.ts +0 -64
- package/layers/core/app/composables/useLayoutAttrs.test.ts +0 -77
- package/layers/core/app/utils/browserInfo.test.ts +0 -81
- package/layers/core/app/utils/featureClasses.test.ts +0 -101
- package/layers/core/app/utils/loading.test.ts +0 -48
- package/layers/core/app/utils/resolveElementStyle.test.ts +0 -99
- package/layers/core/app/utils/scrollGuardClamp.test.ts +0 -36
- package/layers/core/app/utils/styleToTailwindClasses.test.ts +0 -31
- package/layers/data/forms/app/utils/contact-schema.test.ts +0 -75
- package/layers/data/forms/app/utils/fieldProps.test.ts +0 -141
- package/layers/data/metadata/providers/comicvine/server/utils/metadata-comicvine/normalise.test.ts +0 -201
- package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/client.test.ts +0 -76
- package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/normalise.test.ts +0 -144
- package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/client.test.ts +0 -25
- package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +0 -144
- package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +0 -29
- package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/normalise.test.ts +0 -162
- package/layers/data/metadata/server/utils/metadata/cache.test.ts +0 -39
- package/layers/data/metadata/server/utils/metadata/provider-fetch.test.ts +0 -27
- package/layers/data/metadata/server/utils/metadata/provider-registry.test.ts +0 -54
- package/layers/data/metadata/server/utils/metadata/resolve-record.test.ts +0 -87
- package/layers/data/metadata/server/utils/metadata/search.test.ts +0 -204
- package/layers/delivery/feeds/server/utils/content-adapter.test.ts +0 -67
- package/layers/delivery/feeds/server/utils/feed-author.test.ts +0 -71
- package/layers/delivery/feeds/server/utils/feed-config.test.ts +0 -184
- package/layers/delivery/feeds/server/utils/formats.test.ts +0 -71
- package/layers/delivery/seo/app/utils/seoConfig.test.ts +0 -58
- package/layers/design-system/theming/server/utils/accent-css.test.ts +0 -71
- package/layers/design-system/theming/server/utils/fouc-config.test.ts +0 -78
- package/layers/design-system/typography/app/components/Typography/Headline.test.ts +0 -36
- package/layers/design-system/typography/app/components/Typography/index.test.ts +0 -31
- package/layers/design-system/typography/app/composables/color.test.ts +0 -30
- package/layers/design-system/typography/app/composables/typography.test.ts +0 -53
- package/layers/design-system/visual/app/composables/tint.test.ts +0 -22
- package/layers/design-system/visual/app/utils/colorTokens.test.ts +0 -51
- package/layers/design-system/visual/app/utils/gradientStyle.test.ts +0 -75
- package/layers/design-system/visual/app/utils/responsiveSizes.test.ts +0 -71
- package/layers/motion/animations/app/components/Motion/Staggered.test.ts +0 -40
- package/layers/motion/scroll/app/composables/useReducedMotion.test.ts +0 -23
- package/layers/motion/scroll/app/utils/parallax.test.ts +0 -24
- package/layers/structure/grid/app/composables/useGridTracks.test.ts +0 -94
- package/layers/structure/grid/app/types/tracks.test.ts +0 -32
- package/layers/structure/grid/app/utils/placement.test.ts +0 -19
- package/layers/structure/layout/app/utils/gridPlacementStyle.test.ts +0 -269
- package/layers/structure/navigation/app/utils/site.test.ts +0 -39
- 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.
|