cabloy 5.1.60 → 5.1.61

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 (76) hide show
  1. package/.claude/hooks/contract-loop-gate.ts +296 -0
  2. package/.claude/settings.json +16 -0
  3. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
  4. package/.claude/skills/cabloy-contract-loop/SKILL.md +89 -16
  5. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +102 -14
  6. package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +4 -0
  7. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +32 -14
  8. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -0
  9. package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
  10. package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
  11. package/.claude/skills/cabloy-resource-field-update/SKILL.md +7 -0
  12. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
  13. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
  14. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
  15. package/CHANGELOG.md +22 -0
  16. package/CLAUDE.md +10 -0
  17. package/cabloy-docs/.vitepress/config.mjs +50 -4
  18. package/cabloy-docs/ai/cli-to-skill-map.md +7 -0
  19. package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
  20. package/cabloy-docs/ai/future-skill-roadmap.md +10 -7
  21. package/cabloy-docs/ai/introduction.md +1 -0
  22. package/cabloy-docs/ai/playbook-backend-module.md +6 -0
  23. package/cabloy-docs/ai/playbook-module-removal.md +164 -0
  24. package/cabloy-docs/ai/skills.md +11 -0
  25. package/cabloy-docs/backend/dto-guide.md +6 -0
  26. package/cabloy-docs/backend/entity-guide.md +18 -0
  27. package/cabloy-docs/backend/introduction.md +2 -0
  28. package/cabloy-docs/backend/serialization-guide.md +10 -0
  29. package/cabloy-docs/backend/status-guide.md +271 -0
  30. package/cabloy-docs/frontend/api-guide.md +2 -0
  31. package/cabloy-docs/frontend/bean-scene-authoring.md +2 -0
  32. package/cabloy-docs/frontend/cli.md +12 -0
  33. package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
  34. package/cabloy-docs/frontend/design-principles.md +6 -0
  35. package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
  36. package/cabloy-docs/frontend/form-guide.md +795 -0
  37. package/cabloy-docs/frontend/foundation.md +29 -0
  38. package/cabloy-docs/frontend/introduction.md +12 -1
  39. package/cabloy-docs/frontend/ioc-and-beans.md +6 -0
  40. package/cabloy-docs/frontend/mock-guide.md +1 -0
  41. package/cabloy-docs/frontend/model-architecture.md +252 -39
  42. package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
  43. package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
  44. package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
  45. package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
  46. package/cabloy-docs/frontend/model-state-guide.md +366 -13
  47. package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
  48. package/cabloy-docs/frontend/page-guide.md +6 -0
  49. package/cabloy-docs/frontend/quickstart.md +4 -0
  50. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
  51. package/cabloy-docs/frontend/server-data.md +2 -0
  52. package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
  53. package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
  54. package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
  55. package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
  56. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
  57. package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
  58. package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
  59. package/cabloy-docs/fullstack/introduction.md +12 -1
  60. package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
  61. package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +2 -2
  62. package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +30 -5
  63. package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +9 -7
  64. package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +2 -0
  65. package/cabloy-docs/fullstack/tutorials-overview.md +16 -3
  66. package/cabloy-docs/reference/bean-scene-boilerplates.md +2 -0
  67. package/package.json +2 -1
  68. package/scripts/init.ts +2 -18
  69. package/scripts/initTestData.ts +25 -0
  70. package/scripts/upgrade.ts +17 -2
  71. package/vona/pnpm-lock.yaml +94 -4
  72. package/zova/packages-cli/cli/package.json +2 -2
  73. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
  74. package/zova/packages-cli/cli-set-front/package.json +1 -1
  75. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
  76. package/zova/pnpm-lock.yaml +20 -20
@@ -0,0 +1,795 @@
1
+ # Form Guide
2
+
3
+ This guide explains how to build forms in Zova with a practical, tutorial-style path.
4
+
5
+ Zova Form is not only a thin Vue wrapper around a form library. It is a Zova-native form layer built around:
6
+
7
+ - controller-oriented frontend architecture
8
+ - schema-driven field rendering
9
+ - Zod-friendly validation
10
+ - behavior-based field layout and customization
11
+ - provider-based render selection
12
+
13
+ Use this page together with:
14
+
15
+ - [Component Guide](/frontend/component-guide)
16
+ - [Behavior Guide](/frontend/behavior-guide)
17
+ - [API Schema Guide](/frontend/api-schema-guide)
18
+ - [Zod Guide](/frontend/zod-guide)
19
+ - [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
20
+ - [Zova Form Under the Hood](/frontend/zova-form-under-the-hood)
21
+ - [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map)
22
+
23
+ > [!TIP]
24
+ > **Zova Form docs path**
25
+ > 1. **[Form Guide](/frontend/form-guide)** — learn the public authoring surface
26
+ > 2. **[Zova Form Under the Hood](/frontend/zova-form-under-the-hood)** — learn how the runtime pieces cooperate
27
+ > 3. **[Zova Form Source Reading Map](/frontend/zova-form-source-reading-map)** — learn which files to read next
28
+ >
29
+ > **You are here:** step 1.
30
+ > **Next recommended page:** [Zova Form Under the Hood](/frontend/zova-form-under-the-hood).
31
+
32
+ If your next question is how these public APIs cooperate internally at runtime, continue with [Zova Form Under the Hood](/frontend/zova-form-under-the-hood).
33
+
34
+ ## What you should learn first
35
+
36
+ If you only remember one idea, remember this one:
37
+
38
+ > In Zova, a form is centered on a form controller and field controllers, while schema, provider config, and behaviors decide how much of the UI should be automatic and how much should be customized.
39
+
40
+ That leads to four common authoring surfaces:
41
+
42
+ - `ZForm` — the root form component
43
+ - `ZFormFieldPreset` — the quickest way to render a standard field
44
+ - `ZFormField` — the low-level custom field entry
45
+ - `ZFormFieldBlank` — a free layout row for buttons and other non-field content
46
+
47
+ ## One running example through this guide: Student
48
+
49
+ To keep the guide concrete, the examples below all use the same teaching resource:
50
+
51
+ - resource: `demo-student:student`
52
+ - representative fields: `name`, `level`, and `mobile`
53
+ - common scenes: Student create, Student edit, and Student view
54
+
55
+ That Student thread is a good specimen because it grows through the same path most real business forms follow:
56
+
57
+ 1. generated CRUD structure
58
+ 2. schema-driven form rendering
59
+ 3. metadata-driven field rendering
60
+ 4. custom field rendering only where the business UI needs it
61
+
62
+ So as you read the code samples below, treat them as different stages of the same Student form rather than unrelated fragments.
63
+
64
+ ## Step 1: Choose the right form style
65
+
66
+ Before writing code, choose which of these three styles matches your Student page.
67
+
68
+ ### Style A: schema-driven form
69
+
70
+ Use this when:
71
+
72
+ - the backend Student contract already describes the form well
73
+ - you want labels, defaults, readonly behavior, and render metadata to come from schema-driven truth
74
+ - you want the shortest path to a working Student create/edit/view page
75
+
76
+ ### Style B: manual form
77
+
78
+ Use this when:
79
+
80
+ - one Student field needs very custom UI behavior
81
+ - the page has unusual interaction or layout rules
82
+ - you want to wire one field directly through a field slot
83
+
84
+ ### Style C: mixed form
85
+
86
+ Use this when:
87
+
88
+ - most Student fields can use preset or schema-driven rendering
89
+ - one or two Student fields need a custom renderer
90
+ - you need action rows, helper rows, or page-specific layout blocks
91
+
92
+ A practical rule is:
93
+
94
+ - start with **schema-driven** if the Student contract is already strong
95
+ - use **mixed** for most business forms
96
+ - drop to **manual** only where the UI really needs it
97
+
98
+ ## Step 2: Start with the smallest useful Student form
99
+
100
+ The smallest useful Zova form is usually a mixed Student form with `ZForm`, a couple of fields, and one submit row.
101
+
102
+ ```tsx
103
+ <ZForm data={this.studentFormData} onSubmitData={data => this.submitStudent(data)}>
104
+ <ZFormFieldPreset
105
+ name="name"
106
+ layout={{ label: 'Student Name:' }}
107
+ ></ZFormFieldPreset>
108
+
109
+ <ZFormFieldPreset
110
+ name="level"
111
+ render="basic-select:formFieldSelect"
112
+ options={{ items: this.levelItems }}
113
+ layout={{ label: 'Level:' }}
114
+ ></ZFormFieldPreset>
115
+
116
+ <ZFormFieldBlank
117
+ slotDefault={$$form => {
118
+ return (
119
+ <button disabled={$$form.formState.isSubmitting} type="submit" class="btn btn-primary">
120
+ Save Student
121
+ </button>
122
+ );
123
+ }}
124
+ ></ZFormFieldBlank>
125
+ </ZForm>
126
+ ```
127
+
128
+ What this gives you immediately:
129
+
130
+ - form state ownership through `ZForm`
131
+ - standard Student fields through `ZFormFieldPreset`
132
+ - submit state through `$$form.formState.isSubmitting`
133
+ - a Zova-native Student form flow without hand-building local state wiring
134
+
135
+ ## Step 3: Understand the field components
136
+
137
+ The three field entries are intentionally different.
138
+
139
+ ### `ZFormFieldPreset`
140
+
141
+ Use `ZFormFieldPreset` when you want the standard form pipeline with a reusable preset renderer.
142
+
143
+ Typical Student cases:
144
+
145
+ - `name` as a standard input
146
+ - `level` as a select field
147
+ - `mobile` as a standard input or masked input
148
+ - any provider-defined standard Student field component
149
+
150
+ Representative Student field:
151
+
152
+ ```tsx
153
+ <ZFormFieldPreset
154
+ name="level"
155
+ render="basic-select:formFieldSelect"
156
+ options={{
157
+ items: this.levelItems,
158
+ placeholder: 'Choose a level',
159
+ }}
160
+ layout={{ label: 'Level:' }}
161
+ ></ZFormFieldPreset>
162
+ ```
163
+
164
+ ### `ZFormField`
165
+
166
+ Use `ZFormField` when you want direct render control.
167
+
168
+ Representative Student field:
169
+
170
+ ```tsx
171
+ <ZFormField
172
+ name="mobile"
173
+ slotDefault={({ propsBucket, props }, $$formField) => {
174
+ return (
175
+ <input
176
+ {...props}
177
+ class="input"
178
+ value={propsBucket.value}
179
+ placeholder="Student mobile"
180
+ onInput={(e: Event) => {
181
+ $$formField.setValue((e.target as HTMLInputElement).value);
182
+ }}
183
+ onBlur={() => {
184
+ $$formField.handleBlur();
185
+ }}
186
+ ></input>
187
+ );
188
+ }}
189
+ ></ZFormField>
190
+ ```
191
+
192
+ Use this when you need to:
193
+
194
+ - render one Student field manually
195
+ - integrate a custom Student UI component
196
+ - intercept input and blur behavior yourself
197
+ - mix Zova form state with a custom render contract
198
+
199
+ ### `ZFormFieldBlank`
200
+
201
+ Use `ZFormFieldBlank` when the row is **not** a real data field.
202
+
203
+ Typical Student cases:
204
+
205
+ - save/cancel buttons
206
+ - helper text under the Student fields
207
+ - page-specific action rows
208
+ - custom layout blocks between field groups
209
+
210
+ Representative shape:
211
+
212
+ ```tsx
213
+ <ZFormFieldBlank
214
+ slotDefault={$$form => {
215
+ return (
216
+ <div class="flex gap-2">
217
+ <button disabled={$$form.formState.isSubmitting} type="submit" class="btn btn-primary">
218
+ Save Student
219
+ </button>
220
+ <button type="button" class="btn btn-default" onClick={() => this.cancelEdit()}>
221
+ Cancel
222
+ </button>
223
+ </div>
224
+ );
225
+ }}
226
+ ></ZFormFieldBlank>
227
+ ```
228
+
229
+ A simple memory aid is:
230
+
231
+ - `ZFormFieldPreset` = convention-first Student field
232
+ - `ZFormField` = custom Student field render
233
+ - `ZFormFieldBlank` = free row inside the Student form
234
+
235
+ ## Step 4: Add submit behavior
236
+
237
+ The most common submit hook is `onSubmitData`.
238
+
239
+ ```tsx
240
+ <ZForm
241
+ data={this.studentFormData}
242
+ onSubmitData={data => {
243
+ return this.submitStudent(data);
244
+ }}
245
+ ></ZForm>
246
+ ```
247
+
248
+ `onSubmitData` receives submission data plus form API context.
249
+
250
+ In practice, that means you can:
251
+
252
+ - read `data.value` as the submitted Student form data
253
+ - use `data.formApi` when you need lower-level form access
254
+ - pass typed submit metadata if your workflow uses it
255
+
256
+ ### Show loading state
257
+
258
+ A common pattern is to read loading state from `formState`.
259
+
260
+ ```tsx
261
+ slotFooter={$$form => {
262
+ return (
263
+ <div>
264
+ {$$form.formState.isSubmitting && (
265
+ <span class="loading loading-spinner text-primary"></span>
266
+ )}
267
+ <button class="btn btn-primary">Save Student</button>
268
+ </div>
269
+ );
270
+ }}
271
+ ```
272
+
273
+ ### Show errors
274
+
275
+ Use `onShowError` when you want a centralized user-facing error handler.
276
+
277
+ ```tsx
278
+ <ZForm
279
+ onShowError={({ error }) => {
280
+ window.alert(error.message)
281
+ }}
282
+ ></ZForm>
283
+ ```
284
+
285
+ That is useful when the default Student field-level error state is not enough and the page also wants a broader error message.
286
+
287
+ ## Step 5: Move to schema-driven Student rendering when possible
288
+
289
+ When `ZForm` receives a `schema` and you do **not** provide a default body slot for the form, Zova can render fields automatically from the schema properties.
290
+
291
+ ```tsx
292
+ <ZForm
293
+ data={this.studentFormData}
294
+ schema={this.studentFormSchema}
295
+ formMeta={this.studentFormMeta}
296
+ onSubmitData={data => this.submitStudent(data)}
297
+ ></ZForm>
298
+ ```
299
+
300
+ This is usually the best next step after a manual Student prototype because it lets the backend Student contract drive more of the UI.
301
+
302
+ Schema-driven rendering is a strong fit when:
303
+
304
+ - the Student schema already carries field metadata
305
+ - frontend and backend should stay close to the same Student contract truth
306
+ - you want to reduce duplicated Student field configuration
307
+
308
+ Read together with [API Schema Guide](/frontend/api-schema-guide).
309
+
310
+ ## Step 6: Add validation
311
+
312
+ Zova Form is built on top of TanStack Form and integrates naturally with Zod.
313
+
314
+ ### Use schema-driven validation as the default
315
+
316
+ When the Student form has a Zod schema, field validation can often be derived from the field schema directly.
317
+
318
+ That is the best default when:
319
+
320
+ - the backend Student contract already defines the validation truth
321
+ - the frontend should stay close to the same validation semantics
322
+
323
+ ### Add field-level validation when needed
324
+
325
+ You can also provide validators directly on a field.
326
+
327
+ ```tsx
328
+ <ZFormFieldPreset
329
+ name="name"
330
+ validators={{ onDynamic: z.string().min(3) }}
331
+ ></ZFormFieldPreset>
332
+ ```
333
+
334
+ Or, for a custom Student field:
335
+
336
+ ```tsx
337
+ <ZFormField
338
+ name="mobile"
339
+ validators={{ onBlur: z.string().min(6) }}
340
+ ></ZFormField>
341
+ ```
342
+
343
+ The common validator hooks are:
344
+
345
+ - `onDynamic`
346
+ - `onBlur`
347
+ - `onChange`
348
+
349
+ A practical rule is:
350
+
351
+ - use schema-derived validation for the default Student contract-driven case
352
+ - add field-level validators when one Student field needs an explicit frontend rule
353
+
354
+ ### Handle invalid submit
355
+
356
+ Useful form-level hooks include:
357
+
358
+ - `onSubmitInvalid`
359
+ - `onShowError`
360
+
361
+ Zova Form also normalizes server validation failures back into the form pipeline. In practice, backend validation responses such as Student field-oriented 422 errors can flow back into form and field error state instead of being treated as unrelated generic exceptions.
362
+
363
+ ## Step 7: Choose the form scene with `formMeta`
364
+
365
+ `formMeta` is the main scene-level mode input for the form.
366
+
367
+ ```tsx
368
+ this.studentFormMeta = {
369
+ formMode: 'edit',
370
+ editMode: 'update',
371
+ }
372
+ ```
373
+
374
+ A practical way to read it is:
375
+
376
+ - `formMode: 'view'` -> Student view form
377
+ - `formMode: 'edit'` + `editMode: 'create'` -> Student create form
378
+ - `formMode: 'edit'` + `editMode: 'update'` -> Student edit form
379
+
380
+ This matters because the form pipeline can derive behavior such as readonly field state from the form scene.
381
+
382
+ If the Student form is in view mode, field rendering can become readonly automatically without every field re-implementing that rule manually.
383
+
384
+ ## Step 8: Customize layout with `layout`, `options`, and `formProvider`
385
+
386
+ A Zova form is not driven by one source only.
387
+
388
+ In practice, Student field rendering can be influenced by:
389
+
390
+ - schema metadata
391
+ - field props
392
+ - form-level layout defaults
393
+ - provider defaults
394
+ - field-specific options
395
+
396
+ ### Use `layout` for label and wrapper concerns
397
+
398
+ ```tsx
399
+ <ZFormFieldPreset
400
+ name="level"
401
+ layout={{ label: 'Student Level:' }}
402
+ ></ZFormFieldPreset>
403
+ ```
404
+
405
+ Use `layout` for concerns such as:
406
+
407
+ - label text
408
+ - icon prefix/suffix
409
+ - field wrapper presentation
410
+ - row-level layout intent
411
+
412
+ ### Use `options` for renderer-specific input props
413
+
414
+ ```tsx
415
+ <ZFormFieldPreset
416
+ name="level"
417
+ render="basic-select:formFieldSelect"
418
+ options={{
419
+ items: this.levelItems,
420
+ placeholder: 'Choose a level',
421
+ }}
422
+ ></ZFormFieldPreset>
423
+ ```
424
+
425
+ Use `options` when the concrete Student field renderer needs its own input options.
426
+
427
+ ### Use `formProvider` for page-level behavior and render decisions
428
+
429
+ `formProvider` is the main provider-level customization surface.
430
+
431
+ ```tsx
432
+ <ZForm
433
+ formProvider={{ behaviors: { FormFieldLayout: 'demo-student:formFieldLayoutStudent' } }}
434
+ >
435
+ ```
436
+
437
+ This is useful for:
438
+
439
+ - provider-defined Student field components
440
+ - provider-defined layout behavior
441
+ - page-level Student form customization without rewriting every field
442
+
443
+ Read together with [Behavior Guide](/frontend/behavior-guide).
444
+
445
+ ## Step 9: Decide whether the root should be a real `<form>`
446
+
447
+ By default, `ZForm` uses `formTag="form"`.
448
+
449
+ That means:
450
+
451
+ - the root element is a native `<form>`
452
+ - normal submit behavior is available
453
+ - Zova wires the submit event into the form controller
454
+
455
+ If you need the Student form pipeline but do **not** want a native `<form>` root, switch the wrapper tag.
456
+
457
+ ```tsx
458
+ <ZForm
459
+ formTag="div"
460
+ data={this.studentFormData}
461
+ schema={this.studentFormSchema}
462
+ onSubmitData={data => this.submitStudent(data)}
463
+ ></ZForm>
464
+ ```
465
+
466
+ Use this when the Student form is embedded inside a larger block system or another page-level orchestration layer.
467
+
468
+ ## Step 10: Use `controllerRef` when the page needs the form instance
469
+
470
+ Like other Zova components, the preferred instance reference is the **controller instance**, not a generic DOM ref.
471
+
472
+ ```tsx
473
+ <ZForm
474
+ controllerRef={ref => {
475
+ this.studentFormRef = ref;
476
+ }}
477
+ ></ZForm>
478
+ ```
479
+
480
+ This is useful when you need to:
481
+
482
+ - submit the Student form programmatically
483
+ - inspect `formState`
484
+ - integrate the Student form into a larger page-entry or action system
485
+
486
+ Example:
487
+
488
+ ```tsx
489
+ <button
490
+ onClick={async () => {
491
+ await this.studentFormRef.submit();
492
+ }}
493
+ >
494
+ Save Student
495
+ </button>
496
+ ```
497
+
498
+ ## Step 11: Know when to use the advanced base classes
499
+
500
+ The module also exposes controller base classes for cases where the controller itself should participate more directly in form ownership.
501
+
502
+ The main exported bases are:
503
+
504
+ - `BeanControllerFormBase`
505
+ - `BeanControllerPageFormBase`
506
+
507
+ Use these when:
508
+
509
+ - a reusable Student-related component controller wants typed form access
510
+ - a Student page controller wants form-specific helpers while staying in the normal Zova page-controller model
511
+ - you want to keep Student page logic and form lifecycle close together
512
+
513
+ This is an advanced surface. For most page and component authoring, start with `ZForm` and the field components first.
514
+
515
+ ## Step 12: Study one complete CRUD form pattern with the Student resource
516
+
517
+ When you move from a demo form to a real business page, the best source specimen in this repository is the **Student** teaching thread used across the fullstack tutorials.
518
+
519
+ That Student thread is useful because it grows from:
520
+
521
+ - generated CRUD structure
522
+ - schema-driven create/edit/view forms
523
+ - metadata-driven field rendering
524
+ - custom field rendering when the business UI needs it
525
+
526
+ Use the Student resource when you want a concrete mental model for how `ZForm` fits into Cabloy’s larger contract loop.
527
+
528
+ ### The practical Student CRUD story
529
+
530
+ A standard Student entry form usually follows this business path:
531
+
532
+ 1. the backend Student resource defines the contract truth
533
+ 2. the frontend resource model loads schema and provider metadata for Student
534
+ 3. the page decides whether the Student form is `create`, `edit`, or `view`
535
+ 4. `ZForm` renders from Student form schema and Student form data
536
+ 5. submit delegates back to the Student mutation owned by the model
537
+
538
+ So the page is **not** the place that should invent schema lookup rules or mutation policy.
539
+
540
+ ### Controller-side shape for a Student entry page
541
+
542
+ A representative Student-oriented controller shape looks like this:
543
+
544
+ ```ts
545
+ protected async __init__() {
546
+ this.$$modelResource = await this.bean._getBeanSelector(
547
+ 'rest-resource.model.resource',
548
+ true,
549
+ 'demo-student:student',
550
+ );
551
+
552
+ this.studentFormMeta = this.$computed(() => {
553
+ const formScene = this.entryId ? 'edit' : 'create';
554
+ return { ...formMetaFromFormScene(formScene), formScene };
555
+ });
556
+
557
+ this.studentFormProvider = this.$computed(() => {
558
+ return this.$$modelResource.formProvider;
559
+ });
560
+
561
+ this.studentFormSchema = this.$computed(() => {
562
+ return this.$$modelResource.getFormSchema(this.studentFormMeta);
563
+ });
564
+
565
+ this.studentFormData = this.$computed(() => {
566
+ return this.$$modelResource.getFormData(this.studentFormMeta, this.entryId);
567
+ });
568
+ }
569
+
570
+ async submitStudent(data: TypeFormOnSubmitData<StudentFormData>) {
571
+ const mutationSubmit = this.$$modelResource.getFormMutationSubmit(this.studentFormMeta, this.entryId);
572
+ await mutationSubmit?.mutateAsync(data.value);
573
+ }
574
+ ```
575
+
576
+ Read that example as four ownership boundaries:
577
+
578
+ - `demo-student:student` identifies the business resource
579
+ - `ModelResource` owns resource-level Student form schema/data/provider lookup
580
+ - `studentFormMeta` decides whether the Student page is create/edit/view
581
+ - `submitStudent` delegates to the Student mutation policy instead of inventing a page-local submit rule
582
+
583
+ ### Render-side shape for a Student entry page
584
+
585
+ Once the Student model side is prepared, the render side can stay thin:
586
+
587
+ ```tsx
588
+ <ZForm
589
+ formTag="div"
590
+ data={this.studentFormData}
591
+ schema={this.studentFormSchema}
592
+ formMeta={this.studentFormMeta}
593
+ formProvider={this.studentFormProvider}
594
+ onSubmitData={data => this.submitStudent(data)}
595
+ onShowError={({ error }) => {
596
+ window.alert(error.message)
597
+ }}
598
+ ></ZForm>
599
+ ```
600
+
601
+ This is the important design lesson:
602
+
603
+ - the Student page renders the form
604
+ - the Student model owns form resource semantics
605
+ - the backend Student contract still remains the source of truth for schema-driven behavior
606
+
607
+ ### How the Student example evolves over time
608
+
609
+ The Student tutorials show a useful growth path:
610
+
611
+ 1. start with generated CRUD
612
+ 2. let schema drive the Student create/edit/view surfaces
613
+ 3. reuse built-in field renderers for fields like `level`
614
+ 4. only add custom Student field renderers when the business UI really needs them
615
+
616
+ That is the same growth strategy you should usually follow in your own modules.
617
+
618
+ ### Why this is the recommended CRUD architecture
619
+
620
+ This Student pattern is recommended because:
621
+
622
+ - the page does **not** own schema lookup rules
623
+ - the page does **not** invent its own submit mutation policy
624
+ - `ModelResource` owns resource-level form metadata and mutation selection
625
+ - `ZForm` stays focused on rendering, validation, and submission flow
626
+ - Student-specific UI can deepen later without breaking the contract-first structure
627
+
628
+ This is also why Cabloy can provide complete CRUD-style form pages with a strong schema-driven surface instead of requiring every page to hand-build form wiring from scratch.
629
+
630
+ Read together with:
631
+
632
+ - [Tutorial 2: Create Your First CRUD](/fullstack/tutorial-2-first-crud)
633
+ - [Tutorial 3: Frontend Metadata Sharing](/fullstack/tutorial-3-frontend-metadata-sharing)
634
+ - [Tutorial 4: Custom Form/Table Renderers for Level](/fullstack/tutorial-4-custom-level-renderers)
635
+ - [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
636
+
637
+ ## Quick comparison: schema-driven vs manual vs mixed
638
+
639
+ Use this table when you are unsure which style to choose.
640
+
641
+ | Style | Best fit | Strengths | Trade-offs |
642
+ | --- | --- | --- | --- |
643
+ | Schema-driven | resource CRUD pages, contract-first forms, metadata-rich pages | least duplication, easiest to keep aligned with backend truth, fastest way to scale many forms | less suitable when one page has unusual UI structure |
644
+ | Manual | highly custom UI, one-off interaction-heavy forms | maximum render control | easiest way to drift away from contract truth and duplicate field wiring |
645
+ | Mixed | most business pages | keeps standard fields cheap while leaving room for custom rows or custom renderers | requires discipline about which layer should own each customization |
646
+
647
+ A practical recommendation is:
648
+
649
+ - use **schema-driven** for standard Student-style resource create/edit/view pages
650
+ - use **mixed** for most real business forms
651
+ - use **manual** only when the page truly needs renderer-level control
652
+
653
+ ## Relationship map 1: business ownership from resource to form
654
+
655
+ Use this diagram when the question is:
656
+
657
+ - who owns the business scene?
658
+ - where do schema and data come from?
659
+ - how does a resource page reach `ZForm`?
660
+
661
+ ```text
662
+ Backend resource contract (example: Student)
663
+ ├─ field definitions
664
+ ├─ validation truth
665
+ └─ frontend render metadata
666
+
667
+
668
+ ModelResource / page controller
669
+ ├─ provides formMeta
670
+ ├─ provides formSchema
671
+ ├─ provides formData
672
+ └─ provides formProvider
673
+
674
+
675
+ ZForm
676
+ ├─ owns form controller
677
+ ├─ owns form state / submit / reset
678
+ ├─ resolves schema properties
679
+ └─ creates field render context
680
+ ```
681
+
682
+ Read this top-down:
683
+
684
+ - the **backend resource** still defines the business contract
685
+ - the **resource model/page** translates that contract into page-ready form inputs
686
+ - `ZForm` owns the frontend form runtime
687
+
688
+ This is the diagram to use when you are debugging a CRUD form page at the business or architecture level.
689
+
690
+ ## Relationship map 2: field rendering pipeline inside `ZForm`
691
+
692
+ Use this diagram when the question is:
693
+
694
+ - why did this field render that way?
695
+ - where did the final component choice come from?
696
+ - should I change schema metadata, field props, provider config, or behaviors?
697
+
698
+ ```text
699
+ ZForm
700
+ └─ field render context
701
+
702
+ ├───────────────┬───────────────────┐
703
+ ▼ ▼ ▼
704
+ ZFormFieldPreset ZFormField ZFormFieldBlank
705
+ │ │ │
706
+ │ │ └─ free row for buttons / helper content
707
+ │ │
708
+ │ └─ manual slot render
709
+ │ + setValue / handleBlur
710
+
711
+ └─ preset-driven standard field
712
+
713
+ Field rendering pipeline
714
+ schema/rest + field props + layout + provider config
715
+
716
+
717
+ formProvider.components
718
+
719
+
720
+ behaviors
721
+
722
+
723
+ final rendered field
724
+ ```
725
+
726
+ Read this top-down too:
727
+
728
+ - field components choose how much rendering is automatic vs custom
729
+ - schema metadata and field props shape the initial field contract
730
+ - `formProvider.components` decides the concrete renderer surface
731
+ - behaviors wrap or refine the final UI without changing the higher-level form contract
732
+
733
+ This is the diagram to use when you are debugging one field instead of the whole resource page.
734
+
735
+ ## Recommended learning path
736
+
737
+ If you are new to Zova Form, use this order:
738
+
739
+ 1. build one small mixed Student form with `ZForm`, `ZFormFieldPreset`, and `ZFormFieldBlank`
740
+ 2. add `onSubmitData` and loading/error handling
741
+ 3. move one Student page to schema-driven rendering
742
+ 4. add field-level or schema-level validation
743
+ 5. introduce `formMeta` for Student `view`, `create`, and `edit`
744
+ 6. introduce `formProvider` only when page-level layout or render behavior needs customization
745
+ 7. study one Student resource CRUD form and trace how `ModelResource` feeds `ZForm`
746
+ 8. continue with [Zova Form Under the Hood](/frontend/zova-form-under-the-hood) when you want the runtime explanation behind the public authoring surface
747
+ 9. continue with [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map) when you need framework-level source details and targeted file-order guidance
748
+
749
+ That path usually gives the fastest route from first usage to a maintainable business form.
750
+
751
+ ## Common mistakes to avoid
752
+
753
+ ### Mistake 1: starting with manual fields for everything
754
+
755
+ If the backend Student schema already carries the form truth, start schema-driven or mixed instead of manually wiring every field.
756
+
757
+ ### Mistake 2: treating `ZFormFieldBlank` like a normal field
758
+
759
+ Use it for action rows and free layout content, not for real data ownership.
760
+
761
+ ### Mistake 3: putting layout policy into every field manually
762
+
763
+ If the concern is page-wide or provider-wide, prefer `formProvider` and behavior-based customization.
764
+
765
+ ### Mistake 4: forgetting `formMeta`
766
+
767
+ If the Student page has clear `view`, `create`, or `edit` semantics, encode that through `formMeta` so readonly and edit behavior stay consistent.
768
+
769
+ ### Mistake 5: reaching for advanced base classes too early
770
+
771
+ Most Student form pages should start with `ZForm` and field components. Use `BeanControllerFormBase` or `BeanControllerPageFormBase` only when the controller-level ownership is genuinely the clearer architecture.
772
+
773
+ ## Edition note
774
+
775
+ This guide describes the shared Zova Form architecture.
776
+
777
+ That architecture applies across Cabloy Basic and Cabloy Start. However, visual styling, preset field components, and provider-level UI details can still differ once the task becomes UI-library-specific.
778
+
779
+ For Cabloy Basic, public examples in this repository currently align with DaisyUI + Tailwind CSS.
780
+
781
+ ## Verification checklist
782
+
783
+ When documenting or changing Zova Form usage, verify in this order:
784
+
785
+ 1. confirm whether the form should be schema-driven, manual, or mixed
786
+ 2. confirm whether `formMeta` should be `view`, `create`, or `edit`
787
+ 3. confirm whether validation truth should come from schema, field-level validators, or both
788
+ 4. confirm whether layout customization belongs in field props or provider behaviors
789
+ 5. build the docs site:
790
+
791
+ ```bash
792
+ npm run docs:build
793
+ ```
794
+
795
+ 6. verify the page is reachable from the frontend sidebar and the frontend introduction page