@massu/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +40 -0
  2. package/agents/massu-architecture-reviewer.md +104 -0
  3. package/agents/massu-blast-radius-analyzer.md +84 -0
  4. package/agents/massu-competitive-scorer.md +126 -0
  5. package/agents/massu-help-sync.md +73 -0
  6. package/agents/massu-migration-writer.md +94 -0
  7. package/agents/massu-output-scorer.md +87 -0
  8. package/agents/massu-pattern-reviewer.md +84 -0
  9. package/agents/massu-plan-auditor.md +170 -0
  10. package/agents/massu-schema-sync-verifier.md +70 -0
  11. package/agents/massu-security-reviewer.md +98 -0
  12. package/agents/massu-ux-reviewer.md +106 -0
  13. package/commands/_shared-preamble.md +53 -23
  14. package/commands/_shared-references/auto-learning-protocol.md +71 -0
  15. package/commands/_shared-references/blast-radius-protocol.md +76 -0
  16. package/commands/_shared-references/security-pre-screen.md +64 -0
  17. package/commands/_shared-references/test-first-protocol.md +87 -0
  18. package/commands/_shared-references/verification-table.md +52 -0
  19. package/commands/massu-article-review.md +343 -0
  20. package/commands/massu-autoresearch/references/eval-runner.md +84 -0
  21. package/commands/massu-autoresearch/references/safety-rails.md +125 -0
  22. package/commands/massu-autoresearch/references/scoring-protocol.md +151 -0
  23. package/commands/massu-autoresearch.md +258 -0
  24. package/commands/massu-batch.md +44 -12
  25. package/commands/massu-bearings.md +42 -8
  26. package/commands/massu-checkpoint.md +588 -0
  27. package/commands/massu-ci-fix.md +2 -2
  28. package/commands/massu-command-health.md +132 -0
  29. package/commands/massu-command-improve.md +232 -0
  30. package/commands/massu-commit.md +205 -44
  31. package/commands/massu-create-plan.md +239 -57
  32. package/commands/massu-data/references/common-queries.md +79 -0
  33. package/commands/massu-data/references/table-guide.md +50 -0
  34. package/commands/massu-data.md +66 -0
  35. package/commands/massu-dead-code.md +29 -34
  36. package/commands/massu-debug/references/auto-learning.md +61 -0
  37. package/commands/massu-debug/references/codegraph-tracing.md +80 -0
  38. package/commands/massu-debug/references/common-shortcuts.md +98 -0
  39. package/commands/massu-debug/references/investigation-phases.md +294 -0
  40. package/commands/massu-debug/references/report-format.md +107 -0
  41. package/commands/massu-debug.md +105 -386
  42. package/commands/massu-docs.md +1 -1
  43. package/commands/massu-full-audit.md +61 -0
  44. package/commands/massu-gap-enhancement-analyzer.md +276 -16
  45. package/commands/massu-golden-path/references/approval-points.md +216 -0
  46. package/commands/massu-golden-path/references/competitive-mode.md +273 -0
  47. package/commands/massu-golden-path/references/error-handling.md +121 -0
  48. package/commands/massu-golden-path/references/phase-0-requirements.md +53 -0
  49. package/commands/massu-golden-path/references/phase-1-plan-creation.md +168 -0
  50. package/commands/massu-golden-path/references/phase-2-implementation.md +397 -0
  51. package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +156 -0
  52. package/commands/massu-golden-path/references/phase-3-simplify.md +40 -0
  53. package/commands/massu-golden-path/references/phase-4-commit.md +94 -0
  54. package/commands/massu-golden-path/references/phase-5-push.md +116 -0
  55. package/commands/massu-golden-path/references/phase-5.5-production-verify.md +170 -0
  56. package/commands/massu-golden-path/references/phase-6-completion.md +113 -0
  57. package/commands/massu-golden-path/references/qa-evaluator-spec.md +137 -0
  58. package/commands/massu-golden-path/references/sprint-contract-protocol.md +117 -0
  59. package/commands/massu-golden-path/references/vr-visual-calibration.md +73 -0
  60. package/commands/massu-golden-path.md +114 -848
  61. package/commands/massu-guide.md +72 -69
  62. package/commands/massu-hooks.md +27 -12
  63. package/commands/massu-hotfix.md +221 -144
  64. package/commands/massu-incident.md +49 -20
  65. package/commands/massu-infra-audit.md +187 -0
  66. package/commands/massu-learning-audit.md +211 -0
  67. package/commands/massu-loop/references/auto-learning.md +49 -0
  68. package/commands/massu-loop/references/checkpoint-audit.md +40 -0
  69. package/commands/massu-loop/references/guardrails.md +17 -0
  70. package/commands/massu-loop/references/iteration-structure.md +115 -0
  71. package/commands/massu-loop/references/loop-controller.md +188 -0
  72. package/commands/massu-loop/references/plan-extraction.md +78 -0
  73. package/commands/massu-loop/references/vr-plan-spec.md +140 -0
  74. package/commands/massu-loop-playwright.md +9 -9
  75. package/commands/massu-loop.md +115 -670
  76. package/commands/massu-new-pattern.md +423 -0
  77. package/commands/massu-perf.md +422 -0
  78. package/commands/massu-plan-audit.md +1 -1
  79. package/commands/massu-plan.md +389 -122
  80. package/commands/massu-production-verify.md +433 -0
  81. package/commands/massu-push.md +62 -378
  82. package/commands/massu-recap.md +29 -3
  83. package/commands/massu-rollback.md +613 -0
  84. package/commands/massu-scaffold-hook.md +2 -4
  85. package/commands/massu-scaffold-page.md +2 -3
  86. package/commands/massu-scaffold-router.md +1 -2
  87. package/commands/massu-security.md +619 -0
  88. package/commands/massu-simplify.md +115 -85
  89. package/commands/massu-squirrels.md +2 -2
  90. package/commands/massu-tdd.md +38 -22
  91. package/commands/massu-test.md +3 -3
  92. package/commands/massu-type-mismatch-audit.md +469 -0
  93. package/commands/massu-ui-audit.md +587 -0
  94. package/commands/massu-verify-playwright.md +287 -32
  95. package/commands/massu-verify.md +150 -46
  96. package/dist/cli.js +146 -95
  97. package/package.json +6 -2
  98. package/patterns/build-patterns.md +302 -0
  99. package/patterns/component-patterns.md +246 -0
  100. package/patterns/display-patterns.md +185 -0
  101. package/patterns/form-patterns.md +890 -0
  102. package/patterns/integration-testing-checklist.md +445 -0
  103. package/patterns/security-patterns.md +219 -0
  104. package/patterns/testing-patterns.md +569 -0
  105. package/patterns/tool-routing.md +81 -0
  106. package/patterns/ui-patterns.md +371 -0
  107. package/protocols/plan-implementation.md +267 -0
  108. package/protocols/recovery.md +225 -0
  109. package/protocols/verification.md +404 -0
  110. package/reference/command-taxonomy.md +178 -0
  111. package/reference/cr-rules-reference.md +76 -0
  112. package/reference/hook-execution-order.md +148 -0
  113. package/reference/lessons-learned.md +175 -0
  114. package/reference/patterns-quickref.md +208 -0
  115. package/reference/standards.md +135 -0
  116. package/reference/subagents-reference.md +17 -0
  117. package/reference/vr-verification-reference.md +867 -0
  118. package/src/commands/install-commands.ts +149 -53
@@ -0,0 +1,890 @@
1
+ # Form Field Patterns
2
+
3
+ **Purpose**: Form patterns for semantic TextField types, PhoneInputField, AddressForm, react-hook-form + Zod setup, register() vs Controller, edit forms, submission patterns.
4
+
5
+ **When to Read**: Before creating or modifying forms.
6
+
7
+ ---
8
+
9
+ ## Sections
10
+ | Section | Line | Description |
11
+ |---------|------|-------------|
12
+ | Overview | ~15 | Purpose, consistency goals, accessibility compliance |
13
+ | Semantic TextField Usage | ~25 | Mandatory TextField semantic types table |
14
+ | Phone Input Pattern | ~117 | PhoneInputField for international phone numbers |
15
+ | Complete Form Example | ~192 | Full form example combining all patterns |
16
+ | Field-Specific Patterns | ~304 | Per-field guidance: money, percent, date, URL |
17
+ | Address Forms | ~430 | AddressForm component usage |
18
+ | Data Storage Standards | ~453 | How to normalize and store form data |
19
+ | Verification Checklist | ~468 | Pre-submit checklist for form implementations |
20
+ | React Hook Form + Zod Standard Patterns | ~483 | Setup, register(), Controller, reset(), watch(), submission |
21
+ | Migration Checklist | ~850 | Steps to migrate legacy forms to standard patterns |
22
+ | Related Documentation | ~871 | Links to related pattern docs |
23
+
24
+ ## Overview
25
+
26
+ This document establishes the standard patterns for form fields across your application. Following these patterns ensures:
27
+ - Consistent UX across all forms
28
+ - Proper data normalization and validation
29
+ - Mobile-optimized keyboard experiences
30
+ - Accessibility compliance
31
+
32
+ ---
33
+
34
+ ## Semantic TextField Usage
35
+
36
+ ### [x] MANDATORY - Use TextField with Semantic Types
37
+
38
+ **Always use TextField from `@/components/common/form/TextField.tsx` with semantic types for:**
39
+
40
+ | Field Type | Component | Use For |
41
+ |------------|-----------|---------|
42
+ | First Name | `TextField type="firstName"` | Person first names |
43
+ | Last Name | `TextField type="lastName"` | Person last names |
44
+ | Email | `TextField type="email"` | Email addresses |
45
+ | **Phone** | **`PhoneInputField`** | Phone numbers (international) |
46
+ | Company | `TextField type="company"` | Company/organization names |
47
+ | URL | `TextField type="url"` | Website URLs |
48
+ | Money | `TextField type="money"` | Currency amounts |
49
+ | Percent | `TextField type="percent"` | Percentage values |
50
+ | Date | `TextField type="date"` | Date values |
51
+
52
+ > **Note**: Phone fields use `PhoneInputField` (not TextField) for international support. See [Phone Input Pattern](#phone-input-pattern) below.
53
+
54
+ ### [X] WRONG - Raw Input for Semantic Data
55
+
56
+ ```tsx
57
+ // [X] DO NOT USE - Raw Input without semantic handling
58
+ <Input
59
+ type="text"
60
+ name="first_name"
61
+ placeholder="First name"
62
+ />
63
+
64
+ <Input
65
+ type="email"
66
+ name="email"
67
+ placeholder="Email"
68
+ />
69
+
70
+ <Input
71
+ type="tel"
72
+ name="phone"
73
+ placeholder="Phone"
74
+ />
75
+ ```
76
+
77
+ **This is WRONG because:**
78
+ - No auto-formatting or normalization
79
+ - No mobile keyboard optimization
80
+ - No inline validation or success states
81
+ - Inconsistent UX across forms
82
+
83
+ ### [x] CORRECT - Semantic TextField
84
+
85
+ ```tsx
86
+ // [x] CORRECT - Use TextField with semantic types
87
+ import { TextField } from '@/components/common/form/TextField';
88
+
89
+ <TextField
90
+ type="firstName"
91
+ name="first_name"
92
+ label="First Name"
93
+ required
94
+ register={register}
95
+ errors={errors}
96
+ setValue={setValue}
97
+ watch={watch}
98
+ showSuccess
99
+ />
100
+
101
+ <TextField
102
+ type="email"
103
+ name="email"
104
+ label="Email Address"
105
+ required
106
+ register={register}
107
+ errors={errors}
108
+ setValue={setValue}
109
+ watch={watch}
110
+ showSuccess
111
+ />
112
+
113
+ <PhoneInputField
114
+ name="phone"
115
+ label="Phone Number"
116
+ defaultCountry="US"
117
+ errors={errors}
118
+ setValue={setValue}
119
+ watch={watch}
120
+ showSuccess
121
+ />
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Phone Input Pattern
127
+
128
+ ### [x] MANDATORY - Use PhoneInputField for Phone Numbers
129
+
130
+ International phone support with country code selection.
131
+
132
+ | Aspect | Value |
133
+ |--------|-------|
134
+ | Component | `PhoneInputField` |
135
+ | Location | `@/components/common/form/PhoneInputField.tsx` |
136
+ | Purpose | International phone input with country selector |
137
+ | Storage Format | E.164 (`+12025551234`) |
138
+
139
+ ### [X] WRONG - TextField type="phone" (Deprecated)
140
+
141
+ ```tsx
142
+ // [X] DEPRECATED - US/Canada only, no international support
143
+ <TextField
144
+ type="phone"
145
+ name="phone"
146
+ label="Phone Number"
147
+ icon={Phone}
148
+ formatAsYouType
149
+ register={register}
150
+ errors={errors}
151
+ setValue={setValue}
152
+ watch={watch}
153
+ />
154
+ ```
155
+
156
+ **This is WRONG because:**
157
+ - Only supports US/Canada phone formatting
158
+ - No country code selection
159
+ - Cannot handle international customers
160
+ - Not E.164 compatible for Twilio
161
+
162
+ ### [x] CORRECT - PhoneInputField
163
+
164
+ ```tsx
165
+ // [x] CORRECT - Full international support
166
+ import { PhoneInputField } from '@/components/common/form/PhoneInputField';
167
+
168
+ <PhoneInputField
169
+ name="phone"
170
+ label="Phone Number"
171
+ defaultCountry="US"
172
+ errors={errors}
173
+ setValue={setValue}
174
+ watch={watch}
175
+ showSuccess
176
+ />
177
+ ```
178
+
179
+ **Benefits:**
180
+ - Country selector with flags and dial codes
181
+ - Auto-formatting per selected country
182
+ - Stores in E.164 format for Twilio compatibility
183
+ - Uses libphonenumber-js for accurate validation
184
+
185
+ ### PhoneInputField Props
186
+
187
+ | Prop | Type | Required | Description |
188
+ |------|------|----------|-------------|
189
+ | `name` | string | Yes | Field name for react-hook-form |
190
+ | `label` | string | Yes | Label text |
191
+ | `defaultCountry` | CountryCode | No | Default country (default: 'US') |
192
+ | `errors` | FieldErrors | Yes | react-hook-form errors |
193
+ | `setValue` | UseFormSetValue | Yes | react-hook-form setValue |
194
+ | `watch` | UseFormWatch | Yes | react-hook-form watch |
195
+ | `showSuccess` | boolean | No | Show green check when valid |
196
+ | `required` | boolean | No | Mark field as required |
197
+ | `disabled` | boolean | No | Disable the field |
198
+
199
+ ---
200
+
201
+ ## Complete Form Example
202
+
203
+ ### Standard Contact Form Pattern
204
+
205
+ ```tsx
206
+ 'use client';
207
+
208
+ import { useForm } from 'react-hook-form';
209
+ import { zodResolver } from '@hookform/resolvers/zod';
210
+ import { z } from 'zod';
211
+ import { TextField } from '@/components/common/form/TextField';
212
+ import { PhoneInputField } from '@/components/common/form/PhoneInputField';
213
+ import { Button } from '@/components/ui/button';
214
+
215
+ const contactSchema = z.object({
216
+ first_name: z.string().min(1, 'First name is required'),
217
+ last_name: z.string().optional(),
218
+ email: z.string().email('Valid email required'),
219
+ phone: z.string().optional(),
220
+ company: z.string().optional(),
221
+ website: z.string().url().optional().or(z.literal('')),
222
+ });
223
+
224
+ type ContactFormData = z.infer<typeof contactSchema>;
225
+
226
+ export function ContactForm({ onSubmit }: { onSubmit: (data: ContactFormData) => void }) {
227
+ const {
228
+ register,
229
+ handleSubmit,
230
+ setValue,
231
+ watch,
232
+ formState: { errors },
233
+ } = useForm<ContactFormData>({
234
+ resolver: zodResolver(contactSchema),
235
+ });
236
+
237
+ return (
238
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
239
+ <div className="grid grid-cols-2 gap-4">
240
+ <TextField
241
+ type="firstName"
242
+ name="first_name"
243
+ label="First Name"
244
+ required
245
+ register={register}
246
+ errors={errors}
247
+ setValue={setValue}
248
+ watch={watch}
249
+ showSuccess
250
+ />
251
+ <TextField
252
+ type="lastName"
253
+ name="last_name"
254
+ label="Last Name"
255
+ register={register}
256
+ errors={errors}
257
+ setValue={setValue}
258
+ watch={watch}
259
+ />
260
+ </div>
261
+
262
+ <TextField
263
+ type="email"
264
+ name="email"
265
+ label="Email Address"
266
+ required
267
+ register={register}
268
+ errors={errors}
269
+ setValue={setValue}
270
+ watch={watch}
271
+ showSuccess
272
+ />
273
+
274
+ <PhoneInputField
275
+ name="phone"
276
+ label="Phone Number"
277
+ defaultCountry="US"
278
+ errors={errors}
279
+ setValue={setValue}
280
+ watch={watch}
281
+ showSuccess
282
+ />
283
+
284
+ <TextField
285
+ type="company"
286
+ name="company"
287
+ label="Company"
288
+ register={register}
289
+ errors={errors}
290
+ setValue={setValue}
291
+ watch={watch}
292
+ />
293
+
294
+ <TextField
295
+ type="url"
296
+ name="website"
297
+ label="Website"
298
+ placeholder="https://example.com"
299
+ register={register}
300
+ errors={errors}
301
+ setValue={setValue}
302
+ watch={watch}
303
+ />
304
+
305
+ <Button type="submit">Save Contact</Button>
306
+ </form>
307
+ );
308
+ }
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Field-Specific Patterns
314
+
315
+ ### Phone Number Fields
316
+
317
+ ```tsx
318
+ // International phone with country selector
319
+ import { PhoneInputField } from '@/components/common/form/PhoneInputField';
320
+
321
+ <PhoneInputField
322
+ name="phone"
323
+ label="Phone Number"
324
+ defaultCountry="US" // Default country (supports all countries)
325
+ showSuccess // Shows checkmark when valid
326
+ errors={errors}
327
+ setValue={setValue}
328
+ watch={watch}
329
+ />
330
+ ```
331
+
332
+ **Behavior:**
333
+ - Country selector dropdown with flags and dial codes
334
+ - Auto-formats per selected country as user types
335
+ - Stores in E.164 format (`+15551234567`) for Twilio compatibility
336
+ - Shows validation checkmark when valid
337
+ - Uses libphonenumber-js for accurate validation
338
+
339
+ ### Email Fields
340
+
341
+ ```tsx
342
+ // Email with validation feedback
343
+ <TextField
344
+ type="email"
345
+ name="email"
346
+ label="Email Address"
347
+ required
348
+ showSuccess
349
+ register={register}
350
+ errors={errors}
351
+ setValue={setValue}
352
+ watch={watch}
353
+ />
354
+ ```
355
+
356
+ **Behavior:**
357
+ - `inputMode="email"` for mobile email keyboard
358
+ - `autoCapitalize="none"` prevents unwanted caps
359
+ - Normalizes to lowercase and trimmed on blur
360
+ - Validates RFC 5322 pattern
361
+
362
+ ### Name Fields
363
+
364
+ ```tsx
365
+ // First name with auto-capitalize
366
+ <TextField
367
+ type="firstName"
368
+ name="first_name"
369
+ label="First Name"
370
+ required
371
+ register={register}
372
+ errors={errors}
373
+ setValue={setValue}
374
+ watch={watch}
375
+ />
376
+
377
+ // Last name with smart capitalization
378
+ <TextField
379
+ type="lastName"
380
+ name="last_name"
381
+ label="Last Name"
382
+ register={register}
383
+ errors={errors}
384
+ setValue={setValue}
385
+ watch={watch}
386
+ />
387
+ ```
388
+
389
+ **Behavior:**
390
+ - `autoCapitalize="words"` for mobile
391
+ - Smart capitalization handles: McDonald, O'Brien, de la Cruz
392
+ - Normalizes on blur
393
+
394
+ ### Money Fields
395
+
396
+ ```tsx
397
+ // Currency input with prefix
398
+ <TextField
399
+ type="money"
400
+ name="price"
401
+ label="Price"
402
+ prefix="$"
403
+ register={register}
404
+ errors={errors}
405
+ setValue={setValue}
406
+ watch={watch}
407
+ />
408
+ ```
409
+
410
+ **Behavior:**
411
+ - `inputMode="decimal"` for numeric keyboard with decimal
412
+ - Displays `$` prefix
413
+ - Accepts: `1234.56`, `1,234.56`, `$1,234.56`
414
+ - Stores as decimal number
415
+
416
+ ### URL Fields
417
+
418
+ ```tsx
419
+ // URL with protocol normalization
420
+ <TextField
421
+ type="url"
422
+ name="website"
423
+ label="Website"
424
+ placeholder="https://example.com"
425
+ register={register}
426
+ errors={errors}
427
+ setValue={setValue}
428
+ watch={watch}
429
+ />
430
+ ```
431
+
432
+ **Behavior:**
433
+ - `inputMode="url"` for URL keyboard
434
+ - Auto-adds `https://` if missing on blur
435
+ - Validates URL structure
436
+
437
+ ---
438
+
439
+ ## Address Forms
440
+
441
+ ### [x] CORRECT - Use AddressForm Component
442
+
443
+ ```tsx
444
+ import { AddressForm, type AddressFormData } from '@/components/crm/AddressForm';
445
+
446
+ // In your form
447
+ <AddressForm
448
+ data={addressData}
449
+ onChange={setAddressData}
450
+ enableAutoDetect // ZIP auto-fills city/state for US/Canada
451
+ />
452
+ ```
453
+
454
+ **Baymard UX Pattern:**
455
+ - Country field FIRST (determines ZIP format)
456
+ - ZIP code field BEFORE City/State
457
+ - Auto-detection fills City/State from ZIP
458
+ - Reduces typos and improves completion speed
459
+
460
+ ---
461
+
462
+ ## Data Storage Standards
463
+
464
+ | Field Type | Storage Format | Example |
465
+ |------------|---------------|---------|
466
+ | Phone | E.164 | `+15551234567` |
467
+ | Email | Lowercase, trimmed | `john@example.com` |
468
+ | First Name | Title case | `John` |
469
+ | Last Name | Smart title case | `McDonald` |
470
+ | Company | Trimmed | `Acme Corp` |
471
+ | URL | Full with protocol | `https://example.com` |
472
+ | Money | Decimal | `1234.56` |
473
+ | Percent | Decimal fraction | `0.0825` (for 8.25%) |
474
+
475
+ ---
476
+
477
+ ## Verification Checklist
478
+
479
+ When creating or updating forms:
480
+ - [ ] All name fields use `type="firstName"` or `type="lastName"`
481
+ - [ ] All email fields use `type="email"`
482
+ - [ ] All phone fields use `PhoneInputField` (NOT TextField type="phone")
483
+ - [ ] All company fields use `type="company"`
484
+ - [ ] All URL fields use `type="url"`
485
+ - [ ] All money fields use `type="money"` with appropriate prefix
486
+ - [ ] `setValue` and `watch` props passed for normalization
487
+ - [ ] `showSuccess` enabled for required fields
488
+ - [ ] Address forms use `AddressForm` component
489
+
490
+ ---
491
+
492
+ ## React Hook Form + Zod Standard Patterns
493
+
494
+ All forms MUST use react-hook-form with Zod validation.
495
+
496
+ ### [x] MANDATORY - Form Setup Pattern
497
+
498
+ ```tsx
499
+ 'use client';
500
+
501
+ import { useForm, Controller } from 'react-hook-form';
502
+ import { zodResolver } from '@hookform/resolvers/zod';
503
+ import { z } from 'zod';
504
+
505
+ // 1. Define Zod schema with proper validation
506
+ const myFormSchema = z.object({
507
+ name: z.string().min(1, 'Name is required'),
508
+ email: z.string().min(1, 'Email is required').email('Please enter a valid email'),
509
+ description: z.string(), // Optional field (no .min())
510
+ status: z.enum(['draft', 'active', 'archived']),
511
+ is_active: z.boolean(),
512
+ });
513
+
514
+ // 2. Infer TypeScript type from schema
515
+ type MyFormData = z.infer<typeof myFormSchema>;
516
+
517
+ // 3. Setup useForm with zodResolver
518
+ const {
519
+ register,
520
+ handleSubmit,
521
+ control, // For Controller components
522
+ reset, // For populating edit forms
523
+ watch, // For reactive values in UI
524
+ formState: { errors, isSubmitting },
525
+ } = useForm<MyFormData>({
526
+ resolver: zodResolver(myFormSchema),
527
+ defaultValues: {
528
+ name: '',
529
+ email: '',
530
+ description: '',
531
+ status: 'draft',
532
+ is_active: true,
533
+ },
534
+ });
535
+ ```
536
+
537
+ ### [X] WRONG - Legacy useState/formData Pattern
538
+
539
+ ```tsx
540
+ // [X] DO NOT USE - Legacy pattern with individual or object useState
541
+ const [formData, setFormData] = useState({
542
+ name: '',
543
+ email: '',
544
+ });
545
+ const [isSubmitting, setIsSubmitting] = useState(false);
546
+
547
+ // Manual field updates
548
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
549
+ setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }));
550
+ };
551
+
552
+ // Manual submit handling
553
+ const handleSubmit = async () => {
554
+ setIsSubmitting(true);
555
+ // ... validation, submission
556
+ setIsSubmitting(false);
557
+ };
558
+ ```
559
+
560
+ **This is WRONG because:**
561
+ - No built-in validation or error handling
562
+ - Manual state synchronization
563
+ - Verbose boilerplate code
564
+ - No TypeScript inference from schema
565
+
566
+ ### Native Input Elements - Use register()
567
+
568
+ For Input, Textarea, and other native form elements:
569
+
570
+ ```tsx
571
+ // Input with register()
572
+ <div className="space-y-2">
573
+ <Label htmlFor="name">Name *</Label>
574
+ <Input
575
+ id="name"
576
+ {...register('name')}
577
+ placeholder="Enter name"
578
+ className={errors.name ? 'border-destructive' : ''}
579
+ />
580
+ {errors.name && (
581
+ <p className="text-xs text-destructive">{errors.name.message}</p>
582
+ )}
583
+ </div>
584
+
585
+ // Textarea with register()
586
+ <Textarea
587
+ id="description"
588
+ {...register('description')}
589
+ placeholder="Enter description..."
590
+ rows={3}
591
+ />
592
+ ```
593
+
594
+ ### Non-Native Elements - Use Controller
595
+
596
+ For Select, Switch, Checkbox, and custom components:
597
+
598
+ ```tsx
599
+ // Select with Controller
600
+ <Controller
601
+ name="status"
602
+ control={control}
603
+ render={({ field }) => (
604
+ <Select value={field.value} onValueChange={field.onChange}>
605
+ <SelectTrigger>
606
+ <SelectValue placeholder="Select status" />
607
+ </SelectTrigger>
608
+ <SelectContent>
609
+ <SelectItem value="draft">Draft</SelectItem>
610
+ <SelectItem value="active">Active</SelectItem>
611
+ <SelectItem value="archived">Archived</SelectItem>
612
+ </SelectContent>
613
+ </Select>
614
+ )}
615
+ />
616
+
617
+ // Switch with Controller
618
+ <Controller
619
+ name="is_active"
620
+ control={control}
621
+ render={({ field }) => (
622
+ <Switch
623
+ id="is_active"
624
+ checked={field.value}
625
+ onCheckedChange={field.onChange}
626
+ />
627
+ )}
628
+ />
629
+
630
+ // Checkbox with Controller
631
+ <Controller
632
+ name="agree_terms"
633
+ control={control}
634
+ render={({ field }) => (
635
+ <Checkbox
636
+ checked={field.value}
637
+ onCheckedChange={field.onChange}
638
+ />
639
+ )}
640
+ />
641
+ ```
642
+
643
+ ### Edit Forms - Use reset() in useEffect
644
+
645
+ For forms that load existing data:
646
+
647
+ ```tsx
648
+ // Fetch existing data
649
+ const { data: item, isLoading } = api.items.getById.useQuery(
650
+ { id },
651
+ { enabled: !!id }
652
+ );
653
+
654
+ // Populate form when data loads
655
+ useEffect(() => {
656
+ if (item) {
657
+ reset({
658
+ name: item.name || '',
659
+ email: item.email || '',
660
+ description: item.description || '',
661
+ status: (item.status as MyFormData['status']) || 'draft',
662
+ is_active: item.is_active ?? true,
663
+ });
664
+ }
665
+ }, [item, reset]);
666
+ ```
667
+
668
+ ### Reactive Values - Use watch()
669
+
670
+ For displaying form values in UI or validation:
671
+
672
+ ```tsx
673
+ // Watch specific field for UI updates
674
+ const name = watch('name');
675
+
676
+ // Use in conditional rendering or validation
677
+ const isFormValid = name.trim() && email.trim();
678
+
679
+ // In submit button
680
+ <Button type="submit" disabled={!isFormValid || isSubmitting}>
681
+ {isSubmitting ? 'Saving...' : 'Save'}
682
+ </Button>
683
+ ```
684
+
685
+ ### UI-Only State - Keep Separate
686
+
687
+ For transient UI state like tag/capability inputs:
688
+
689
+ ```tsx
690
+ // Tags that are added via Enter key - keep as separate useState
691
+ const [tags, setTags] = useState<string[]>([]);
692
+ const [tagInput, setTagInput] = useState('');
693
+
694
+ const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
695
+ if (e.key === 'Enter' && tagInput.trim()) {
696
+ e.preventDefault();
697
+ if (!tags.includes(tagInput.trim())) {
698
+ setTags([...tags, tagInput.trim()]);
699
+ }
700
+ setTagInput('');
701
+ }
702
+ };
703
+
704
+ // Note: Tags are NOT part of the Zod schema - they're managed separately
705
+ // Include tags in the mutation call, not the form data
706
+ ```
707
+
708
+ ### Form Submission Pattern
709
+
710
+ ```tsx
711
+ // Name the handler 'onSubmit' to avoid conflict with handleSubmit from useForm
712
+ const onSubmit = (data: MyFormData) => {
713
+ mutation.mutate({
714
+ name: data.name.trim(),
715
+ email: data.email.trim(),
716
+ description: data.description.trim() || null,
717
+ status: data.status,
718
+ is_active: data.is_active,
719
+ });
720
+ };
721
+
722
+ // In JSX - wrap with handleSubmit from useForm
723
+ <form onSubmit={handleSubmit(onSubmit)}>
724
+ {/* form fields */}
725
+ </form>
726
+ ```
727
+
728
+ ### Complete Edit Form Example
729
+
730
+ ```tsx
731
+ 'use client';
732
+
733
+ import { use, useState, useEffect, Suspense } from 'react';
734
+ import { useForm, Controller } from 'react-hook-form';
735
+ import { zodResolver } from '@hookform/resolvers/zod';
736
+ import { z } from 'zod';
737
+ import { api } from '@/lib/api/client';
738
+
739
+ const templateSchema = z.object({
740
+ name: z.string().min(1, 'Name is required'),
741
+ description: z.string(),
742
+ template_type: z.enum(['email', 'sms', 'notification']),
743
+ is_active: z.boolean(),
744
+ });
745
+
746
+ type TemplateFormData = z.infer<typeof templateSchema>;
747
+
748
+ function TemplateEditPage({ params }: { params: Promise<{ id: string }> }) {
749
+ const { id } = use(params);
750
+ const [isEditing, setIsEditing] = useState(false);
751
+
752
+ const {
753
+ register,
754
+ handleSubmit,
755
+ control,
756
+ reset,
757
+ watch,
758
+ formState: { errors, isSubmitting },
759
+ } = useForm<TemplateFormData>({
760
+ resolver: zodResolver(templateSchema),
761
+ defaultValues: {
762
+ name: '',
763
+ description: '',
764
+ template_type: 'email',
765
+ is_active: true,
766
+ },
767
+ });
768
+
769
+ const name = watch('name');
770
+
771
+ const { data: template, isLoading, refetch } = api.templates.getById.useQuery(
772
+ { id },
773
+ { enabled: !!id }
774
+ );
775
+
776
+ const updateMutation = api.templates.update.useMutation({
777
+ onSuccess: () => {
778
+ setIsEditing(false);
779
+ refetch();
780
+ },
781
+ });
782
+
783
+ useEffect(() => {
784
+ if (template) {
785
+ reset({
786
+ name: template.name || '',
787
+ description: template.description || '',
788
+ template_type: (template.template_type as TemplateFormData['template_type']) || 'email',
789
+ is_active: template.is_active ?? true,
790
+ });
791
+ }
792
+ }, [template, reset]);
793
+
794
+ const onSubmit = (data: TemplateFormData) => {
795
+ updateMutation.mutate({
796
+ id,
797
+ data: {
798
+ name: data.name,
799
+ description: data.description || null,
800
+ template_type: data.template_type,
801
+ is_active: data.is_active,
802
+ },
803
+ });
804
+ };
805
+
806
+ const handleCancel = () => {
807
+ if (template) {
808
+ reset({
809
+ name: template.name || '',
810
+ description: template.description || '',
811
+ template_type: (template.template_type as TemplateFormData['template_type']) || 'email',
812
+ is_active: template.is_active ?? true,
813
+ });
814
+ }
815
+ setIsEditing(false);
816
+ };
817
+
818
+ return (
819
+ <form onSubmit={handleSubmit(onSubmit)}>
820
+ <Input {...register('name')} className={errors.name ? 'border-destructive' : ''} />
821
+ {errors.name && <p className="text-xs text-destructive">{errors.name.message}</p>}
822
+
823
+ <Textarea {...register('description')} />
824
+
825
+ <Controller
826
+ name="template_type"
827
+ control={control}
828
+ render={({ field }) => (
829
+ <Select value={field.value} onValueChange={field.onChange}>
830
+ <SelectTrigger><SelectValue /></SelectTrigger>
831
+ <SelectContent>
832
+ <SelectItem value="email">Email</SelectItem>
833
+ <SelectItem value="sms">SMS</SelectItem>
834
+ <SelectItem value="notification">Notification</SelectItem>
835
+ </SelectContent>
836
+ </Select>
837
+ )}
838
+ />
839
+
840
+ <Controller
841
+ name="is_active"
842
+ control={control}
843
+ render={({ field }) => (
844
+ <Switch checked={field.value} onCheckedChange={field.onChange} />
845
+ )}
846
+ />
847
+
848
+ <Button type="button" variant="outline" onClick={handleCancel}>Cancel</Button>
849
+ <Button type="submit" disabled={!name || isSubmitting}>
850
+ {isSubmitting ? 'Saving...' : 'Save'}
851
+ </Button>
852
+ </form>
853
+ );
854
+ }
855
+ ```
856
+
857
+ ---
858
+
859
+ ## Migration Checklist
860
+
861
+ When migrating forms from useState/formData to react-hook-form:
862
+
863
+ - [ ] Import useForm, Controller, zodResolver, z
864
+ - [ ] Create Zod schema with all form fields
865
+ - [ ] Create TypeScript type with z.infer
866
+ - [ ] Replace useState with useForm setup
867
+ - [ ] Set defaultValues for all fields
868
+ - [ ] Replace manual setFormData with register() for native inputs
869
+ - [ ] Use Controller for Select, Switch, Checkbox
870
+ - [ ] Rename handleSubmit to onSubmit (avoid conflict)
871
+ - [ ] Wrap form with handleSubmit(onSubmit)
872
+ - [ ] Replace setIsSubmitting with isSubmitting from formState
873
+ - [ ] For edit forms: use reset() in useEffect
874
+ - [ ] For reactive values: use watch()
875
+ - [ ] Keep UI-only state (tags, etc.) as separate useState
876
+ - [ ] Add error display with errors.fieldName.message
877
+
878
+ ---
879
+
880
+ ## Related Documentation
881
+
882
+ - **Component**: `src/components/common/form/TextField.tsx`
883
+ - **Formatting Functions**: `src/lib/formatting/fields.ts`
884
+ - **Display Patterns**: `patterns/display-patterns.md`
885
+ - **UI Patterns**: `patterns/ui-patterns.md`
886
+
887
+ ---
888
+
889
+ **Status**: MANDATORY
890
+ **Compliance**: All forms MUST follow these patterns