@ng-forge/dynamic-form-mcp 1.2.0-next.7 → 1.2.0-next.9

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/src/index.js CHANGED
@@ -22,7 +22,7 @@ var FIELD_TYPES = [
22
22
  },
23
23
  validationSupported: true,
24
24
  source: "adapter",
25
- allowedIn: ["top-level", "page", "row", "group", "array"],
25
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
26
26
  example: `{
27
27
  key: 'email',
28
28
  type: 'input',
@@ -57,7 +57,7 @@ var FIELD_TYPES = [
57
57
  },
58
58
  validationSupported: true,
59
59
  source: "adapter",
60
- allowedIn: ["top-level", "page", "row", "group", "array"],
60
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
61
61
  example: `{
62
62
  key: 'description',
63
63
  type: 'textarea',
@@ -91,7 +91,7 @@ var FIELD_TYPES = [
91
91
  },
92
92
  validationSupported: true,
93
93
  source: "adapter",
94
- allowedIn: ["top-level", "page", "row", "group", "array"],
94
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
95
95
  example: `{
96
96
  key: 'country',
97
97
  type: 'select',
@@ -113,7 +113,7 @@ var FIELD_TYPES = [
113
113
  props: {},
114
114
  validationSupported: true,
115
115
  source: "adapter",
116
- allowedIn: ["top-level", "page", "row", "group", "array"],
116
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
117
117
  example: `{
118
118
  key: 'acceptTerms',
119
119
  type: 'checkbox',
@@ -138,7 +138,7 @@ var FIELD_TYPES = [
138
138
  },
139
139
  validationSupported: true,
140
140
  source: "adapter",
141
- allowedIn: ["top-level", "page", "row", "group", "array"],
141
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
142
142
  example: `{
143
143
  key: 'interests',
144
144
  type: 'multi-checkbox',
@@ -168,7 +168,7 @@ var FIELD_TYPES = [
168
168
  },
169
169
  validationSupported: true,
170
170
  source: "adapter",
171
- allowedIn: ["top-level", "page", "row", "group", "array"],
171
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
172
172
  example: `{
173
173
  key: 'gender',
174
174
  type: 'radio',
@@ -203,7 +203,7 @@ var FIELD_TYPES = [
203
203
  },
204
204
  validationSupported: true,
205
205
  source: "adapter",
206
- allowedIn: ["top-level", "page", "row", "group", "array"],
206
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
207
207
  example: `{
208
208
  key: 'birthDate',
209
209
  type: 'datepicker',
@@ -222,7 +222,7 @@ var FIELD_TYPES = [
222
222
  props: {},
223
223
  validationSupported: true,
224
224
  source: "adapter",
225
- allowedIn: ["top-level", "page", "row", "group", "array"],
225
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
226
226
  example: `{
227
227
  key: 'notifications',
228
228
  type: 'toggle',
@@ -261,7 +261,7 @@ var FIELD_TYPES = [
261
261
  },
262
262
  validationSupported: true,
263
263
  source: "adapter",
264
- allowedIn: ["top-level", "page", "row", "group", "array"],
264
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
265
265
  example: `{
266
266
  key: 'volume',
267
267
  type: 'slider',
@@ -282,7 +282,7 @@ var FIELD_TYPES = [
282
282
  props: {},
283
283
  validationSupported: false,
284
284
  source: "core",
285
- allowedIn: ["top-level (single-page forms only)", "page.fields", "group.fields", "array.fields"],
285
+ allowedIn: ["top-level (single-page forms only)", "page.fields", "group.fields", "array.fields", "container"],
286
286
  notAllowedIn: ["row", "top-level when using pages"],
287
287
  example: `// Hidden field - value is REQUIRED!
288
288
  {
@@ -319,7 +319,7 @@ var FIELD_TYPES = [
319
319
  },
320
320
  validationSupported: false,
321
321
  source: "core",
322
- allowedIn: ["top-level", "page", "row", "group", "array"],
322
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
323
323
  example: `{
324
324
  key: 'sectionTitle',
325
325
  type: 'text',
@@ -422,11 +422,12 @@ var FIELD_TYPES = [
422
422
  },
423
423
  validationSupported: true,
424
424
  source: "core",
425
- allowedIn: ["top-level", "page", "row", "array"],
425
+ allowedIn: ["top-level", "page", "row", "array", "container"],
426
426
  notAllowedIn: ["group"],
427
427
  canContain: [
428
428
  "row",
429
429
  "array",
430
+ "container",
430
431
  "input",
431
432
  "textarea",
432
433
  "select",
@@ -520,11 +521,12 @@ var FIELD_TYPES = [
520
521
  },
521
522
  validationSupported: true,
522
523
  source: "core",
523
- allowedIn: ["top-level", "page", "row", "group"],
524
+ allowedIn: ["top-level", "page", "row", "group", "container"],
524
525
  notAllowedIn: ["array"],
525
526
  canContain: [
526
527
  "row",
527
528
  "group",
529
+ "container",
528
530
  "input",
529
531
  "textarea",
530
532
  "select",
@@ -586,6 +588,74 @@ var FIELD_TYPES = [
586
588
  // ? { kind: 'periodOrder' } : null`,
587
589
  minimalExample: `{ key: 'items', type: 'array', fields: [...] }`
588
590
  },
591
+ {
592
+ type: "container",
593
+ category: "container",
594
+ description: "Wraps its children in UI chrome via a chain of wrapper components. Containers do NOT have a label property and do NOT create a nesting level in form values \u2014 child values are flattened into the parent, exactly like rows. The 'wrappers' property is REQUIRED (use [] for no chrome). A container is NOT a group with chrome and the two are not substitutable: a container flattens child values into the parent (like a row), while a group nests them under its own key and owns a schema path, so swapping one for the other changes the submitted value shape and which validators can run. Wrappers are applied outermost-first, so the first entry in the array is the outermost element. Supports only 'hidden' logic type for conditional visibility. Like `row` and `page`, a container flattens into its parent and has no schema path of its own, so it does NOT support container-level `validators`, `required` or `validationMessages` \u2014 use `group` or `array` for cross-field rules.",
595
+ valueType: void 0,
596
+ baseInterface: "FieldDef",
597
+ props: {
598
+ fields: {
599
+ name: "fields",
600
+ type: "ContainerAllowedChildren[]",
601
+ description: "Child fields to render inside the wrapper chain. Allowed: rows, groups, arrays, nested containers, hidden fields, and value fields (input, select, etc). NOT ALLOWED: pages",
602
+ required: true
603
+ },
604
+ wrappers: {
605
+ name: "wrappers",
606
+ type: "WrapperConfig[]",
607
+ description: "REQUIRED. Wrapper components chained around the children, outermost first. Use [] for no chrome. Each entry needs a 'type'; the built-in wrapper is 'css', which takes 'cssClasses'. Custom wrapper types can be registered.",
608
+ required: true
609
+ }
610
+ },
611
+ validationSupported: false,
612
+ source: "core",
613
+ allowedIn: ["top-level", "page", "group", "array", "row", "container"],
614
+ notAllowedIn: [],
615
+ canContain: [
616
+ "row",
617
+ "group",
618
+ "array",
619
+ "container",
620
+ "hidden",
621
+ "input",
622
+ "textarea",
623
+ "select",
624
+ "checkbox",
625
+ "multi-checkbox",
626
+ "radio",
627
+ "datepicker",
628
+ "toggle",
629
+ "slider",
630
+ "text",
631
+ "button",
632
+ "submit",
633
+ "next",
634
+ "previous",
635
+ "add-array-item",
636
+ "prepend-array-item",
637
+ "insert-array-item",
638
+ "remove-array-item",
639
+ "pop-array-item",
640
+ "shift-array-item"
641
+ ],
642
+ cannotContain: ["page"],
643
+ example: `{
644
+ key: 'billingChrome',
645
+ type: 'container',
646
+ // NOTE: Containers do NOT have a label property
647
+ // wrappers is REQUIRED \u2014 use [] when no chrome is needed
648
+ wrappers: [{ type: 'css', cssClasses: 'card p-3' }],
649
+ fields: [
650
+ { key: 'street', type: 'input', label: 'Street' },
651
+ { key: 'city', type: 'input', label: 'City' }
652
+ ]
653
+ }
654
+ // Values flatten: { street: '', city: '' } \u2014 no 'billingChrome' key
655
+ // NOT ALLOWED in containers:
656
+ // - type: 'page' (pages are top-level only)`,
657
+ minimalExample: `{ key: 'chrome', type: 'container', wrappers: [], fields: [...] }`
658
+ },
589
659
  {
590
660
  type: "page",
591
661
  category: "container",
@@ -603,11 +673,12 @@ var FIELD_TYPES = [
603
673
  validationSupported: false,
604
674
  source: "core",
605
675
  allowedIn: ["top-level"],
606
- notAllowedIn: ["page", "row", "group", "array"],
676
+ notAllowedIn: ["page", "row", "group", "array", "container"],
607
677
  canContain: [
608
678
  "row",
609
679
  "group",
610
680
  "array",
681
+ "container",
611
682
  "input",
612
683
  "textarea",
613
684
  "select",
@@ -647,7 +718,7 @@ var FIELD_TYPES = [
647
718
  props: {},
648
719
  validationSupported: false,
649
720
  source: "adapter",
650
- allowedIn: ["top-level", "page", "row", "group", "array"],
721
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
651
722
  example: `{
652
723
  key: 'next',
653
724
  type: 'next',
@@ -664,7 +735,7 @@ var FIELD_TYPES = [
664
735
  props: {},
665
736
  validationSupported: false,
666
737
  source: "adapter",
667
- allowedIn: ["top-level", "page", "row", "group", "array"],
738
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
668
739
  example: `{
669
740
  key: 'back',
670
741
  type: 'previous',
@@ -688,7 +759,7 @@ var FIELD_TYPES = [
688
759
  },
689
760
  validationSupported: false,
690
761
  source: "adapter",
691
- allowedIn: ["top-level", "page", "row", "group", "array"],
762
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
692
763
  example: `// RECOMMENDED: Use pre-defined button types for common actions
693
764
  { key: 'submit', type: 'submit', label: 'Submit Form' }
694
765
  { key: 'next', type: 'next', label: 'Next' }
@@ -715,7 +786,7 @@ var FIELD_TYPES = [
715
786
  props: {},
716
787
  validationSupported: false,
717
788
  source: "adapter",
718
- allowedIn: ["top-level", "page", "row", "group", "array"],
789
+ allowedIn: ["top-level", "page", "row", "group", "array", "container"],
719
790
  example: `{
720
791
  key: 'submit',
721
792
  type: 'submit',
@@ -732,7 +803,7 @@ var FIELD_TYPES = [
732
803
  props: {},
733
804
  validationSupported: false,
734
805
  source: "adapter",
735
- allowedIn: ["array", "row", "group"],
806
+ allowedIn: ["array", "row", "group", "container"],
736
807
  example: `{
737
808
  key: 'addContact',
738
809
  type: 'add-array-item',
@@ -749,7 +820,7 @@ var FIELD_TYPES = [
749
820
  props: {},
750
821
  validationSupported: false,
751
822
  source: "adapter",
752
- allowedIn: ["array", "row", "group"],
823
+ allowedIn: ["array", "row", "group", "container"],
753
824
  example: `{
754
825
  key: 'prependContact',
755
826
  type: 'prepend-array-item',
@@ -766,7 +837,7 @@ var FIELD_TYPES = [
766
837
  props: {},
767
838
  validationSupported: false,
768
839
  source: "adapter",
769
- allowedIn: ["array", "row", "group"],
840
+ allowedIn: ["array", "row", "group", "container"],
770
841
  example: `{
771
842
  key: 'insertContact',
772
843
  type: 'insert-array-item',
@@ -784,7 +855,7 @@ var FIELD_TYPES = [
784
855
  props: {},
785
856
  validationSupported: false,
786
857
  source: "adapter",
787
- allowedIn: ["array", "row", "group"],
858
+ allowedIn: ["array", "row", "group", "container"],
788
859
  example: `{
789
860
  key: 'removeContact',
790
861
  type: 'remove-array-item',
@@ -801,7 +872,7 @@ var FIELD_TYPES = [
801
872
  props: {},
802
873
  validationSupported: false,
803
874
  source: "adapter",
804
- allowedIn: ["array", "row", "group"],
875
+ allowedIn: ["array", "row", "group", "container"],
805
876
  example: `{
806
877
  key: 'popContact',
807
878
  type: 'pop-array-item',
@@ -819,7 +890,7 @@ var FIELD_TYPES = [
819
890
  props: {},
820
891
  validationSupported: false,
821
892
  source: "adapter",
822
- allowedIn: ["array", "row", "group"],
893
+ allowedIn: ["array", "row", "group", "container"],
823
894
  example: `{
824
895
  key: 'shiftContact',
825
896
  type: 'shift-array-item',
@@ -1949,7 +2020,7 @@ var ADDON_TYPES = [
1949
2020
  preset: {
1950
2021
  name: "preset",
1951
2022
  type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1952
- description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
2023
+ description: "Built-in preset action for Material input fields. Other Material addon-capable fields should use `actionRef` or `action`. Mutually exclusive with `actionRef` and `action`.",
1953
2024
  required: false
1954
2025
  },
1955
2026
  actionRef: {
@@ -2140,6 +2211,21 @@ var FIELD_ADDON_SUPPORT = [
2140
2211
  adapter: "material",
2141
2212
  slots: ["prefix", "suffix"]
2142
2213
  },
2214
+ {
2215
+ fieldType: "select",
2216
+ adapter: "material",
2217
+ slots: ["prefix", "suffix"]
2218
+ },
2219
+ {
2220
+ fieldType: "textarea",
2221
+ adapter: "material",
2222
+ slots: ["prefix", "suffix"]
2223
+ },
2224
+ {
2225
+ fieldType: "datepicker",
2226
+ adapter: "material",
2227
+ slots: ["prefix", "suffix"]
2228
+ },
2143
2229
  {
2144
2230
  fieldType: "input",
2145
2231
  adapter: "bootstrap",
@@ -2921,6 +3007,34 @@ Paged forms may virtualize inactive pages. DynamicForm \`(initialized)\` emits a
2921
3007
  the initially active page is ready. Use \`(activePageInitialized)\` when work must run
2922
3008
  after every visible-page transition; it announces once per visit.
2923
3009
 
3010
+ ### Rendering Performance
3011
+
3012
+ Use \`options.pagePreloadWindow\` for paged forms. It controls how many neighbouring
3013
+ pages are mounted and have their component chunks preloaded. \`0\` keeps only the
3014
+ active page in the render window; the default is \`1\`.
3015
+
3016
+ Use \`options.fieldWindowing\` for large flat forms:
3017
+
3018
+ \`\`\`typescript
3019
+ {
3020
+ options: {
3021
+ fieldWindowing: {
3022
+ eager: 20,
3023
+ placeholderHeight: '4rem',
3024
+ park: { margin: '100%' },
3025
+ },
3026
+ },
3027
+ fields: [ /* flat fields */ ],
3028
+ }
3029
+ \`\`\`
3030
+
3031
+ - \`eager\` mounts that many leading leaf fields immediately. Remaining fields mount near the viewport.
3032
+ - \`placeholderHeight\` reserves layout space before a field mounts.
3033
+ - \`park\` keeps mounted offscreen DOM in place but removes its view from routine change detection. Safety-related state such as disabled, readonly, required, and validation state stays current. Other model-to-DOM updates catch up when the field returns.
3034
+ - \`park.margin\` follows \`IntersectionObserver.rootMargin\`: use one to four \`px\` or \`%\` values. Unsupported units fall back to the inherited margin.
3035
+ - \`fieldWindowing: false\` disables inherited deferred mounting for that form.
3036
+ - A park-only object changes parking without changing the inherited mounting mode. Use \`{ park: false }\` to opt out or \`{ park: true }\` to opt in.
3037
+
2924
3038
  ## Validation
2925
3039
 
2926
3040
  ### Shorthand Validators (Preferred)
@@ -4813,15 +4927,23 @@ var ContainerLogicSchema = z13.object({
4813
4927
  type: z13.literal("hidden"),
4814
4928
  condition: z13.union([ConditionalExpressionSchema, z13.boolean()])
4815
4929
  });
4930
+ var SIMPLIFIED_ONLY_PROPS = ["template", "value", "addButton", "removeButton"];
4931
+ var SIMPLIFIED_ONLY_HINTS = {
4932
+ template: 'Use "fields" for the full API, or "template" for the simplified one.',
4933
+ value: 'Set initial values on the field definitions inside "fields".',
4934
+ addButton: 'Add an "add-array-item" or "insert-array-item" button as a field.',
4935
+ removeButton: 'Add a "remove-array-item" button to the item definition.'
4936
+ };
4816
4937
  function createContainerSchemas(options) {
4817
4938
  const { leafFieldSchema } = options;
4818
4939
  const AnyFieldSchema = z13.lazy(
4819
- () => z13.union([
4940
+ () => z13.discriminatedUnion("type", [
4820
4941
  leafFieldSchema,
4821
4942
  PageFieldSchema,
4822
4943
  RowFieldSchema,
4823
4944
  GroupFieldSchema,
4824
- ArrayFieldSchema
4945
+ ArrayFieldSchema,
4946
+ ContainerFieldSchema
4825
4947
  ])
4826
4948
  );
4827
4949
  const ContainerBaseSchema = BaseFieldDefSchema.omit({
@@ -4833,6 +4955,18 @@ function createContainerSchemas(options) {
4833
4955
  label: z13.never().optional(),
4834
4956
  meta: z13.never().optional()
4835
4957
  });
4958
+ const ContainerValidationShape = {
4959
+ required: z13.boolean().optional(),
4960
+ validators: ValidatorsArraySchema.optional(),
4961
+ validationMessages: ValidationMessagesSchema.optional()
4962
+ };
4963
+ const WrapperConfigSchema = z13.object({ type: z13.string() }).passthrough();
4964
+ const ContainerFieldSchema = ContainerBaseSchema.extend({
4965
+ type: z13.literal("container"),
4966
+ fields: z13.array(AnyFieldSchema),
4967
+ wrappers: z13.array(WrapperConfigSchema),
4968
+ logic: z13.array(ContainerLogicSchema).optional()
4969
+ });
4836
4970
  const PageFieldSchema = ContainerBaseSchema.extend({
4837
4971
  type: z13.literal("page"),
4838
4972
  fields: z13.array(AnyFieldSchema),
@@ -4849,33 +4983,27 @@ function createContainerSchemas(options) {
4849
4983
  const GroupFieldSchema = ContainerBaseSchema.extend({
4850
4984
  type: z13.literal("group"),
4851
4985
  fields: z13.array(AnyFieldSchema),
4986
+ ...ContainerValidationShape,
4852
4987
  // Container logic - only 'hidden' type allowed (same as pages)
4853
4988
  logic: z13.array(ContainerLogicSchema).optional()
4854
4989
  });
4855
4990
  const ArrayButtonConfigSchema = z13.object({
4856
4991
  label: z13.string().optional(),
4857
- props: z13.record(z13.unknown()).optional()
4858
- });
4859
- const FullArrayFieldSchema = ContainerBaseSchema.extend({
4860
- type: z13.literal("array"),
4861
- fields: z13.array(AnyFieldSchema),
4862
- logic: z13.array(ContainerLogicSchema).optional(),
4863
- // Full API does not use template
4864
- template: z13.never().optional(),
4865
- // Array length validation
4866
- minLength: z13.number().int().min(0).optional(),
4867
- maxLength: z13.number().int().min(0).optional()
4992
+ props: z13.record(z13.string(), z13.unknown()).optional()
4868
4993
  });
4869
4994
  const ArrayAllowedChildSchema = z13.lazy(
4870
4995
  () => AnyFieldSchema.refine((field) => field.type !== "page" && field.type !== "array", {
4871
4996
  message: "Array templates cannot contain page or array fields. Only leaf fields, rows, and groups are allowed."
4872
4997
  })
4873
4998
  );
4874
- const SimplifiedArrayFieldSchema = ContainerBaseSchema.extend({
4999
+ const ArrayFieldSchema = ContainerBaseSchema.extend({
4875
5000
  type: z13.literal("array"),
4876
- // Template: single field (primitive array) or array of fields (object array)
5001
+ ...ContainerValidationShape,
5002
+ // Full API: explicit item definitions.
5003
+ fields: z13.array(AnyFieldSchema).optional(),
5004
+ // Simplified API: single field (primitive array) or array of fields (object array).
4877
5005
  // Only ArrayAllowedChildren (leaf fields, rows, groups) are valid — no pages or nested arrays.
4878
- template: z13.union([ArrayAllowedChildSchema, z13.array(ArrayAllowedChildSchema)]),
5006
+ template: z13.union([ArrayAllowedChildSchema, z13.array(ArrayAllowedChildSchema)]).optional(),
4879
5007
  // Initial values for the array
4880
5008
  value: z13.array(z13.unknown()).optional(),
4881
5009
  // Button customization or opt-out (false to disable)
@@ -4885,18 +5013,64 @@ function createContainerSchemas(options) {
4885
5013
  // Array length validation
4886
5014
  minLength: z13.number().int().min(0).optional(),
4887
5015
  maxLength: z13.number().int().min(0).optional(),
4888
- // Simplified API does not use fields
4889
- fields: z13.never().optional(),
5016
+ // The array size properties are minLength/maxLength. These are the common
5017
+ // wrong spelling, and naming them keeps the mistake from being silently
5018
+ // stripped. Previously only the simplified API rejected them.
4890
5019
  minItems: z13.never().optional(),
4891
5020
  maxItems: z13.never().optional()
5021
+ }).superRefine((field, ctx) => {
5022
+ const hasFields = field.fields !== void 0;
5023
+ const hasTemplate = field.template !== void 0;
5024
+ if (hasFields && hasTemplate) {
5025
+ ctx.addIssue({
5026
+ code: "custom",
5027
+ message: 'Array has BOTH "fields" and "template". These are mutually exclusive: use "fields" for the full API, or "template" + "value" for the simplified API.'
5028
+ });
5029
+ return;
5030
+ }
5031
+ if (!hasFields && !hasTemplate) {
5032
+ ctx.addIssue({
5033
+ code: "custom",
5034
+ message: 'Array is MISSING both "fields" and "template". Use "fields" (full API) or "template" + "value" (simplified API).'
5035
+ });
5036
+ return;
5037
+ }
5038
+ if (hasFields) {
5039
+ for (const prop of SIMPLIFIED_ONLY_PROPS) {
5040
+ if (prop === "template" || field[prop] === void 0) continue;
5041
+ ctx.addIssue({
5042
+ code: "custom",
5043
+ path: [prop],
5044
+ message: `Array uses the full API ("fields"), so "${prop}" is not allowed: it belongs to the simplified API. ${SIMPLIFIED_ONLY_HINTS[prop]}`
5045
+ });
5046
+ }
5047
+ }
5048
+ }).meta({
5049
+ // The refinement above is invisible to JSON Schema generation, and the
5050
+ // generated schema is authoring guidance for a model. Two optional
5051
+ // properties with no stated relationship reads as "pass either, both or
5052
+ // neither", which is exactly the mistake the refinement rejects at
5053
+ // runtime. Restating the rule as `oneOf` keeps the published schema
5054
+ // saying what the validator enforces — the union of two array schemas
5055
+ // used to express this, and collapsing them to one schema lost it.
5056
+ description: 'Array field. Use EXACTLY ONE of "fields" (full API: explicit item definitions) or "template" (simplified API: one field, or an array of fields, repeated per item). Never both, never neither. "value", "addButton" and "removeButton" belong to the simplified API only: with "fields", put initial values on the item definitions and add buttons as fields.',
5057
+ oneOf: [
5058
+ {
5059
+ required: ["fields"],
5060
+ // `not` wraps an `anyOf`, not one multi-name `required`: the latter
5061
+ // would only forbid all of them being present at once.
5062
+ not: { anyOf: SIMPLIFIED_ONLY_PROPS.map((prop) => ({ required: [prop] })) }
5063
+ },
5064
+ { required: ["template"], not: { required: ["fields"] } }
5065
+ ]
4892
5066
  });
4893
- const ArrayFieldSchema = z13.union([FullArrayFieldSchema, SimplifiedArrayFieldSchema]);
4894
5067
  const AllFieldsSchema = AnyFieldSchema;
4895
5068
  return {
4896
5069
  PageFieldSchema,
4897
5070
  RowFieldSchema,
4898
5071
  GroupFieldSchema,
4899
5072
  ArrayFieldSchema,
5073
+ ContainerFieldSchema,
4900
5074
  AllFieldsSchema
4901
5075
  };
4902
5076
  }
@@ -5106,7 +5280,7 @@ var BaseFormConfigSchema = z18.object({
5106
5280
  });
5107
5281
 
5108
5282
  // internal/dynamic-forms-validation/validate/src/json-schema/form-config-json-schema.ts
5109
- import zodToJsonSchema from "zod-to-json-schema";
5283
+ import { z as z55 } from "zod";
5110
5284
 
5111
5285
  // internal/dynamic-forms-validation/material/src/props/mat-common-props.schema.ts
5112
5286
  import { z as z19 } from "zod";
@@ -6604,24 +6778,20 @@ var formConfigSchemas = {
6604
6778
  primeng: PrimeFormConfigSchema,
6605
6779
  ionic: IonicFormConfigSchema
6606
6780
  };
6781
+ function toJsonSchema(schema, name, basePath) {
6782
+ const jsonSchema = z55.toJSONSchema(schema, { target: "draft-7", io: "input", unrepresentable: "any" });
6783
+ jsonSchema["$id"] = basePath ? `${basePath.replace(/\/$/, "")}/${name}` : name;
6784
+ return jsonSchema;
6785
+ }
6607
6786
  function getFormConfigJsonSchema(uiIntegration, options = {}) {
6608
6787
  const schema = formConfigSchemas[uiIntegration];
6609
6788
  if (!schema) {
6610
6789
  throw new Error(`Unknown UI integration: ${uiIntegration}. Valid options: material, bootstrap, primeng, ionic`);
6611
6790
  }
6612
- const { name, basePath } = options;
6613
- const jsonSchema = zodToJsonSchema(schema, {
6614
- name: name ?? `${uiIntegration}FormConfig`,
6615
- basePath: basePath ? basePath.split("/") : void 0,
6616
- $refStrategy: "none",
6617
- // Inline all definitions for MCP compatibility
6618
- errorMessages: true
6619
- });
6620
- return jsonSchema;
6791
+ return toJsonSchema(schema, options.name ?? `${uiIntegration}FormConfig`, options.basePath);
6621
6792
  }
6622
6793
 
6623
6794
  // internal/dynamic-forms-validation/validate/src/json-schema/field-type-json-schemas.ts
6624
- import zodToJsonSchema2 from "zod-to-json-schema";
6625
6795
  var leafFieldSchemas = {
6626
6796
  material: MatLeafFieldSchema,
6627
6797
  bootstrap: BsLeafFieldSchema,
@@ -6716,53 +6886,41 @@ function getFieldTypeJsonSchema(uiIntegration, fieldType) {
6716
6886
  if (!schema) {
6717
6887
  throw new Error(`Unknown field type: ${fieldType}. Valid: ${getSupportedFieldTypes().join(", ")}`);
6718
6888
  }
6719
- const jsonSchema = zodToJsonSchema2(schema, {
6720
- name: `${uiIntegration}${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)}Field`,
6721
- $refStrategy: "none",
6722
- errorMessages: true
6723
- });
6724
- return jsonSchema;
6889
+ return toJsonSchema(schema, `${uiIntegration}${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)}Field`);
6725
6890
  }
6726
6891
  function getLeafFieldJsonSchema(uiIntegration) {
6727
6892
  const schema = leafFieldSchemas[uiIntegration];
6728
6893
  if (!schema) {
6729
6894
  throw new Error(`Unknown UI integration: ${uiIntegration}`);
6730
6895
  }
6731
- const jsonSchema = zodToJsonSchema2(schema, {
6732
- name: `${uiIntegration}LeafField`,
6733
- $refStrategy: "none",
6734
- errorMessages: true
6735
- });
6736
- return jsonSchema;
6896
+ return toJsonSchema(schema, `${uiIntegration}LeafField`);
6737
6897
  }
6738
6898
 
6739
6899
  // internal/dynamic-forms-validation/src/lib/schemas/field-type-names.ts
6740
- import { z as z55 } from "zod";
6900
+ import { z as z56 } from "zod";
6741
6901
  var MAX_DEPTH = 20;
6742
6902
  function collectDiscriminator(schema, into) {
6743
- if (schema instanceof z55.ZodLiteral) {
6744
- if (typeof schema.value === "string") into.add(schema.value);
6903
+ if (schema instanceof z56.ZodLiteral) {
6904
+ for (const value of schema.def.values) {
6905
+ if (typeof value === "string") into.add(value);
6906
+ }
6745
6907
  return;
6746
6908
  }
6747
- if (schema instanceof z55.ZodEnum) {
6909
+ if (schema instanceof z56.ZodEnum) {
6748
6910
  for (const value of schema.options) into.add(value);
6749
6911
  }
6750
6912
  }
6751
6913
  function visit(schema, into, depth) {
6752
6914
  if (depth > MAX_DEPTH) return;
6753
- if (schema instanceof z55.ZodLazy) {
6754
- visit(schema.schema, into, depth + 1);
6915
+ if (schema instanceof z56.ZodLazy) {
6916
+ visit(schema.unwrap(), into, depth + 1);
6755
6917
  return;
6756
6918
  }
6757
- if (schema instanceof z55.ZodEffects) {
6758
- visit(schema.innerType(), into, depth + 1);
6759
- return;
6760
- }
6761
- if (schema instanceof z55.ZodDiscriminatedUnion || schema instanceof z55.ZodUnion) {
6919
+ if (schema instanceof z56.ZodUnion) {
6762
6920
  for (const option of schema.options) visit(option, into, depth + 1);
6763
6921
  return;
6764
6922
  }
6765
- if (schema instanceof z55.ZodObject) {
6923
+ if (schema instanceof z56.ZodObject) {
6766
6924
  collectDiscriminator(schema.shape["type"], into);
6767
6925
  return;
6768
6926
  }
@@ -6835,14 +6993,14 @@ var DID_YOU_MEAN = {
6835
6993
  validation: "Did you mean to use validator shorthand (`required`, `email`, `min`, etc.) or `validators: [...]` array?"
6836
6994
  };
6837
6995
  function formatZodError(error, uiIntegration, config) {
6838
- const errors = error.errors.map((err) => {
6996
+ const errors = error.issues.map((err) => {
6839
6997
  const path = err.path.join(".") || "root";
6840
6998
  let message = err.message;
6841
6999
  let expected;
6842
7000
  let received;
6843
7001
  if ("expected" in err) expected = String(err.expected);
6844
7002
  if ("received" in err) received = String(err.received);
6845
- if (message === "Invalid input" || message === "Invalid union") {
7003
+ if (err.code === "invalid_union") {
6846
7004
  const fieldMatch = path.match(/fields\.(\d+)/);
6847
7005
  if (fieldMatch && config && typeof config === "object" && "fields" in config) {
6848
7006
  const fields = config.fields;
@@ -6973,123 +7131,31 @@ var FORBIDDEN_HIDDEN_PROPS = [
6973
7131
  "pattern",
6974
7132
  "placeholder"
6975
7133
  ];
6976
- var CONTAINER_FORBIDDEN_PROPS = ["validators", "required", "email", "min", "max", "minLength", "maxLength", "pattern", "value"];
7134
+ var CONTAINER_FORBIDDEN_PROPS = ["email", "min", "max", "pattern", "value"];
7135
+ var VALIDATING_CONTAINERS = ["group", "array"];
7136
+ var CONTAINER_VALIDATION_PROPS = ["validators", "required", "validationMessages"];
7137
+ var ARRAY_SIZE_PROPS = ["minLength", "maxLength"];
6977
7138
  var FIELDS_REQUIRING_OPTIONS = ["select", "radio", "multi-checkbox"];
6978
7139
  var NESTING_RULES = {
6979
7140
  page: {
6980
- allowed: [
6981
- "row",
6982
- "group",
6983
- "array",
6984
- "input",
6985
- "textarea",
6986
- "select",
6987
- "checkbox",
6988
- "multi-checkbox",
6989
- "radio",
6990
- "datepicker",
6991
- "toggle",
6992
- "slider",
6993
- "hidden",
6994
- "text",
6995
- "button",
6996
- "submit",
6997
- "next",
6998
- "previous",
6999
- "add-array-item",
7000
- "prepend-array-item",
7001
- "insert-array-item",
7002
- "remove-array-item",
7003
- "pop-array-item",
7004
- "shift-array-item"
7005
- ],
7006
7141
  forbidden: ["page"],
7007
7142
  message: "Pages cannot be nested inside other containers. ALL top-level fields must be pages if using multi-page mode."
7008
7143
  },
7009
7144
  row: {
7010
- allowed: [
7011
- "group",
7012
- "array",
7013
- "input",
7014
- "textarea",
7015
- "select",
7016
- "checkbox",
7017
- "multi-checkbox",
7018
- "radio",
7019
- "datepicker",
7020
- "toggle",
7021
- "slider",
7022
- "text",
7023
- "button",
7024
- "submit",
7025
- "next",
7026
- "previous",
7027
- "add-array-item",
7028
- "prepend-array-item",
7029
- "insert-array-item",
7030
- "remove-array-item",
7031
- "pop-array-item",
7032
- "shift-array-item"
7033
- ],
7034
7145
  forbidden: ["page", "row", "hidden"],
7035
7146
  message: "Rows cannot contain pages, other rows, or hidden fields. Hidden fields should be at page or form level."
7036
7147
  },
7037
7148
  group: {
7038
- allowed: [
7039
- "row",
7040
- "input",
7041
- "textarea",
7042
- "select",
7043
- "checkbox",
7044
- "multi-checkbox",
7045
- "radio",
7046
- "datepicker",
7047
- "toggle",
7048
- "slider",
7049
- "hidden",
7050
- "text",
7051
- "button",
7052
- "submit",
7053
- "next",
7054
- "previous",
7055
- "add-array-item",
7056
- "prepend-array-item",
7057
- "insert-array-item",
7058
- "remove-array-item",
7059
- "pop-array-item",
7060
- "shift-array-item"
7061
- ],
7062
7149
  forbidden: ["page", "group"],
7063
7150
  message: "Groups cannot contain pages or other groups (no nested groups)."
7064
7151
  },
7065
7152
  array: {
7066
- allowed: [
7067
- "row",
7068
- "group",
7069
- "input",
7070
- "textarea",
7071
- "select",
7072
- "checkbox",
7073
- "multi-checkbox",
7074
- "radio",
7075
- "datepicker",
7076
- "toggle",
7077
- "slider",
7078
- "hidden",
7079
- "text",
7080
- "button",
7081
- "submit",
7082
- "next",
7083
- "previous",
7084
- "add-array-item",
7085
- "prepend-array-item",
7086
- "insert-array-item",
7087
- "remove-array-item",
7088
- "pop-array-item",
7089
- "shift-array-item"
7090
- ],
7091
7153
  forbidden: ["page", "array"],
7092
7154
  message: "Arrays cannot contain pages or other arrays (no nested arrays)."
7155
+ },
7156
+ container: {
7157
+ forbidden: ["page"],
7158
+ message: "Containers cannot contain pages. Pages are top-level fields only."
7093
7159
  }
7094
7160
  };
7095
7161
  var EXPECTED_STRUCTURE = {
@@ -7101,7 +7167,8 @@ var EXPECTED_STRUCTURE = {
7101
7167
  row: `{ key: 'rowKey', type: 'row', fields: [...childFields] }`,
7102
7168
  group: `{ key: 'groupKey', type: 'group', fields: [...childFields] }`,
7103
7169
  array: `Full API: { key: 'arrayKey', type: 'array', fields: [...itemDefs] } OR Simplified API: { key: 'arrayKey', type: 'array', template: { type: 'input', label: 'Item' }, value: [] }`,
7104
- page: `{ key: 'pageKey', type: 'page', fields: [...childFields, { key: 'next', type: 'next', label: 'Next' }] }`
7170
+ page: `{ key: 'pageKey', type: 'page', fields: [...childFields, { key: 'next', type: 'next', label: 'Next' }] }`,
7171
+ container: `{ key: 'containerKey', type: 'container', wrappers: [{ type: 'css', cssClasses: 'card' }], fields: [...childFields] }`
7105
7172
  };
7106
7173
  function preValidateConfig(config) {
7107
7174
  const errors = [];
@@ -7146,7 +7213,7 @@ function preValidateConfig(config) {
7146
7213
  }
7147
7214
  });
7148
7215
  }
7149
- if (["row", "group", "array"].includes(fieldType || "")) {
7216
+ if (["row", "group", "array", "container"].includes(fieldType || "")) {
7150
7217
  for (const prop of INVALID_CONTAINER_PROPS) {
7151
7218
  if (prop in f) {
7152
7219
  errors.push({
@@ -7163,10 +7230,30 @@ function preValidateConfig(config) {
7163
7230
  const expected = EXPECTED_STRUCTURE[fieldType || ""] || "";
7164
7231
  errors.push({
7165
7232
  path: `${path}.${prop}`,
7166
- message: `"${fieldType}" containers do NOT support "${prop}". Containers don't hold values or validate - they are purely for layout/grouping.${expected ? ` Expected structure: ${expected}` : ""}`
7233
+ message: `"${fieldType}" containers do NOT support "${prop}". Containers don't hold values - they are purely for layout/grouping.${expected ? ` Expected structure: ${expected}` : ""}`
7167
7234
  });
7168
7235
  }
7169
7236
  }
7237
+ if (!VALIDATING_CONTAINERS.includes(fieldType || "")) {
7238
+ for (const prop of CONTAINER_VALIDATION_PROPS) {
7239
+ if (prop in f) {
7240
+ errors.push({
7241
+ path: `${path}.${prop}`,
7242
+ message: `"${fieldType}" containers do NOT support "${prop}". They flatten into their parent and have no schema path, so a container-level validator would have no value to run against. Use "group" or "array" for cross-field rules, or move the rule onto a child field.`
7243
+ });
7244
+ }
7245
+ }
7246
+ }
7247
+ if (fieldType !== "array") {
7248
+ for (const prop of ARRAY_SIZE_PROPS) {
7249
+ if (prop in f) {
7250
+ errors.push({
7251
+ path: `${path}.${prop}`,
7252
+ message: `"${fieldType}" containers do NOT support "${prop}". Array size constraints apply to "array" fields only.`
7253
+ });
7254
+ }
7255
+ }
7256
+ }
7170
7257
  if ("logic" in f && Array.isArray(f["logic"])) {
7171
7258
  const nonHiddenLogic = f["logic"].filter(
7172
7259
  (l) => l && typeof l === "object" && l["type"] !== "hidden"
@@ -7197,6 +7284,14 @@ function preValidateConfig(config) {
7197
7284
  });
7198
7285
  }
7199
7286
  }
7287
+ for (const prop of [...CONTAINER_VALIDATION_PROPS, ...ARRAY_SIZE_PROPS]) {
7288
+ if (prop in f) {
7289
+ errors.push({
7290
+ path: `${path}.${prop}`,
7291
+ message: `"page" containers do NOT support "${prop}". Pages flatten into the form and have no schema path of their own.`
7292
+ });
7293
+ }
7294
+ }
7200
7295
  }
7201
7296
  if (parentType && NESTING_RULES[parentType]) {
7202
7297
  const rules = NESTING_RULES[parentType];
@@ -7277,7 +7372,7 @@ function preValidateConfig(config) {
7277
7372
  });
7278
7373
  }
7279
7374
  }
7280
- if (["row", "group", "page"].includes(fieldType || "")) {
7375
+ if (["row", "group", "page", "container"].includes(fieldType || "")) {
7281
7376
  if (!("fields" in f)) {
7282
7377
  errors.push({
7283
7378
  path: `${path}.fields`,
@@ -7290,6 +7385,19 @@ function preValidateConfig(config) {
7290
7385
  });
7291
7386
  }
7292
7387
  }
7388
+ if (fieldType === "container") {
7389
+ if (!("wrappers" in f)) {
7390
+ errors.push({
7391
+ path: `${path}.wrappers`,
7392
+ message: `"container" container "${fieldKey || "unknown"}" is MISSING required "wrappers" property. Add a wrappers array naming the UI chrome to wrap the children in, or [] for no chrome. Do NOT switch to a "group" instead: a container flattens its children into the parent value, while a group nests them under its own key and owns a schema path, so the submitted value shape and validation behaviour both change. Expected structure: ${EXPECTED_STRUCTURE["container"]}`
7393
+ });
7394
+ } else if (!Array.isArray(f["wrappers"])) {
7395
+ errors.push({
7396
+ path: `${path}.wrappers`,
7397
+ message: `"container" container "${fieldKey || "unknown"}" has invalid "wrappers" - must be an array of wrapper objects, not ${typeof f["wrappers"]}.`
7398
+ });
7399
+ }
7400
+ }
7293
7401
  if (fieldType === "array") {
7294
7402
  const hasFields = "fields" in f;
7295
7403
  const hasTemplate = "template" in f;
@@ -8127,7 +8235,7 @@ function registerResources(server) {
8127
8235
  }
8128
8236
 
8129
8237
  // packages/dynamic-form-mcp/src/tools/lookup.tool.ts
8130
- import { z as z56 } from "zod";
8238
+ import { z as z57 } from "zod";
8131
8239
 
8132
8240
  // packages/dynamic-form-mcp/src/tools/data/lookup-topics.ts
8133
8241
  var TOPICS = {
@@ -9862,25 +9970,42 @@ provideDynamicForm({
9862
9970
  | FieldContext | \`ctx.value()\` | \`ctx.valueOf('path' as never)\` | custom validators |`
9863
9971
  },
9864
9972
  containers: {
9865
- brief: `| Container | Label? | Logic? | Notes |
9866
- |-----------|--------|--------|-------|
9867
- | page | NO | hidden only | Nav buttons INSIDE |
9868
- | group | NO | hidden only | Creates nested object |
9869
- | array | NO | hidden only | Two APIs: \`template\`+\`value\` (simplified) or \`fields\` (full) |
9870
- | row | NO | hidden only | Layout only |`,
9973
+ brief: `| Container | Label? | Logic? | Value | Notes |
9974
+ |-----------|--------|--------|-------|-------|
9975
+ | page | NO | hidden only | flattened | Nav buttons INSIDE |
9976
+ | group | NO | hidden only | nested under its key | Creates nested object |
9977
+ | array | NO | hidden only | nested under its key | Two APIs: \`template\`+\`value\` (simplified) or \`fields\` (full) |
9978
+ | row | NO | hidden only | flattened | Layout only |
9979
+ | container | NO | hidden only | flattened | Layout + REQUIRED \`wrappers\` chain |`,
9871
9980
  full: `# Container Rules
9872
9981
 
9873
- | Container | Label? | Logic? | Allowed Children | Notes |
9874
- |-----------|--------|--------|------------------|-------|
9875
- | page | NO | YES (hidden only) | rows, groups, arrays, leaf fields, buttons | Nav buttons go INSIDE |
9876
- | group | NO | YES (hidden only) | rows, arrays, leaf fields (NOT pages, groups) | Creates nested object |
9877
- | array | NO | YES (hidden only) | rows, groups, leaf fields (NOT pages, arrays) | Two APIs: \`template\`+\`value\` (simplified) or \`fields\` (full) |
9878
- | row | NO | YES (hidden only) | groups, arrays, containers, nested rows, hidden fields, leaf fields (NOT pages) | Layout only |
9982
+ | Container | Label? | Logic? | Value Handling | Allowed Children | Notes |
9983
+ |-----------|--------|--------|----------------|------------------|-------|
9984
+ | page | NO | YES (hidden only) | flattened | rows, groups, arrays, containers, hidden, leaf fields, buttons (NOT pages) | Nav buttons go INSIDE |
9985
+ | group | NO | YES (hidden only) | nested under its key | rows, arrays, containers, hidden, leaf fields, buttons (NOT pages, groups) | Creates nested object |
9986
+ | array | NO | YES (hidden only) | nested under its key | rows, groups, containers, hidden, leaf fields, buttons (NOT pages, arrays) | Two APIs: \`template\`+\`value\` (simplified) or \`fields\` (full) |
9987
+ | row | NO | YES (hidden only) | flattened | groups, arrays, containers, leaf fields, buttons (NOT pages, nested rows, hidden) | Layout only |
9988
+ | container | NO | YES (hidden only) | flattened | rows, groups, arrays, nested containers, hidden, leaf fields, buttons (NOT pages) | \`wrappers\` is REQUIRED (use [] for none) |
9989
+
9990
+ **Value handling decides which container you want.** \`page\`, \`row\` and \`container\` flatten: their children land directly in the parent value, and the container key never appears in the submitted data. \`group\` and \`array\` nest their children under their own key.
9991
+
9992
+ That difference is not cosmetic, so these are not interchangeable:
9993
+
9994
+ \`\`\`typescript
9995
+ // container \u2192 { street: '', city: '' } (chrome only, key not in the value)
9996
+ { key: 'billing', type: 'container', wrappers: [{ type: 'css', cssClasses: 'card' }], fields: [street, city] }
9997
+
9998
+ // group \u2192 { billing: { street: '', city: '' } } (adds a level to the value)
9999
+ { key: 'billing', type: 'group', fields: [street, city] }
10000
+ \`\`\`
10001
+
10002
+ Only \`group\` and \`array\` own a schema path, so only they take container-level \`validators\`, \`required\` and \`validationMessages\`. Reach for \`container\` when you want chrome around fields without changing the data shape, and for \`group\` when you want the nesting or the cross-field validation.
9879
10003
 
9880
10004
  **CRITICAL:**
9881
- - All containers (page, group, row, array) support only the \`hidden\` logic type for conditional visibility.
10005
+ - All containers (page, group, row, array, container) support only the \`hidden\` logic type for conditional visibility.
9882
10006
  - For other logic types (disabled, required, readonly, derivation), apply them to child fields instead.
9883
- - Pages cannot be nested - ALL top-level fields must be pages if using multi-page mode.`
10007
+ - Pages cannot be nested - ALL top-level fields must be pages if using multi-page mode.
10008
+ - Rows reject nested rows and hidden fields; put hidden fields at page or form level.`
9884
10009
  },
9885
10010
  "field-placement": {
9886
10011
  brief: `**Multi-page:** ALL root fields must be pages. Hidden fields go INSIDE first page.
@@ -10674,7 +10799,8 @@ var TOPIC_DESCRIPTIONS = {
10674
10799
  "field-placement": "Which fields can go where \u2014 nesting and placement rules",
10675
10800
  "logic-matrix": "Which logic types are supported on which field types",
10676
10801
  "context-api": "FieldContext API \u2014 valueOf(), formValue, parent, arrayIndex",
10677
- containers: "Overview of all container types (group, row, array, page)",
10802
+ containers: "Overview of all container types (group, row, array, container, page) and what each may hold",
10803
+ container: "Wraps children in a wrapper chain; flattens their values into the parent like a row",
10678
10804
  "array-buttons": "Add/remove array item button configuration",
10679
10805
  "custom-validators": "Custom validator functions via validators array",
10680
10806
  conditions: "Condition operators and types for logic rules",
@@ -10934,7 +11060,7 @@ function formatTopicItem(key) {
10934
11060
  }
10935
11061
  function getTopicList() {
10936
11062
  const fieldTypes = ["input", "select", "slider", "radio", "checkbox", "textarea", "datepicker", "toggle", "text", "hidden"];
10937
- const containers = ["group", "row", "array", "simplified-array", "page"];
11063
+ const containers = [...getFieldTypesByCategory("container").map((f) => f.type), "simplified-array"];
10938
11064
  const wrappers = ["wrappers", ...getWrappers().map((w) => w.type)];
10939
11065
  const addons = ["addons", ...getAddonTypes().map((k) => k.type)];
10940
11066
  const concepts = [
@@ -10990,7 +11116,7 @@ ${patterns.map(formatTopicItem).join("\n")}
10990
11116
  }
10991
11117
  function formatFieldInfoFull(field, uiFieldType) {
10992
11118
  const lines = [];
10993
- const isContainer = ["row", "group", "array", "page"].includes(field.type);
11119
+ const isContainer = field.category === "container";
10994
11120
  const isHidden = field.type === "hidden";
10995
11121
  lines.push(`## ${field.type} field`);
10996
11122
  lines.push("");
@@ -11419,11 +11545,11 @@ Patterns: field-placement, logic-matrix, context-api, containers, multi-page-got
11419
11545
  Use topic="list" to see all available topics with descriptions.
11420
11546
  Use ngforge_search query="your question" to find topics by keyword.`,
11421
11547
  {
11422
- topic: z56.string().describe(
11423
- 'Topic to look up: field types (input, select, hidden, group, row, array, page), wrappers (wrappers, css, section, arraySection), concepts (validation, conditional, derivation, property-derivation, options-format), patterns (golden-path, pitfalls, multi-page-gotchas), or "list" to see all topics'
11548
+ topic: z57.string().describe(
11549
+ 'Topic to look up: field types (input, select, hidden, group, row, array, container, page), wrappers (wrappers, css, section, arraySection), concepts (validation, conditional, derivation, property-derivation, options-format), patterns (golden-path, pitfalls, multi-page-gotchas), or "list" to see all topics'
11424
11550
  ),
11425
- depth: z56.enum(["brief", "full", "schema"]).default("full").describe("brief=quick syntax (~20 lines), full=complete docs with examples, schema=include JSON schema"),
11426
- uiIntegration: z56.enum(UI_INTEGRATIONS2).optional().describe("Filter UI-specific info (only with depth=schema). material, bootstrap, primeng, ionic")
11551
+ depth: z57.enum(["brief", "full", "schema"]).default("full").describe("brief=quick syntax (~20 lines), full=complete docs with examples, schema=include JSON schema"),
11552
+ uiIntegration: z57.enum(UI_INTEGRATIONS2).optional().describe("Filter UI-specific info (only with depth=schema). material, bootstrap, primeng, ionic")
11427
11553
  },
11428
11554
  async ({ topic, depth, uiIntegration }) => {
11429
11555
  const normalizedTopic = topic.toLowerCase().trim();
@@ -11471,7 +11597,7 @@ ${nextSteps}`;
11471
11597
  }
11472
11598
 
11473
11599
  // packages/dynamic-form-mcp/src/tools/examples.tool.ts
11474
- import { z as z57 } from "zod";
11600
+ import { z as z58 } from "zod";
11475
11601
  var PATTERNS = {
11476
11602
  // ========== MINIMAL PATTERNS (NEW) ==========
11477
11603
  "minimal-multipage": {
@@ -13736,10 +13862,10 @@ Standard patterns: derivation, property-derivation, multi-page, conditional, val
13736
13862
  Use pattern="list" to see all available patterns.
13737
13863
  Use depth="minimal" for code-only output (no markdown).`,
13738
13864
  {
13739
- pattern: z57.string().optional().describe(
13865
+ pattern: z58.string().optional().describe(
13740
13866
  'Pattern to get: minimal-multipage, minimal-array, minimal-simplified-array, minimal-conditional, minimal-validation, minimal-hidden, derivation, property-derivation, multi-page, conditional, validation, complete, mega. Use "list" to see all.'
13741
13867
  ),
13742
- depth: z57.enum(["minimal", "brief", "full", "explained"]).default("full").describe(
13868
+ depth: z58.enum(["minimal", "brief", "full", "explained"]).default("full").describe(
13743
13869
  "minimal=code only (~20-50 lines), brief=code+summary, full=code+comments (default), explained=code+detailed explanation"
13744
13870
  )
13745
13871
  },
@@ -13804,7 +13930,7 @@ function extractCode(markdown) {
13804
13930
  }
13805
13931
 
13806
13932
  // packages/dynamic-form-mcp/src/tools/validate.tool.ts
13807
- import { z as z58 } from "zod";
13933
+ import { z as z59 } from "zod";
13808
13934
  var UI_INTEGRATIONS3 = ["material", "bootstrap", "primeng", "ionic"];
13809
13935
  var ERROR_TOPIC_HINTS = [
13810
13936
  { pattern: /options/i, hint: '`ngforge_lookup topic="options-format"` \u2014 correct options syntax' },
@@ -13857,10 +13983,10 @@ Example errors you'll see:
13857
13983
  - "options MUST be at FIELD level, NOT inside props"
13858
13984
  - "containers only support 'hidden' logic type"`,
13859
13985
  {
13860
- config: z58.union([z58.string(), z58.object({}).passthrough()]).describe(
13986
+ config: z59.union([z59.string(), z59.object({}).passthrough()]).describe(
13861
13987
  "File path (.ts/.js) OR JSON string OR JSON object. Auto-detects: paths ending in .ts/.js or starting with / are treated as files."
13862
13988
  ),
13863
- uiIntegration: z58.enum(UI_INTEGRATIONS3).default("material").describe("UI library to validate against (material, bootstrap, primeng, ionic). Defaults to material.")
13989
+ uiIntegration: z59.enum(UI_INTEGRATIONS3).default("material").describe("UI library to validate against (material, bootstrap, primeng, ionic). Defaults to material.")
13864
13990
  },
13865
13991
  async ({ config, uiIntegration }) => {
13866
13992
  try {
@@ -13952,7 +14078,7 @@ If validating JSON, ensure:
13952
14078
  }
13953
14079
 
13954
14080
  // packages/dynamic-form-mcp/src/tools/scaffold.tool.ts
13955
- import { z as z59 } from "zod";
14081
+ import { z as z60 } from "zod";
13956
14082
  var UI_INTEGRATIONS4 = ["material", "bootstrap", "primeng", "ionic"];
13957
14083
  function parseFieldDefs(fields) {
13958
14084
  return fields.map((f) => {
@@ -14207,12 +14333,12 @@ Examples:
14207
14333
  - With groups: pages=0 groups=["address","billing"]
14208
14334
  - With hidden: hidden=["userId:abc123","source:web"]`,
14209
14335
  {
14210
- pages: z59.number().min(0).max(10).default(0).describe("Number of pages (0 = single-page form, 1-10 = multi-page wizard)"),
14211
- arrays: z59.array(z59.string()).optional().describe('Array field names for dynamic lists, e.g., ["contacts", "addresses"]'),
14212
- groups: z59.array(z59.string()).optional().describe('Group field names for nested objects, e.g., ["address", "billing"]'),
14213
- hidden: z59.array(z59.string()).optional().describe('Hidden fields as "name:value" pairs, e.g., ["userId:abc", "version:1"]'),
14214
- fields: z59.array(z59.string()).optional().describe('Basic fields as "name:type" pairs, e.g., ["email:input", "country:select", "age:slider"]'),
14215
- uiIntegration: z59.enum(UI_INTEGRATIONS4).default("material").describe("UI library (for future UI-specific props). Defaults to material.")
14336
+ pages: z60.number().min(0).max(10).default(0).describe("Number of pages (0 = single-page form, 1-10 = multi-page wizard)"),
14337
+ arrays: z60.array(z60.string()).optional().describe('Array field names for dynamic lists, e.g., ["contacts", "addresses"]'),
14338
+ groups: z60.array(z60.string()).optional().describe('Group field names for nested objects, e.g., ["address", "billing"]'),
14339
+ hidden: z60.array(z60.string()).optional().describe('Hidden fields as "name:value" pairs, e.g., ["userId:abc", "version:1"]'),
14340
+ fields: z60.array(z60.string()).optional().describe('Basic fields as "name:type" pairs, e.g., ["email:input", "country:select", "age:slider"]'),
14341
+ uiIntegration: z60.enum(UI_INTEGRATIONS4).default("material").describe("UI library (for future UI-specific props). Defaults to material.")
14216
14342
  },
14217
14343
  async ({ pages, arrays = [], groups = [], hidden = [], fields = [] }) => {
14218
14344
  const fieldDefs = parseFieldDefs(fields);
@@ -14254,11 +14380,11 @@ Examples:
14254
14380
  }
14255
14381
 
14256
14382
  // packages/dynamic-form-mcp/src/tools/search.tool.ts
14257
- import { z as z60 } from "zod";
14383
+ import { z as z61 } from "zod";
14258
14384
  var searchIndex = null;
14259
14385
  function getTopicSubcategory(key) {
14260
14386
  const fieldTypes = ["input", "select", "slider", "radio", "checkbox", "textarea", "datepicker", "toggle", "text", "hidden"];
14261
- const containers = ["group", "row", "array", "simplified-array", "page"];
14387
+ const containers = [...getFieldTypesByCategory("container").map((f) => f.type), "simplified-array"];
14262
14388
  const concepts = [
14263
14389
  "validation",
14264
14390
  "validation-messages",
@@ -14298,6 +14424,19 @@ function buildIndex() {
14298
14424
  toolCall: `ngforge_lookup topic="${key}"`
14299
14425
  });
14300
14426
  }
14427
+ for (const field of getFieldTypes()) {
14428
+ if (TOPICS[field.type]) continue;
14429
+ const description = TOPIC_DESCRIPTIONS[field.type] || field.description;
14430
+ entries.push({
14431
+ id: field.type,
14432
+ category: "topic",
14433
+ subcategory: getTopicSubcategory(field.type),
14434
+ description,
14435
+ aliases: aliasMap[field.type] || [],
14436
+ content: `${description} ${field.description}`.toLowerCase(),
14437
+ toolCall: `ngforge_lookup topic="${field.type}"`
14438
+ });
14439
+ }
14301
14440
  for (const [key, pattern] of Object.entries(PATTERNS)) {
14302
14441
  entries.push({
14303
14442
  id: key,
@@ -14402,7 +14541,7 @@ Use this when you don't know the exact topic name.
14402
14541
 
14403
14542
  New to ng-forge? Start with: ngforge_lookup topic="workflow"`,
14404
14543
  {
14405
- query: z60.string().describe("Natural language or keywords to search for")
14544
+ query: z61.string().describe("Natural language or keywords to search for")
14406
14545
  },
14407
14546
  async ({ query }) => {
14408
14547
  const index = buildIndex();