create-auto-app 1.109.0 → 1.110.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-auto-app",
3
- "version": "1.109.0",
3
+ "version": "1.110.1",
4
4
  "description": "Create Auto Engineer apps with no configuration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  "fs-extra": "^11.2.0",
34
34
  "inquirer": "^9.2.15",
35
35
  "ora": "^8.0.1",
36
- "@auto-engineer/id": "1.109.0"
36
+ "@auto-engineer/id": "1.110.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/fs-extra": "^11.0.4",
@@ -0,0 +1,308 @@
1
+ {
2
+ "job": {
3
+ "id": "job_workout-log-form",
4
+ "dependsOn": [],
5
+ "target": "ImplementComponent",
6
+ "payload": {
7
+ "componentId": "workout-log-form",
8
+ "structure": [
9
+ "Renders as semantic <main> element with role='main' aria-label='Log workout form'",
10
+ "Composes Card with CardHeader (form-header region: title + description), CardContent (form-fields region: Form + fields), CardFooter (form-actions region: ButtonGroup with submit + cancel)",
11
+ "Form region uses Form component wrapping Field groups for date, type, duration, notes with FormFieldContext for validation",
12
+ "feedback-area region renders Alert in CardContent above form when error state active",
13
+ "Uses two-column responsive layout: form fields in main column, collapsible sidebar optional for workout history preview",
14
+ "Date field composes Calendar with mode='single' and disabled dates before today",
15
+ "Workout type field composes Select with predefined options: 'Strength', 'Cardio', 'HIIT', 'Yoga', 'Other'",
16
+ "Duration field composes Input type='number' with inputMode='numeric' min=1 max=360 step=5",
17
+ "Notes field composes Textarea with field-sizing-content and min-h-16",
18
+ "Actions region uses ButtonGroup orientation='horizontal' with primary submit Button and secondary cancel Button"
19
+ ],
20
+ "rendering": [
21
+ "In loading state (isLoading=true), shows Skeleton h-8 w-48 in form-header region and Skeleton h-64 w-full in form-fields region; hides form-fields, form-actions, feedback-area regions",
22
+ "In ready state (isLoading=false, error=null), shows all regions: form-header, form-fields, form-actions; hides feedback-area",
23
+ "In editing-invalid state (error=null, hasValidationErrors=true), shows all regions including feedback-area with inline FieldError messages colocated below each invalid field",
24
+ "In submitting state (isSubmitting=true), shows form-header and form-actions regions with submit Button disabled and Progress indicator (value=100); hides form-fields and feedback-area",
25
+ "In error state (error truthy), shows form-header and feedback-area with Alert variant='destructive' displaying error.message and Retry Button; hides form-fields and form-actions",
26
+ "Pre-populates workout date to today using new Date() with local timezone; workout type defaults to empty; duration defaults to 30; notes defaults to empty string",
27
+ "Calendar disables all dates before today (new Date().setHours(0,0,0,0)) using disabled matcher",
28
+ "Duration input shows inline validation 'Duration must be 1-360 minutes' when value <1 or >360",
29
+ "Required field asterisks rendered via Label variant='required' on date, type, duration fields"
30
+ ],
31
+ "interaction": [
32
+ "Form submission on Cmd+Enter/Ctrl+Enter or native form Enter-to-submit calls onSubmit: (data: WorkoutFormData) => Promise<void> collecting { workoutDate, workoutType, duration, notes }",
33
+ "Submit Button (label='Log Workout') type='submit' disabled during isSubmitting; shows internal Spinner size='sm' when isSubmitting=true",
34
+ "Cancel Button calls onCancel: () => void navigating to workout-history; shows AlertDialog confirmation when unsavedChanges=true with message 'Unsaved changes will be lost. Continue?'",
35
+ "Each form field calls onFieldChange: (fieldName: string, value: string | number | Date) => void updating internal form state and triggering validation",
36
+ "Validation runs on field blur and form submit: required fields, duration 1-360, workoutType not empty; sets field-level errors cleared on user edit",
37
+ "Retry Button in error state calls onRetry: () => void resetting error=null and transitioning to ready state",
38
+ "Calendar onDayClick calls onFieldChange('workoutDate', selectedDate) with full Date object",
39
+ "Select onValueChange calls onFieldChange('workoutType', value as string)",
40
+ "Input/Textarea onChange calls onFieldChange with trimmed string values; duration parses to number with validation",
41
+ "Form fields set aria-invalid='true' and aria-describedby pointing to colocated FieldMessage when validation fails"
42
+ ],
43
+ "styling": [
44
+ "Main container uses `bg-background text-foreground min-h-screen py-8 px-4 sm:px-6 lg:px-8` with `max-w-2xl mx-auto` for responsive centering and full-bleed padding on small screens",
45
+ "Card root uses `bg-card text-card-foreground rounded-xl border shadow-sm overflow-hidden` with `w-full` to fill container; CardHeader `px-6 py-8 md:py-6`, CardContent `px-6 py-8 space-y-6`, CardFooter `px-6 py-8 bg-muted/50 border-t`",
46
+ "Form header title uses `text-2xl font-bold leading-tight tracking-tight text-foreground text-balance`, description uses `mt-2 text-muted-foreground text-lg/relaxed leading-relaxed`",
47
+ "In loading state (`isLoading=true`), form-header shows Skeleton `h-8 w-48 rounded-lg mb-3` followed by Skeleton `h-4 w-32 rounded-md`; form-fields shows Skeleton `h-64 w-full rounded-lg`; uses `animate-pulse` with `transition-opacity duration-300` fade-in/out for region swaps",
48
+ "Form fields use responsive two-column layout `@container sm:grid-cols-2 sm:gap-6` with date/type fields col-span-1 and duration/notes col-span-2; each Field uses `space-y-2` with `w-full`",
49
+ "ButtonGroup in form-actions uses `w-full flex-col sm:flex-row sm:justify-end sm:gap-3` with submit Button `variant=\"default\" size=\"default\" w-full sm:w-auto` and cancel Button `variant=\"outline\" size=\"default\" w-full sm:w-auto`",
50
+ "Submit Button during `isSubmitting=true` uses `pointer-events-none` with internal Spinner `size-4 mr-2 shrink-0 animate-spin` and text `Logging workout...`; Progress bar uses `useSpring({ width: isSubmitting ? '100%' : '0%', config: { tension: 120, friction: 14 } })` with `h-1.5 bg-primary/20 rounded-full overflow-hidden absolute bottom-0 left-0`; respects `useReducedMotion()` with `immediate: true`",
51
+ "Feedback-area Alert in error state uses `variant=\"destructive\" relative w-full mx-auto max-w-md` positioned above form with `mt-4 mb-6`; AlertTitle `font-semibold text-destructive`, AlertDescription `text-sm`; Retry Button `variant=\"default\" size=\"sm\" ml-auto mt-2`",
52
+ "FieldLabel uses `variant=\"required\" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70`; invalid fields (`aria-invalid=true`) use `data-[error=true]:text-destructive` with colocated FieldError `text-destructive text-sm mt-1 ml-2 list-disc`",
53
+ "Form validation errors use inline FieldMessage `text-destructive text-xs mt-1.5 ml-3` with `before:content-['•'] before:mr-1`; uses `font-variant-numeric: tabular-nums` on duration Input for changing numeric values",
54
+ "Skeleton placeholders match content dimensions: date field `h-9 w-full rounded-md`, select trigger `h-9 w-full rounded-md`, duration `h-9 w-20 rounded-md`, textarea `h-16 w-full rounded-md`; shimmer uses `bg-gradient-to-r from-muted/50 via-muted to-muted/50 animate-shimmer` where `@keyframes shimmer { 0% { background-position: -200% 0 } 100% { background-position: 200% 0 } }` with `background-size: 400% 100%`",
55
+ "All interactive elements (Buttons, Inputs, Select trigger, Calendar day buttons) use minimum 44px touch targets via `min-h-[44px]` pseudo-element expansion `::before:content-[''] absolute -inset-1 rounded-md pointer-events-none`; `touch-action: manipulation`; hover effects `@media (hover: hover) and (pointer: fine)` only",
56
+ "Focus management: all interactive elements use `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background`; primary buttons use `focus-visible:ring-ring/50 ring-[3px]`; form inputs use neutral ring colors",
57
+ "Responsive typography: form title `text-xl sm:text-2xl md:text-3xl`, field labels `text-sm md:text-base`; uses `text-wrap: balance` on headings; `-webkit-font-smoothing: antialiased -moz-osx-font-smoothing: grayscale` on root",
58
+ "Page entrance uses `useSpring({ opacity: isReady ? 1 : 0, transform: isReady ? 'translateY(0)' : 'translateY(8px)', config: { tension: 400, friction: 26 } })` on main container when transitioning from loading→ready; `useReducedMotion()` skips with `immediate: true`; state transitions (ready↔editing-invalid, submitting↔error) use gentle `{ tension: 170, friction: 26 }` for layout shifts",
59
+ "Error Alert entrance uses `useTransition(error !== null, { from: { opacity: 0, height: 0 }, enter: { opacity: 1, height: 'auto' }, leave: { opacity: 0, height: 0 }, config: { tension: 250, friction: 28 } })` measuring content height with ResizeObserver ref; paired with form-fields exit using same config",
60
+ "Dark mode shadows reduce opacity: `shadow-sm dark:shadow-xs`; borders use `border-border dark:border-border/80`; Card uses `dark:bg-card/95`; form inputs `dark:bg-input/30 dark:border-input dark:ring-input/20`",
61
+ "Z-index: AlertDialog confirmation uses modal-overlay `z-[300]` and content `z-[400]`; no other overlays needed; form uses `isolation: isolate` for local stacking context",
62
+ "Cancel AlertDialog uses `size=\"sm\"` with AlertDialogContent `max-w-md mx-auto`; header `text-left gap-2 p-6`; title `text-xl font-semibold`; description `text-muted-foreground text-sm`; footer `flex-col-reverse sm:flex-row sm:justify-end gap-2 pt-4 pb-6`; Action Button `variant=\"destructive\"`, Cancel Button `variant=\"outline`",
63
+ "Accessibility polish: form uses `aria-labelledby` pointing to title; field groups use `role='group' aria-labelledby=labelId`; validation errors `role='alert' aria-live='polite'`; loading skeletons `aria-hidden='true'`; `scroll-margin-top: 2rem` on main for anchor navigation"
64
+ ],
65
+ "storybookPath": "src/components/WorkoutLogForm.stories.tsx",
66
+ "files": {
67
+ "create": [
68
+ "src/components/WorkoutLogForm.tsx"
69
+ ]
70
+ },
71
+ "composes": [
72
+ {
73
+ "id": "ui-components-card",
74
+ "path": "./src/components/ui/Card.tsx"
75
+ },
76
+ {
77
+ "id": "ui-components-form",
78
+ "path": "./src/components/ui/Form.tsx"
79
+ },
80
+ {
81
+ "id": "ui-components-field",
82
+ "path": "./src/components/ui/Field.tsx"
83
+ },
84
+ {
85
+ "id": "ui-components-label",
86
+ "path": "./src/components/ui/Label.tsx"
87
+ },
88
+ {
89
+ "id": "ui-components-input",
90
+ "path": "./src/components/ui/Input.tsx"
91
+ },
92
+ {
93
+ "id": "ui-components-textarea",
94
+ "path": "./src/components/ui/Textarea.tsx"
95
+ },
96
+ {
97
+ "id": "ui-components-button",
98
+ "path": "./src/components/ui/Button.tsx"
99
+ },
100
+ {
101
+ "id": "ui-components-calendar",
102
+ "path": "./src/components/ui/Calendar.tsx"
103
+ },
104
+ {
105
+ "id": "ui-components-select",
106
+ "path": "./src/components/ui/Select.tsx"
107
+ },
108
+ {
109
+ "id": "ui-components-skeleton",
110
+ "path": "./src/components/ui/Skeleton.tsx"
111
+ },
112
+ {
113
+ "id": "ui-components-alert",
114
+ "path": "./src/components/ui/Alert.tsx"
115
+ },
116
+ {
117
+ "id": "ui-components-alertdialog",
118
+ "path": "./src/components/ui/AlertDialog.tsx"
119
+ }
120
+ ],
121
+ "props": [
122
+ {
123
+ "name": "isLoading",
124
+ "type": "boolean",
125
+ "required": false,
126
+ "default": "false",
127
+ "description": "Shows loading skeletons when true.",
128
+ "category": "state"
129
+ },
130
+ {
131
+ "name": "isSubmitting",
132
+ "type": "boolean",
133
+ "required": false,
134
+ "default": "false",
135
+ "description": "Disables submit button and shows progress when true.",
136
+ "category": "state"
137
+ },
138
+ {
139
+ "name": "error",
140
+ "type": "string | null",
141
+ "required": false,
142
+ "default": "null",
143
+ "description": "Error message to display in error state.",
144
+ "category": "state"
145
+ },
146
+ {
147
+ "name": "initialData",
148
+ "type": "{ workoutDate?: Date; workoutType?: string; duration?: number; notes?: string }",
149
+ "required": false,
150
+ "default": "{}",
151
+ "description": "Initial form values for editing existing workout.",
152
+ "category": "data"
153
+ },
154
+ {
155
+ "name": "onSubmit",
156
+ "type": "(data: { workoutDate: Date; workoutType: string; duration: number; notes: string }) => Promise<void>",
157
+ "required": true,
158
+ "description": "Form submission handler receiving validated workout data.",
159
+ "category": "callback"
160
+ },
161
+ {
162
+ "name": "onCancel",
163
+ "type": "() => void",
164
+ "required": true,
165
+ "description": "Cancel handler navigating away from form.",
166
+ "category": "callback"
167
+ },
168
+ {
169
+ "name": "onRetry",
170
+ "type": "() => void",
171
+ "required": false,
172
+ "default": "() => {}",
173
+ "description": "Retry handler after submission error.",
174
+ "category": "callback"
175
+ },
176
+ {
177
+ "name": "unsavedChanges",
178
+ "type": "boolean",
179
+ "required": false,
180
+ "default": "false",
181
+ "description": "Whether form has unsaved changes requiring cancel confirmation.",
182
+ "category": "state"
183
+ }
184
+ ],
185
+ "storyVariants": [
186
+ {
187
+ "name": "Default",
188
+ "description": "Ready state with pre-filled today date and empty other fields.",
189
+ "args": {
190
+ "isLoading": false,
191
+ "isSubmitting": false,
192
+ "error": null,
193
+ "initialData": {
194
+ "workoutDate": "2024-10-15",
195
+ "workoutType": "",
196
+ "duration": 30,
197
+ "notes": ""
198
+ },
199
+ "onSubmit": "() => {}",
200
+ "onCancel": "() => {}",
201
+ "onRetry": "() => {}"
202
+ }
203
+ },
204
+ {
205
+ "name": "Loading",
206
+ "description": "Loading state with skeletons.",
207
+ "args": {
208
+ "isLoading": true,
209
+ "isSubmitting": false,
210
+ "error": null,
211
+ "initialData": {},
212
+ "onSubmit": "() => {}",
213
+ "onCancel": "() => {}",
214
+ "onRetry": "() => {}"
215
+ }
216
+ },
217
+ {
218
+ "name": "Submitting",
219
+ "description": "Submitting state with disabled submit button and progress.",
220
+ "args": {
221
+ "isLoading": false,
222
+ "isSubmitting": true,
223
+ "error": null,
224
+ "initialData": {
225
+ "workoutDate": "2024-10-15",
226
+ "workoutType": "Strength",
227
+ "duration": 45,
228
+ "notes": "Bench press 3x10"
229
+ },
230
+ "onSubmit": "() => {}",
231
+ "onCancel": "() => {}",
232
+ "onRetry": "() => {}"
233
+ },
234
+ "needsPlayFunction": false
235
+ },
236
+ {
237
+ "name": "ValidationError",
238
+ "description": "Invalid state with field-level validation errors.",
239
+ "args": {
240
+ "isLoading": false,
241
+ "isSubmitting": false,
242
+ "error": null,
243
+ "initialData": {
244
+ "workoutDate": "",
245
+ "workoutType": "",
246
+ "duration": 0,
247
+ "notes": ""
248
+ },
249
+ "onSubmit": "() => {}",
250
+ "onCancel": "() => {}",
251
+ "onRetry": "() => {}"
252
+ }
253
+ },
254
+ {
255
+ "name": "SubmitError",
256
+ "description": "Error state with server error message and retry button.",
257
+ "args": {
258
+ "isLoading": false,
259
+ "isSubmitting": false,
260
+ "error": "Failed to save workout. Please check your connection and try again.",
261
+ "initialData": {
262
+ "workoutDate": "2024-10-15",
263
+ "workoutType": "Strength",
264
+ "duration": 45,
265
+ "notes": "Bench press 3x10"
266
+ },
267
+ "onSubmit": "() => {}",
268
+ "onCancel": "() => {}",
269
+ "onRetry": "() => {}"
270
+ },
271
+ "needsPlayFunction": true,
272
+ "playDescription": "1. Click getByRole('button', { name: 'Retry' }); 2. Wait for error Alert to disappear using waitFor(() => !getByRole('alert')); 3. Verify form fields are enabled expect(getByLabelText('Workout date')).not.toBeDisabled();"
273
+ },
274
+ {
275
+ "name": "UnsavedChanges",
276
+ "description": "Form with unsaved changes triggering cancel confirmation.",
277
+ "args": {
278
+ "isLoading": false,
279
+ "isSubmitting": false,
280
+ "error": null,
281
+ "initialData": {
282
+ "workoutDate": "2024-10-15",
283
+ "workoutType": "Strength",
284
+ "duration": 60,
285
+ "notes": "Changed notes"
286
+ },
287
+ "unsavedChanges": true,
288
+ "onSubmit": "() => {}",
289
+ "onCancel": "() => {}",
290
+ "onRetry": "() => {}"
291
+ },
292
+ "needsPlayFunction": true,
293
+ "playDescription": "1. Click getByRole('button', { name: 'Cancel' }); 2. Verify AlertDialog opens with waitFor(() => getByRole('dialog')); 3. Click getByRole('button', { name: /cancel|close/i }) to dismiss confirmation; 4. Verify form unchanged expect(getByLabelText('Duration')).toHaveValue('60');"
294
+ }
295
+ ],
296
+ "dataContract": {
297
+ "source": "props",
298
+ "propsFieldName": "initialData",
299
+ "fields": [
300
+ "workoutDate",
301
+ "workoutType",
302
+ "duration",
303
+ "notes"
304
+ ]
305
+ }
306
+ }
307
+ }
308
+ }
@@ -0,0 +1,320 @@
1
+ {
2
+ "graphId": "ux-components-1772662041530",
3
+ "jobs": [
4
+ {
5
+ "id": "job_workout-log-form",
6
+ "dependsOn": [],
7
+ "target": "ImplementComponent",
8
+ "payload": {
9
+ "job": {
10
+ "id": "job_workout-log-form",
11
+ "dependsOn": [],
12
+ "target": "ImplementComponent",
13
+ "payload": {
14
+ "componentId": "workout-log-form",
15
+ "structure": [
16
+ "Renders as semantic <main> element with role='main' aria-label='Log workout form'",
17
+ "Composes Card with CardHeader (form-header region: title + description), CardContent (form-fields region: Form + fields), CardFooter (form-actions region: ButtonGroup with submit + cancel)",
18
+ "Form region uses Form component wrapping Field groups for date, type, duration, notes with FormFieldContext for validation",
19
+ "feedback-area region renders Alert in CardContent above form when error state active",
20
+ "Uses two-column responsive layout: form fields in main column, collapsible sidebar optional for workout history preview",
21
+ "Date field composes Calendar with mode='single' and disabled dates before today",
22
+ "Workout type field composes Select with predefined options: 'Strength', 'Cardio', 'HIIT', 'Yoga', 'Other'",
23
+ "Duration field composes Input type='number' with inputMode='numeric' min=1 max=360 step=5",
24
+ "Notes field composes Textarea with field-sizing-content and min-h-16",
25
+ "Actions region uses ButtonGroup orientation='horizontal' with primary submit Button and secondary cancel Button"
26
+ ],
27
+ "rendering": [
28
+ "In loading state (isLoading=true), shows Skeleton h-8 w-48 in form-header region and Skeleton h-64 w-full in form-fields region; hides form-fields, form-actions, feedback-area regions",
29
+ "In ready state (isLoading=false, error=null), shows all regions: form-header, form-fields, form-actions; hides feedback-area",
30
+ "In editing-invalid state (error=null, hasValidationErrors=true), shows all regions including feedback-area with inline FieldError messages colocated below each invalid field",
31
+ "In submitting state (isSubmitting=true), shows form-header and form-actions regions with submit Button disabled and Progress indicator (value=100); hides form-fields and feedback-area",
32
+ "In error state (error truthy), shows form-header and feedback-area with Alert variant='destructive' displaying error.message and Retry Button; hides form-fields and form-actions",
33
+ "Pre-populates workout date to today using new Date() with local timezone; workout type defaults to empty; duration defaults to 30; notes defaults to empty string",
34
+ "Calendar disables all dates before today (new Date().setHours(0,0,0,0)) using disabled matcher",
35
+ "Duration input shows inline validation 'Duration must be 1-360 minutes' when value <1 or >360",
36
+ "Required field asterisks rendered via Label variant='required' on date, type, duration fields"
37
+ ],
38
+ "interaction": [
39
+ "Form submission on Cmd+Enter/Ctrl+Enter or native form Enter-to-submit calls onSubmit: (data: WorkoutFormData) => Promise<void> collecting { workoutDate, workoutType, duration, notes }",
40
+ "Submit Button (label='Log Workout') type='submit' disabled during isSubmitting; shows internal Spinner size='sm' when isSubmitting=true",
41
+ "Cancel Button calls onCancel: () => void navigating to workout-history; shows AlertDialog confirmation when unsavedChanges=true with message 'Unsaved changes will be lost. Continue?'",
42
+ "Each form field calls onFieldChange: (fieldName: string, value: string | number | Date) => void updating internal form state and triggering validation",
43
+ "Validation runs on field blur and form submit: required fields, duration 1-360, workoutType not empty; sets field-level errors cleared on user edit",
44
+ "Retry Button in error state calls onRetry: () => void resetting error=null and transitioning to ready state",
45
+ "Calendar onDayClick calls onFieldChange('workoutDate', selectedDate) with full Date object",
46
+ "Select onValueChange calls onFieldChange('workoutType', value as string)",
47
+ "Input/Textarea onChange calls onFieldChange with trimmed string values; duration parses to number with validation",
48
+ "Form fields set aria-invalid='true' and aria-describedby pointing to colocated FieldMessage when validation fails"
49
+ ],
50
+ "styling": [
51
+ "Main container uses `bg-background text-foreground min-h-screen py-8 px-4 sm:px-6 lg:px-8` with `max-w-2xl mx-auto` for responsive centering and full-bleed padding on small screens",
52
+ "Card root uses `bg-card text-card-foreground rounded-xl border shadow-sm overflow-hidden` with `w-full` to fill container; CardHeader `px-6 py-8 md:py-6`, CardContent `px-6 py-8 space-y-6`, CardFooter `px-6 py-8 bg-muted/50 border-t`",
53
+ "Form header title uses `text-2xl font-bold leading-tight tracking-tight text-foreground text-balance`, description uses `mt-2 text-muted-foreground text-lg/relaxed leading-relaxed`",
54
+ "In loading state (`isLoading=true`), form-header shows Skeleton `h-8 w-48 rounded-lg mb-3` followed by Skeleton `h-4 w-32 rounded-md`; form-fields shows Skeleton `h-64 w-full rounded-lg`; uses `animate-pulse` with `transition-opacity duration-300` fade-in/out for region swaps",
55
+ "Form fields use responsive two-column layout `@container sm:grid-cols-2 sm:gap-6` with date/type fields col-span-1 and duration/notes col-span-2; each Field uses `space-y-2` with `w-full`",
56
+ "ButtonGroup in form-actions uses `w-full flex-col sm:flex-row sm:justify-end sm:gap-3` with submit Button `variant=\"default\" size=\"default\" w-full sm:w-auto` and cancel Button `variant=\"outline\" size=\"default\" w-full sm:w-auto`",
57
+ "Submit Button during `isSubmitting=true` uses `pointer-events-none` with internal Spinner `size-4 mr-2 shrink-0 animate-spin` and text `Logging workout...`; Progress bar uses `useSpring({ width: isSubmitting ? '100%' : '0%', config: { tension: 120, friction: 14 } })` with `h-1.5 bg-primary/20 rounded-full overflow-hidden absolute bottom-0 left-0`; respects `useReducedMotion()` with `immediate: true`",
58
+ "Feedback-area Alert in error state uses `variant=\"destructive\" relative w-full mx-auto max-w-md` positioned above form with `mt-4 mb-6`; AlertTitle `font-semibold text-destructive`, AlertDescription `text-sm`; Retry Button `variant=\"default\" size=\"sm\" ml-auto mt-2`",
59
+ "FieldLabel uses `variant=\"required\" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70`; invalid fields (`aria-invalid=true`) use `data-[error=true]:text-destructive` with colocated FieldError `text-destructive text-sm mt-1 ml-2 list-disc`",
60
+ "Form validation errors use inline FieldMessage `text-destructive text-xs mt-1.5 ml-3` with `before:content-['•'] before:mr-1`; uses `font-variant-numeric: tabular-nums` on duration Input for changing numeric values",
61
+ "Skeleton placeholders match content dimensions: date field `h-9 w-full rounded-md`, select trigger `h-9 w-full rounded-md`, duration `h-9 w-20 rounded-md`, textarea `h-16 w-full rounded-md`; shimmer uses `bg-gradient-to-r from-muted/50 via-muted to-muted/50 animate-shimmer` where `@keyframes shimmer { 0% { background-position: -200% 0 } 100% { background-position: 200% 0 } }` with `background-size: 400% 100%`",
62
+ "All interactive elements (Buttons, Inputs, Select trigger, Calendar day buttons) use minimum 44px touch targets via `min-h-[44px]` pseudo-element expansion `::before:content-[''] absolute -inset-1 rounded-md pointer-events-none`; `touch-action: manipulation`; hover effects `@media (hover: hover) and (pointer: fine)` only",
63
+ "Focus management: all interactive elements use `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background`; primary buttons use `focus-visible:ring-ring/50 ring-[3px]`; form inputs use neutral ring colors",
64
+ "Responsive typography: form title `text-xl sm:text-2xl md:text-3xl`, field labels `text-sm md:text-base`; uses `text-wrap: balance` on headings; `-webkit-font-smoothing: antialiased -moz-osx-font-smoothing: grayscale` on root",
65
+ "Page entrance uses `useSpring({ opacity: isReady ? 1 : 0, transform: isReady ? 'translateY(0)' : 'translateY(8px)', config: { tension: 400, friction: 26 } })` on main container when transitioning from loading→ready; `useReducedMotion()` skips with `immediate: true`; state transitions (ready↔editing-invalid, submitting↔error) use gentle `{ tension: 170, friction: 26 }` for layout shifts",
66
+ "Error Alert entrance uses `useTransition(error !== null, { from: { opacity: 0, height: 0 }, enter: { opacity: 1, height: 'auto' }, leave: { opacity: 0, height: 0 }, config: { tension: 250, friction: 28 } })` measuring content height with ResizeObserver ref; paired with form-fields exit using same config",
67
+ "Dark mode shadows reduce opacity: `shadow-sm dark:shadow-xs`; borders use `border-border dark:border-border/80`; Card uses `dark:bg-card/95`; form inputs `dark:bg-input/30 dark:border-input dark:ring-input/20`",
68
+ "Z-index: AlertDialog confirmation uses modal-overlay `z-[300]` and content `z-[400]`; no other overlays needed; form uses `isolation: isolate` for local stacking context",
69
+ "Cancel AlertDialog uses `size=\"sm\"` with AlertDialogContent `max-w-md mx-auto`; header `text-left gap-2 p-6`; title `text-xl font-semibold`; description `text-muted-foreground text-sm`; footer `flex-col-reverse sm:flex-row sm:justify-end gap-2 pt-4 pb-6`; Action Button `variant=\"destructive\"`, Cancel Button `variant=\"outline`",
70
+ "Accessibility polish: form uses `aria-labelledby` pointing to title; field groups use `role='group' aria-labelledby=labelId`; validation errors `role='alert' aria-live='polite'`; loading skeletons `aria-hidden='true'`; `scroll-margin-top: 2rem` on main for anchor navigation"
71
+ ],
72
+ "storybookPath": "src/components/WorkoutLogForm.stories.tsx",
73
+ "files": {
74
+ "create": [
75
+ "src/components/WorkoutLogForm.tsx"
76
+ ]
77
+ },
78
+ "composes": [
79
+ {
80
+ "id": "ui-components-card",
81
+ "path": "./src/components/ui/Card.tsx"
82
+ },
83
+ {
84
+ "id": "ui-components-form",
85
+ "path": "./src/components/ui/Form.tsx"
86
+ },
87
+ {
88
+ "id": "ui-components-field",
89
+ "path": "./src/components/ui/Field.tsx"
90
+ },
91
+ {
92
+ "id": "ui-components-label",
93
+ "path": "./src/components/ui/Label.tsx"
94
+ },
95
+ {
96
+ "id": "ui-components-input",
97
+ "path": "./src/components/ui/Input.tsx"
98
+ },
99
+ {
100
+ "id": "ui-components-textarea",
101
+ "path": "./src/components/ui/Textarea.tsx"
102
+ },
103
+ {
104
+ "id": "ui-components-button",
105
+ "path": "./src/components/ui/Button.tsx"
106
+ },
107
+ {
108
+ "id": "ui-components-calendar",
109
+ "path": "./src/components/ui/Calendar.tsx"
110
+ },
111
+ {
112
+ "id": "ui-components-select",
113
+ "path": "./src/components/ui/Select.tsx"
114
+ },
115
+ {
116
+ "id": "ui-components-skeleton",
117
+ "path": "./src/components/ui/Skeleton.tsx"
118
+ },
119
+ {
120
+ "id": "ui-components-alert",
121
+ "path": "./src/components/ui/Alert.tsx"
122
+ },
123
+ {
124
+ "id": "ui-components-alertdialog",
125
+ "path": "./src/components/ui/AlertDialog.tsx"
126
+ }
127
+ ],
128
+ "props": [
129
+ {
130
+ "name": "isLoading",
131
+ "type": "boolean",
132
+ "required": false,
133
+ "default": "false",
134
+ "description": "Shows loading skeletons when true.",
135
+ "category": "state"
136
+ },
137
+ {
138
+ "name": "isSubmitting",
139
+ "type": "boolean",
140
+ "required": false,
141
+ "default": "false",
142
+ "description": "Disables submit button and shows progress when true.",
143
+ "category": "state"
144
+ },
145
+ {
146
+ "name": "error",
147
+ "type": "string | null",
148
+ "required": false,
149
+ "default": "null",
150
+ "description": "Error message to display in error state.",
151
+ "category": "state"
152
+ },
153
+ {
154
+ "name": "initialData",
155
+ "type": "{ workoutDate?: Date; workoutType?: string; duration?: number; notes?: string }",
156
+ "required": false,
157
+ "default": "{}",
158
+ "description": "Initial form values for editing existing workout.",
159
+ "category": "data"
160
+ },
161
+ {
162
+ "name": "onSubmit",
163
+ "type": "(data: { workoutDate: Date; workoutType: string; duration: number; notes: string }) => Promise<void>",
164
+ "required": true,
165
+ "description": "Form submission handler receiving validated workout data.",
166
+ "category": "callback"
167
+ },
168
+ {
169
+ "name": "onCancel",
170
+ "type": "() => void",
171
+ "required": true,
172
+ "description": "Cancel handler navigating away from form.",
173
+ "category": "callback"
174
+ },
175
+ {
176
+ "name": "onRetry",
177
+ "type": "() => void",
178
+ "required": false,
179
+ "default": "() => {}",
180
+ "description": "Retry handler after submission error.",
181
+ "category": "callback"
182
+ },
183
+ {
184
+ "name": "unsavedChanges",
185
+ "type": "boolean",
186
+ "required": false,
187
+ "default": "false",
188
+ "description": "Whether form has unsaved changes requiring cancel confirmation.",
189
+ "category": "state"
190
+ }
191
+ ],
192
+ "storyVariants": [
193
+ {
194
+ "name": "Default",
195
+ "description": "Ready state with pre-filled today date and empty other fields.",
196
+ "args": {
197
+ "isLoading": false,
198
+ "isSubmitting": false,
199
+ "error": null,
200
+ "initialData": {
201
+ "workoutDate": "2024-10-15",
202
+ "workoutType": "",
203
+ "duration": 30,
204
+ "notes": ""
205
+ },
206
+ "onSubmit": "() => {}",
207
+ "onCancel": "() => {}",
208
+ "onRetry": "() => {}"
209
+ }
210
+ },
211
+ {
212
+ "name": "Loading",
213
+ "description": "Loading state with skeletons.",
214
+ "args": {
215
+ "isLoading": true,
216
+ "isSubmitting": false,
217
+ "error": null,
218
+ "initialData": {},
219
+ "onSubmit": "() => {}",
220
+ "onCancel": "() => {}",
221
+ "onRetry": "() => {}"
222
+ }
223
+ },
224
+ {
225
+ "name": "Submitting",
226
+ "description": "Submitting state with disabled submit button and progress.",
227
+ "args": {
228
+ "isLoading": false,
229
+ "isSubmitting": true,
230
+ "error": null,
231
+ "initialData": {
232
+ "workoutDate": "2024-10-15",
233
+ "workoutType": "Strength",
234
+ "duration": 45,
235
+ "notes": "Bench press 3x10"
236
+ },
237
+ "onSubmit": "() => {}",
238
+ "onCancel": "() => {}",
239
+ "onRetry": "() => {}"
240
+ },
241
+ "needsPlayFunction": false
242
+ },
243
+ {
244
+ "name": "ValidationError",
245
+ "description": "Invalid state with field-level validation errors.",
246
+ "args": {
247
+ "isLoading": false,
248
+ "isSubmitting": false,
249
+ "error": null,
250
+ "initialData": {
251
+ "workoutDate": "",
252
+ "workoutType": "",
253
+ "duration": 0,
254
+ "notes": ""
255
+ },
256
+ "onSubmit": "() => {}",
257
+ "onCancel": "() => {}",
258
+ "onRetry": "() => {}"
259
+ }
260
+ },
261
+ {
262
+ "name": "SubmitError",
263
+ "description": "Error state with server error message and retry button.",
264
+ "args": {
265
+ "isLoading": false,
266
+ "isSubmitting": false,
267
+ "error": "Failed to save workout. Please check your connection and try again.",
268
+ "initialData": {
269
+ "workoutDate": "2024-10-15",
270
+ "workoutType": "Strength",
271
+ "duration": 45,
272
+ "notes": "Bench press 3x10"
273
+ },
274
+ "onSubmit": "() => {}",
275
+ "onCancel": "() => {}",
276
+ "onRetry": "() => {}"
277
+ },
278
+ "needsPlayFunction": true,
279
+ "playDescription": "1. Click getByRole('button', { name: 'Retry' }); 2. Wait for error Alert to disappear using waitFor(() => !getByRole('alert')); 3. Verify form fields are enabled expect(getByLabelText('Workout date')).not.toBeDisabled();"
280
+ },
281
+ {
282
+ "name": "UnsavedChanges",
283
+ "description": "Form with unsaved changes triggering cancel confirmation.",
284
+ "args": {
285
+ "isLoading": false,
286
+ "isSubmitting": false,
287
+ "error": null,
288
+ "initialData": {
289
+ "workoutDate": "2024-10-15",
290
+ "workoutType": "Strength",
291
+ "duration": 60,
292
+ "notes": "Changed notes"
293
+ },
294
+ "unsavedChanges": true,
295
+ "onSubmit": "() => {}",
296
+ "onCancel": "() => {}",
297
+ "onRetry": "() => {}"
298
+ },
299
+ "needsPlayFunction": true,
300
+ "playDescription": "1. Click getByRole('button', { name: 'Cancel' }); 2. Verify AlertDialog opens with waitFor(() => getByRole('dialog')); 3. Click getByRole('button', { name: /cancel|close/i }) to dismiss confirmation; 4. Verify form unchanged expect(getByLabelText('Duration')).toHaveValue('60');"
301
+ }
302
+ ],
303
+ "dataContract": {
304
+ "source": "props",
305
+ "propsFieldName": "initialData",
306
+ "fields": [
307
+ "workoutDate",
308
+ "workoutType",
309
+ "duration",
310
+ "notes"
311
+ ]
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
317
+ ],
318
+ "failurePolicy": "skip-dependents",
319
+ "graphqlSchema": "input LogWorkoutExercisesInput {\n exerciseId: String!\n sets: Float!\n reps: Float!\n}\n\ninput LogWorkoutInput {\n workoutId: String\n date: String\n duration: Float\n exercises: [LogWorkoutExercisesInput]\n}\n\ninput AwardPointsInput {\n userId: String\n points: Float\n workoutId: String\n}\n\ntype WorkoutHistoryViewWorkouts {\n workoutId: String!\n date: String!\n duration: Float!\n totalSets: Float!\n}\n\ntype WorkoutHistoryView {\n userId: String!\n workouts: [WorkoutHistoryViewWorkouts]!\n}\n\ntype ProgressStatsView {\n userId: String!\n totalWorkouts: Float!\n avgDuration: Float!\n totalPoints: Float!\n weeklyTrend: Float!\n}\n\ninput UpdateLeaderboardInput {\n userId: String\n newRank: Float\n totalPoints: Float\n}\n\ntype ProgressDashboardView {\n userId: String!\n totalWorkouts: Float!\n avgExercisesPerWorkout: Float!\n totalVolume: Float!\n lastWorkoutDate: String!\n streakDays: Float!\n}\n\ntype MutationError {\n type: String!\n message: String\n}\n\ntype MutationResponse {\n success: Boolean!\n error: MutationError\n}\n\ntype Mutation {\n logWorkout(input: LogWorkoutInput!): MutationResponse!\n awardPoints(input: AwardPointsInput!): MutationResponse!\n updateLeaderboard(input: UpdateLeaderboardInput!): MutationResponse!\n}\n\ntype Query {\n workoutHistory(userId: ID!, startDate: String, endDate: String): [WorkoutHistoryView!]!\n progressStats(userId: ID!): [ProgressStatsView!]!\n progressMetrics(userId: ID!): [ProgressDashboardView!]!\n}"
320
+ }