@ng-forge/dynamic-form-mcp 0.9.0-next.10 → 0.9.0-next.12

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.
@@ -780,7 +780,7 @@ var VALIDATORS = [
780
780
  {
781
781
  type: "custom",
782
782
  category: "custom",
783
- description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config.",
783
+ description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config. NOTE: TypeScript-authored configs may also use an inline `fn: CustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
784
784
  parameters: {
785
785
  functionName: {
786
786
  name: "functionName",
@@ -832,7 +832,7 @@ var VALIDATORS = [
832
832
  {
833
833
  type: "async",
834
834
  category: "async",
835
- description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators.",
835
+ description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators. NOTE: TypeScript-authored configs may also use an inline `fn: AsyncCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
836
836
  parameters: {
837
837
  functionName: {
838
838
  name: "functionName",
@@ -855,7 +855,7 @@ var VALIDATORS = [
855
855
  {
856
856
  type: "http",
857
857
  category: "http",
858
- description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages).",
858
+ description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages). NOTE: TypeScript-authored configs may also use an inline `fn: HttpCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName` or declarative mode.",
859
859
  parameters: {
860
860
  functionName: {
861
861
  name: "functionName",
@@ -2142,7 +2142,9 @@ const config: FormConfig = {
2142
2142
 
2143
2143
  Every field MUST have:
2144
2144
 
2145
- - \`key\`: Unique identifier (except for \`row\` type which is layout-only)
2145
+ - \`key\`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. \`createA.name\` and \`createB.name\`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.
2146
+ - **DOM id / data-testid composition:** keys are joined with \`_\` through group ancestors to form the rendered DOM id. A leaf \`street\` inside group \`address\` renders as \`id="address_street"\`. Inside an array item, the item index is appended after: \`id="address_street_0"\`. Use this exact form for \`for=\`, \`aria-describedby\`, \`aria-labelledby\`, or CSS selectors that target group-nested fields.
2147
+ - **Avoid \`_\` in keys when they could collide with a group-prefixed id.** A top-level key \`foo_bar\` and a leaf \`bar\` inside group \`foo\` would both render as \`id="foo_bar"\`. The validator catches this collision at boot, but it's easier to just not put \`_\` in keys that share a name with an existing group + leaf pair.
2146
2148
  - \`type\`: One of the registered field types
2147
2149
  - \`label\`: Human-readable label (for accessibility)
2148
2150
 
@@ -2409,6 +2411,8 @@ Invokes a registered custom function by name:
2409
2411
 
2410
2412
  Register functions via \`customFnConfig.customFunctions\`.
2411
2413
 
2414
+ > **TypeScript-authored configs:** the same surface also accepts an inline \`fn: CustomFunction\` instead of \`functionName\` (XOR with \`functionName\`, code-only \u2014 not JSON-serializable). The MCP server emits \`functionName\` exclusively; consumers writing configs in TS may prefer \`fn\` when they don't need JSON round-tripping. Same caveat applies to async conditions (\`asyncFn\`), function derivations (\`fn\`), async function derivations (\`asyncFn\`), and validators (\`fn\`).
2415
+
2412
2416
  #### Async Custom Condition
2413
2417
 
2414
2418
  Invokes a registered async function that returns \`Promise<boolean>\` or \`Observable<boolean>\`:
@@ -2516,7 +2520,7 @@ Available adapters:
2516
2520
 
2517
2521
  1. **Missing labels** - Always include labels for accessibility
2518
2522
  2. **Missing validation messages** - Users need clear feedback
2519
- 3. **Duplicate keys** - Each key must be unique within its scope
2523
+ 3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.
2520
2524
  4. **Select without options** - Select/radio fields must have options
2521
2525
  5. **Array without fields** - Array fields need a fields template
2522
2526
  6. **Group without fields** - Group fields need child fields
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-forge/dynamic-form-mcp",
3
- "version": "0.9.0-next.10",
3
+ "version": "0.9.0-next.12",
4
4
  "description": "MCP server for ng-forge dynamic forms - AI-assisted form schema generation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,11 +51,11 @@
51
51
  "zod": "^3.25.76"
52
52
  },
53
53
  "devDependencies": {
54
- "@ng-forge/dynamic-forms": "^0.9.0-next.10",
55
- "@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.10",
56
- "@ng-forge/dynamic-forms-ionic": "^0.9.0-next.10",
57
- "@ng-forge/dynamic-forms-material": "^0.9.0-next.10",
58
- "@ng-forge/dynamic-forms-primeng": "^0.9.0-next.10",
54
+ "@ng-forge/dynamic-forms": "^0.9.0-next.12",
55
+ "@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.12",
56
+ "@ng-forge/dynamic-forms-ionic": "^0.9.0-next.12",
57
+ "@ng-forge/dynamic-forms-material": "^0.9.0-next.12",
58
+ "@ng-forge/dynamic-forms-primeng": "^0.9.0-next.12",
59
59
  "tsx": "^4.19.0"
60
60
  }
61
61
  }
package/src/index.js CHANGED
@@ -778,7 +778,7 @@ var VALIDATORS = [
778
778
  {
779
779
  type: "custom",
780
780
  category: "custom",
781
- description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config.",
781
+ description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config. NOTE: TypeScript-authored configs may also use an inline `fn: CustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
782
782
  parameters: {
783
783
  functionName: {
784
784
  name: "functionName",
@@ -830,7 +830,7 @@ var VALIDATORS = [
830
830
  {
831
831
  type: "async",
832
832
  category: "async",
833
- description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators.",
833
+ description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators. NOTE: TypeScript-authored configs may also use an inline `fn: AsyncCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
834
834
  parameters: {
835
835
  functionName: {
836
836
  name: "functionName",
@@ -853,7 +853,7 @@ var VALIDATORS = [
853
853
  {
854
854
  type: "http",
855
855
  category: "http",
856
- description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages).",
856
+ description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages). NOTE: TypeScript-authored configs may also use an inline `fn: HttpCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName` or declarative mode.",
857
857
  parameters: {
858
858
  functionName: {
859
859
  name: "functionName",
@@ -2144,7 +2144,9 @@ const config: FormConfig = {
2144
2144
 
2145
2145
  Every field MUST have:
2146
2146
 
2147
- - \`key\`: Unique identifier (except for \`row\` type which is layout-only)
2147
+ - \`key\`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. \`createA.name\` and \`createB.name\`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.
2148
+ - **DOM id / data-testid composition:** keys are joined with \`_\` through group ancestors to form the rendered DOM id. A leaf \`street\` inside group \`address\` renders as \`id="address_street"\`. Inside an array item, the item index is appended after: \`id="address_street_0"\`. Use this exact form for \`for=\`, \`aria-describedby\`, \`aria-labelledby\`, or CSS selectors that target group-nested fields.
2149
+ - **Avoid \`_\` in keys when they could collide with a group-prefixed id.** A top-level key \`foo_bar\` and a leaf \`bar\` inside group \`foo\` would both render as \`id="foo_bar"\`. The validator catches this collision at boot, but it's easier to just not put \`_\` in keys that share a name with an existing group + leaf pair.
2148
2150
  - \`type\`: One of the registered field types
2149
2151
  - \`label\`: Human-readable label (for accessibility)
2150
2152
 
@@ -2411,6 +2413,8 @@ Invokes a registered custom function by name:
2411
2413
 
2412
2414
  Register functions via \`customFnConfig.customFunctions\`.
2413
2415
 
2416
+ > **TypeScript-authored configs:** the same surface also accepts an inline \`fn: CustomFunction\` instead of \`functionName\` (XOR with \`functionName\`, code-only \u2014 not JSON-serializable). The MCP server emits \`functionName\` exclusively; consumers writing configs in TS may prefer \`fn\` when they don't need JSON round-tripping. Same caveat applies to async conditions (\`asyncFn\`), function derivations (\`fn\`), async function derivations (\`asyncFn\`), and validators (\`fn\`).
2417
+
2414
2418
  #### Async Custom Condition
2415
2419
 
2416
2420
  Invokes a registered async function that returns \`Promise<boolean>\` or \`Observable<boolean>\`:
@@ -2518,7 +2522,7 @@ Available adapters:
2518
2522
 
2519
2523
  1. **Missing labels** - Always include labels for accessibility
2520
2524
  2. **Missing validation messages** - Users need clear feedback
2521
- 3. **Duplicate keys** - Each key must be unique within its scope
2525
+ 3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.
2522
2526
  4. **Select without options** - Select/radio fields must have options
2523
2527
  5. **Array without fields** - Array fields need a fields template
2524
2528
  6. **Group without fields** - Group fields need child fields
@@ -4,5 +4,5 @@
4
4
  * This content is served via the ng-forge://instructions resource
5
5
  * to guide AI assistants in generating FormConfig objects.
6
6
  */
7
- export declare const INSTRUCTIONS = "# ng-forge Dynamic Forms - Best Practices\n\nYou MUST follow these practices when generating FormConfig objects for ng-forge.\n\n## Core Principles\n\n1. **Configuration-driven**: ng-forge wraps Angular Reactive Forms. Define structure declaratively via `FormConfig`, not imperatively.\n2. **Type-safe**: Use TypeScript interfaces for form values. The library provides full type inference.\n3. **Validation-first**: Always include validation and user-friendly error messages.\n\n## FormConfig Structure\n\n```typescript\nconst config: FormConfig = {\n // Optional: Default validation messages for all fields\n defaultValidationMessages: {\n required: 'This field is required',\n email: 'Please enter a valid email',\n minLength: 'Must be at least {{requiredLength}} characters',\n },\n // Required: Array of field definitions\n fields: [\n // ... field definitions\n ],\n};\n```\n\n## Field Definition Rules\n\n### Required Properties\n\nEvery field MUST have:\n\n- `key`: Unique identifier (except for `row` type which is layout-only)\n- `type`: One of the registered field types\n- `label`: Human-readable label (for accessibility)\n\n### Value Fields\n\nFor fields that collect user input (`input`, `textarea`, `select`, `checkbox`, `radio`, `datepicker`, `toggle`, `slider`):\n\n```typescript\n{\n key: 'email',\n type: 'input',\n label: 'Email Address',\n required: true, // Shorthand validator\n email: true, // Shorthand validator\n placeholder: 'user@example.com', // Field-level, not inside props\n validationMessages: { // Always provide clear messages\n required: 'Email is required',\n email: 'Please enter a valid email'\n },\n props: {\n type: 'email', // HTML input type\n hint: 'We will never share your email' // UI library specific\n }\n}\n```\n\n### Select/Radio Fields\n\nMUST include `options` array:\n\n```typescript\n{\n key: 'country',\n type: 'select',\n label: 'Country',\n required: true,\n placeholder: 'Select a country',\n options: [\n { value: 'us', label: 'United States' },\n { value: 'uk', label: 'United Kingdom' }\n ]\n}\n```\n\n### Container Fields\n\n#### Row (Horizontal Layout)\n\nUse for side-by-side fields. Row fields do NOT create nested values - children are flat.\n\n```typescript\n{\n key: 'nameRow',\n type: 'row',\n fields: [\n { key: 'firstName', type: 'input', label: 'First', col: 6 },\n { key: 'lastName', type: 'input', label: 'Last', col: 6 }\n ]\n}\n// Result: { firstName: '...', lastName: '...' }\n```\n\nUse `col` property (1-12) to control column widths.\n\n#### Group (Nested Object)\n\nUse when you need nested object structure in form values:\n\n```typescript\n{\n key: 'address',\n type: 'group',\n fields: [\n { key: 'street', type: 'input', label: 'Street' },\n { key: 'city', type: 'input', label: 'City' }\n ]\n}\n// Result: { address: { street: '...', city: '...' } }\n```\n\n#### Array (Repeatable Items)\n\nUse for dynamic lists. Each item is wrapped in a `<div class=\"df-array-item\">` with `role=\"group\"` and `aria-label=\"Item N\"` for accessibility. Use `--df-array-item-gap` CSS variable to control spacing between items.\n\n```typescript\n{\n key: 'contacts',\n type: 'array',\n // \u26A0\uFE0F NO label property on arrays!\n fields: [\n {\n key: 'contact',\n type: 'group',\n fields: [\n { key: 'name', type: 'input', label: 'Name', required: true },\n { key: 'phone', type: 'input', label: 'Phone', required: true }\n ]\n }\n ]\n}\n// Result: { contacts: [{ name: '...', phone: '...' }, ...] }\n```\n\n#### Page (Multi-step Forms)\n\nUse for wizard-style forms:\n\n```typescript\n{\n fields: [\n {\n key: 'step1',\n type: 'page',\n fields: [\n { key: 'name', type: 'input', label: 'Name' },\n { key: 'next', type: 'next', label: 'Continue' },\n ],\n },\n {\n key: 'step2',\n type: 'page',\n fields: [\n { key: 'prev', type: 'previous', label: 'Back' },\n { key: 'submit', type: 'submit', label: 'Submit' },\n ],\n },\n ];\n}\n```\n\n## Validation\n\n### Shorthand Validators (Preferred)\n\nUse shorthand properties directly on field definitions:\n\n```typescript\n{\n key: 'age',\n type: 'input',\n label: 'Age',\n required: true,\n min: 18,\n max: 120,\n validationMessages: {\n required: 'Age is required',\n min: 'Must be at least {{min}}',\n max: 'Cannot exceed {{max}}'\n }\n}\n```\n\nAvailable shorthands: `required`, `email`, `min`, `max`, `minLength`, `maxLength`, `pattern`\n\n### Nullable Values\n\nValue fields accept a `nullable?: boolean` flag. When `true`:\n\n- `value` accepts `null` in addition to the field's normal value type.\n- An omitted `value` resolves to `null` (instead of the type-specific empty default like `''`, `NaN`, or `[]`).\n- Orthogonal to `required`. `nullable` declares that the **model** accepts `null`; `required` is a **validation** constraint. Angular's `Validators.required` treats `null` as invalid, so a field that is both `nullable` and `required` will fail required-validation when the value is `null` \u2014 the two flags describe different layers (data shape vs. validation), not conflicting semantics.\n- Map OpenAPI `nullable: true` (3.0) or `type: [T, null]` (3.1) to this flag.\n\n```typescript\n{\n key: 'middleName',\n type: 'input',\n label: 'Middle Name',\n nullable: true,\n value: null, // valid; default-resolves to null\n}\n```\n\nRead-side caveat: a user clearing a text input reads back as `\"\"`, not `null` \u2014 this is a DOM/Web IDL contract, identical to classic Reactive Forms. `nullable` is a contract for accepted values, not a guarantee of emitted ones.\n\n### Validation Messages\n\nALWAYS provide validation messages. Use template variables:\n\n- `{{requiredLength}}` - for minLength/maxLength\n- `{{min}}`, `{{max}}` - for min/max\n- `{{requiredPattern}}` - for pattern\n\n## Conditional Logic\n\nUse `logic` array for conditional behavior:\n\n```typescript\n{\n key: 'businessName',\n type: 'input',\n label: 'Business Name',\n logic: [\n {\n type: 'hidden',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'notEquals',\n value: 'business'\n }\n },\n {\n type: 'required',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n }\n }\n ]\n}\n```\n\n### Logic Types\n\n- `hidden` - Show/hide field\n- `disabled` - Enable/disable field\n- `required` - Conditional requirement\n\n### Condition Types\n\n#### Field Value Condition\n\nCompares a specific field's value:\n\n```typescript\n{\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n}\n```\n\n#### Comparison Operators\n\n- `equals`, `notEquals`\n- `greater`, `less`, `greaterOrEqual`, `lessOrEqual`\n- `contains`, `startsWith`, `endsWith`, `matches`\n\n#### JavaScript Condition\n\nEvaluates a JavaScript expression with access to `formValue`, `fieldValue`, `externalData`:\n\n```typescript\n{\n type: 'javascript',\n expression: 'formValue.items.length > 0'\n}\n```\n\n#### Custom Condition\n\nInvokes a registered custom function by name:\n\n```typescript\n{\n type: 'custom',\n functionName: 'myCustomCheck'\n}\n```\n\nRegister functions via `customFnConfig.customFunctions`.\n\n#### Async Custom Condition\n\nInvokes a registered async function that returns `Promise<boolean>` or `Observable<boolean>`:\n\n```typescript\n{\n type: 'async',\n asyncFunctionName: 'checkPermission',\n pendingValue: false, // value while async resolution is pending (default: false)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nRegister functions via `customFnConfig.asyncConditions`. The function receives an `EvaluationContext` with access to `formValue`, `fieldValue`, and `externalData`.\n\n#### HTTP Condition\n\nEvaluates based on an HTTP response from a remote server. Fully declarative and JSON-serializable:\n\n```typescript\n{\n type: 'http',\n http: {\n url: '/api/permissions',\n method: 'GET',\n queryParams: {\n userId: 'formValue.userId',\n role: 'formValue.role'\n }\n },\n responseExpression: 'response.canEdit', // extract boolean from response (default: !!response)\n pendingValue: false, // value while HTTP request is in-flight (default: false)\n cacheDurationMs: 30000, // cache duration in ms (default: 30000)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nUse HTTP conditions when visibility, disabled state, or required state depends on server-side data (e.g., permissions, feature flags, inventory checks). The request re-evaluates reactively when dependent form values change.\n\n#### Logical Combinators\n\nCombine conditions with `and` (all must be true) or `or` (at least one must be true):\n\n```typescript\n{\n type: 'and',\n conditions: [\n { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'US' },\n { type: 'fieldValue', fieldPath: 'age', operator: 'greaterOrEqual', value: 18 }\n ]\n}\n```\n\n## UI Library Integration\n\nng-forge supports multiple UI libraries. Configure in `app.config.ts`:\n\n```typescript\nimport { provideDynamicForm } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [provideDynamicForm(...withMaterialFields())],\n};\n```\n\nAvailable adapters:\n\n- `@ng-forge/dynamic-forms-material` - Angular Material\n- `@ng-forge/dynamic-forms-bootstrap` - Bootstrap 5\n- `@ng-forge/dynamic-forms-primeng` - PrimeNG\n- `@ng-forge/dynamic-forms-ionic` - Ionic\n\n## Common Patterns\n\n### Always End with Submit Button\n\n```typescript\n{ type: 'submit', key: 'submit', label: 'Submit', props: { color: 'primary' } }\n```\n\n### Use Text Fields for Headings\n\n```typescript\n{ key: 'sectionTitle', type: 'text', label: 'Personal Info', props: { elementType: 'h3' } }\n```\n\n### Input Types via Props\n\n```typescript\n// Email input\n{ key: 'email', type: 'input', props: { type: 'email' } }\n\n// Password input\n{ key: 'password', type: 'input', props: { type: 'password' } }\n\n// Number input\n{ key: 'age', type: 'input', props: { type: 'number' } }\n\n// Phone input\n{ key: 'phone', type: 'input', props: { type: 'tel' } }\n```\n\n## Anti-patterns to Avoid\n\n1. **Missing labels** - Always include labels for accessibility\n2. **Missing validation messages** - Users need clear feedback\n3. **Duplicate keys** - Each key must be unique within its scope\n4. **Select without options** - Select/radio fields must have options\n5. **Array without fields** - Array fields need a fields template\n6. **Group without fields** - Group fields need child fields\n7. **min > max** - Ensure min values are less than max values\n\n## CRITICAL: UI Library Differences\n\nDifferent UI libraries support different properties. Always validate against the correct library using `ngforge_validate_form_config` with the appropriate `uiIntegration` parameter.\n\n### Property Differences by Library\n\n| Property | Material | Bootstrap | PrimeNG | Ionic |\n|----------|----------|-----------|---------|-------|\n| `appearance` | \u2713 (fill, outline) | \u2717 | \u2717 | \u2713 (fill, outline) |\n| `subscriptSizing` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `floatLabel` | \u2713 (auto, always, never) | \u2717 | \u2717 | \u2717 |\n| `hideRequiredMarker` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `hint` | \u2713 | \u2713 (as `helpText`) | \u2713 | \u2713 |\n\n### Container Fields\n\nContainer fields (`page`, `group`, `row`) do NOT support these properties:\n- `label` - Use `text` field type for headings instead\n- `required`, `email`, `min`, `max`, etc. - Validation is for leaf fields only\n\n### Common Mistakes to Avoid\n\n1. **Using `expressions` on fields** - This property is NOT supported on standard fields. Use `derivation` for computed values or `logic` for conditional behavior.\n\n2. **Adding `label` to container fields** - Pages, groups, and rows don't have labels:\n ```typescript\n // \u274C WRONG\n { key: 'address', type: 'group', label: 'Address', fields: [...] }\n\n // \u2705 CORRECT - Use a text field for the heading\n { key: 'addressTitle', type: 'text', label: 'Address', props: { elementType: 'h3' } }\n { key: 'address', type: 'group', fields: [...] }\n ```\n\n3. **Adding `id` to FormConfig** - The root FormConfig does not accept an `id` property.\n\n4. **Putting slider min/max in props** - For slider fields, `min`, `max`, and `step` are field-level properties:\n ```typescript\n // \u274C WRONG\n { key: 'volume', type: 'slider', label: 'Volume', props: { min: 0, max: 100 } }\n\n // \u2705 CORRECT\n { key: 'volume', type: 'slider', label: 'Volume', min: 0, max: 100 }\n ```\n\n5. **Using unsupported field types** - Each UI library supports specific field types. Use `ngforge_get_field_schema` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate_form_config` tool before using it. This tool uses the actual TypeScript/Zod schemas, so if validation passes, the config will work correctly at runtime.\n\n```typescript\n// Always specify the UI integration you're using\nngforge_validate_form_config({\n uiIntegration: 'material', // or 'bootstrap', 'primeng', 'ionic'\n config: { fields: [...] }\n})\n```\n\nTo understand what properties are supported for each field type, use `ngforge_get_field_schema`:\n\n```typescript\nngforge_get_field_schema({ uiIntegration: 'material' })\n```\n";
7
+ export declare const INSTRUCTIONS = "# ng-forge Dynamic Forms - Best Practices\n\nYou MUST follow these practices when generating FormConfig objects for ng-forge.\n\n## Core Principles\n\n1. **Configuration-driven**: ng-forge wraps Angular Reactive Forms. Define structure declaratively via `FormConfig`, not imperatively.\n2. **Type-safe**: Use TypeScript interfaces for form values. The library provides full type inference.\n3. **Validation-first**: Always include validation and user-friendly error messages.\n\n## FormConfig Structure\n\n```typescript\nconst config: FormConfig = {\n // Optional: Default validation messages for all fields\n defaultValidationMessages: {\n required: 'This field is required',\n email: 'Please enter a valid email',\n minLength: 'Must be at least {{requiredLength}} characters',\n },\n // Required: Array of field definitions\n fields: [\n // ... field definitions\n ],\n};\n```\n\n## Field Definition Rules\n\n### Required Properties\n\nEvery field MUST have:\n\n- `key`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. `createA.name` and `createB.name`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.\n - **DOM id / data-testid composition:** keys are joined with `_` through group ancestors to form the rendered DOM id. A leaf `street` inside group `address` renders as `id=\"address_street\"`. Inside an array item, the item index is appended after: `id=\"address_street_0\"`. Use this exact form for `for=`, `aria-describedby`, `aria-labelledby`, or CSS selectors that target group-nested fields.\n - **Avoid `_` in keys when they could collide with a group-prefixed id.** A top-level key `foo_bar` and a leaf `bar` inside group `foo` would both render as `id=\"foo_bar\"`. The validator catches this collision at boot, but it's easier to just not put `_` in keys that share a name with an existing group + leaf pair.\n- `type`: One of the registered field types\n- `label`: Human-readable label (for accessibility)\n\n### Value Fields\n\nFor fields that collect user input (`input`, `textarea`, `select`, `checkbox`, `radio`, `datepicker`, `toggle`, `slider`):\n\n```typescript\n{\n key: 'email',\n type: 'input',\n label: 'Email Address',\n required: true, // Shorthand validator\n email: true, // Shorthand validator\n placeholder: 'user@example.com', // Field-level, not inside props\n validationMessages: { // Always provide clear messages\n required: 'Email is required',\n email: 'Please enter a valid email'\n },\n props: {\n type: 'email', // HTML input type\n hint: 'We will never share your email' // UI library specific\n }\n}\n```\n\n### Select/Radio Fields\n\nMUST include `options` array:\n\n```typescript\n{\n key: 'country',\n type: 'select',\n label: 'Country',\n required: true,\n placeholder: 'Select a country',\n options: [\n { value: 'us', label: 'United States' },\n { value: 'uk', label: 'United Kingdom' }\n ]\n}\n```\n\n### Container Fields\n\n#### Row (Horizontal Layout)\n\nUse for side-by-side fields. Row fields do NOT create nested values - children are flat.\n\n```typescript\n{\n key: 'nameRow',\n type: 'row',\n fields: [\n { key: 'firstName', type: 'input', label: 'First', col: 6 },\n { key: 'lastName', type: 'input', label: 'Last', col: 6 }\n ]\n}\n// Result: { firstName: '...', lastName: '...' }\n```\n\nUse `col` property (1-12) to control column widths.\n\n#### Group (Nested Object)\n\nUse when you need nested object structure in form values:\n\n```typescript\n{\n key: 'address',\n type: 'group',\n fields: [\n { key: 'street', type: 'input', label: 'Street' },\n { key: 'city', type: 'input', label: 'City' }\n ]\n}\n// Result: { address: { street: '...', city: '...' } }\n```\n\n#### Array (Repeatable Items)\n\nUse for dynamic lists. Each item is wrapped in a `<div class=\"df-array-item\">` with `role=\"group\"` and `aria-label=\"Item N\"` for accessibility. Use `--df-array-item-gap` CSS variable to control spacing between items.\n\n```typescript\n{\n key: 'contacts',\n type: 'array',\n // \u26A0\uFE0F NO label property on arrays!\n fields: [\n {\n key: 'contact',\n type: 'group',\n fields: [\n { key: 'name', type: 'input', label: 'Name', required: true },\n { key: 'phone', type: 'input', label: 'Phone', required: true }\n ]\n }\n ]\n}\n// Result: { contacts: [{ name: '...', phone: '...' }, ...] }\n```\n\n#### Page (Multi-step Forms)\n\nUse for wizard-style forms:\n\n```typescript\n{\n fields: [\n {\n key: 'step1',\n type: 'page',\n fields: [\n { key: 'name', type: 'input', label: 'Name' },\n { key: 'next', type: 'next', label: 'Continue' },\n ],\n },\n {\n key: 'step2',\n type: 'page',\n fields: [\n { key: 'prev', type: 'previous', label: 'Back' },\n { key: 'submit', type: 'submit', label: 'Submit' },\n ],\n },\n ];\n}\n```\n\n## Validation\n\n### Shorthand Validators (Preferred)\n\nUse shorthand properties directly on field definitions:\n\n```typescript\n{\n key: 'age',\n type: 'input',\n label: 'Age',\n required: true,\n min: 18,\n max: 120,\n validationMessages: {\n required: 'Age is required',\n min: 'Must be at least {{min}}',\n max: 'Cannot exceed {{max}}'\n }\n}\n```\n\nAvailable shorthands: `required`, `email`, `min`, `max`, `minLength`, `maxLength`, `pattern`\n\n### Nullable Values\n\nValue fields accept a `nullable?: boolean` flag. When `true`:\n\n- `value` accepts `null` in addition to the field's normal value type.\n- An omitted `value` resolves to `null` (instead of the type-specific empty default like `''`, `NaN`, or `[]`).\n- Orthogonal to `required`. `nullable` declares that the **model** accepts `null`; `required` is a **validation** constraint. Angular's `Validators.required` treats `null` as invalid, so a field that is both `nullable` and `required` will fail required-validation when the value is `null` \u2014 the two flags describe different layers (data shape vs. validation), not conflicting semantics.\n- Map OpenAPI `nullable: true` (3.0) or `type: [T, null]` (3.1) to this flag.\n\n```typescript\n{\n key: 'middleName',\n type: 'input',\n label: 'Middle Name',\n nullable: true,\n value: null, // valid; default-resolves to null\n}\n```\n\nRead-side caveat: a user clearing a text input reads back as `\"\"`, not `null` \u2014 this is a DOM/Web IDL contract, identical to classic Reactive Forms. `nullable` is a contract for accepted values, not a guarantee of emitted ones.\n\n### Validation Messages\n\nALWAYS provide validation messages. Use template variables:\n\n- `{{requiredLength}}` - for minLength/maxLength\n- `{{min}}`, `{{max}}` - for min/max\n- `{{requiredPattern}}` - for pattern\n\n## Conditional Logic\n\nUse `logic` array for conditional behavior:\n\n```typescript\n{\n key: 'businessName',\n type: 'input',\n label: 'Business Name',\n logic: [\n {\n type: 'hidden',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'notEquals',\n value: 'business'\n }\n },\n {\n type: 'required',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n }\n }\n ]\n}\n```\n\n### Logic Types\n\n- `hidden` - Show/hide field\n- `disabled` - Enable/disable field\n- `required` - Conditional requirement\n\n### Condition Types\n\n#### Field Value Condition\n\nCompares a specific field's value:\n\n```typescript\n{\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n}\n```\n\n#### Comparison Operators\n\n- `equals`, `notEquals`\n- `greater`, `less`, `greaterOrEqual`, `lessOrEqual`\n- `contains`, `startsWith`, `endsWith`, `matches`\n\n#### JavaScript Condition\n\nEvaluates a JavaScript expression with access to `formValue`, `fieldValue`, `externalData`:\n\n```typescript\n{\n type: 'javascript',\n expression: 'formValue.items.length > 0'\n}\n```\n\n#### Custom Condition\n\nInvokes a registered custom function by name:\n\n```typescript\n{\n type: 'custom',\n functionName: 'myCustomCheck'\n}\n```\n\nRegister functions via `customFnConfig.customFunctions`.\n\n> **TypeScript-authored configs:** the same surface also accepts an inline `fn: CustomFunction` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable). The MCP server emits `functionName` exclusively; consumers writing configs in TS may prefer `fn` when they don't need JSON round-tripping. Same caveat applies to async conditions (`asyncFn`), function derivations (`fn`), async function derivations (`asyncFn`), and validators (`fn`).\n\n#### Async Custom Condition\n\nInvokes a registered async function that returns `Promise<boolean>` or `Observable<boolean>`:\n\n```typescript\n{\n type: 'async',\n asyncFunctionName: 'checkPermission',\n pendingValue: false, // value while async resolution is pending (default: false)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nRegister functions via `customFnConfig.asyncConditions`. The function receives an `EvaluationContext` with access to `formValue`, `fieldValue`, and `externalData`.\n\n#### HTTP Condition\n\nEvaluates based on an HTTP response from a remote server. Fully declarative and JSON-serializable:\n\n```typescript\n{\n type: 'http',\n http: {\n url: '/api/permissions',\n method: 'GET',\n queryParams: {\n userId: 'formValue.userId',\n role: 'formValue.role'\n }\n },\n responseExpression: 'response.canEdit', // extract boolean from response (default: !!response)\n pendingValue: false, // value while HTTP request is in-flight (default: false)\n cacheDurationMs: 30000, // cache duration in ms (default: 30000)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nUse HTTP conditions when visibility, disabled state, or required state depends on server-side data (e.g., permissions, feature flags, inventory checks). The request re-evaluates reactively when dependent form values change.\n\n#### Logical Combinators\n\nCombine conditions with `and` (all must be true) or `or` (at least one must be true):\n\n```typescript\n{\n type: 'and',\n conditions: [\n { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'US' },\n { type: 'fieldValue', fieldPath: 'age', operator: 'greaterOrEqual', value: 18 }\n ]\n}\n```\n\n## UI Library Integration\n\nng-forge supports multiple UI libraries. Configure in `app.config.ts`:\n\n```typescript\nimport { provideDynamicForm } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [provideDynamicForm(...withMaterialFields())],\n};\n```\n\nAvailable adapters:\n\n- `@ng-forge/dynamic-forms-material` - Angular Material\n- `@ng-forge/dynamic-forms-bootstrap` - Bootstrap 5\n- `@ng-forge/dynamic-forms-primeng` - PrimeNG\n- `@ng-forge/dynamic-forms-ionic` - Ionic\n\n## Common Patterns\n\n### Always End with Submit Button\n\n```typescript\n{ type: 'submit', key: 'submit', label: 'Submit', props: { color: 'primary' } }\n```\n\n### Use Text Fields for Headings\n\n```typescript\n{ key: 'sectionTitle', type: 'text', label: 'Personal Info', props: { elementType: 'h3' } }\n```\n\n### Input Types via Props\n\n```typescript\n// Email input\n{ key: 'email', type: 'input', props: { type: 'email' } }\n\n// Password input\n{ key: 'password', type: 'input', props: { type: 'password' } }\n\n// Number input\n{ key: 'age', type: 'input', props: { type: 'number' } }\n\n// Phone input\n{ key: 'phone', type: 'input', props: { type: 'tel' } }\n```\n\n## Anti-patterns to Avoid\n\n1. **Missing labels** - Always include labels for accessibility\n2. **Missing validation messages** - Users need clear feedback\n3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.\n4. **Select without options** - Select/radio fields must have options\n5. **Array without fields** - Array fields need a fields template\n6. **Group without fields** - Group fields need child fields\n7. **min > max** - Ensure min values are less than max values\n\n## CRITICAL: UI Library Differences\n\nDifferent UI libraries support different properties. Always validate against the correct library using `ngforge_validate_form_config` with the appropriate `uiIntegration` parameter.\n\n### Property Differences by Library\n\n| Property | Material | Bootstrap | PrimeNG | Ionic |\n|----------|----------|-----------|---------|-------|\n| `appearance` | \u2713 (fill, outline) | \u2717 | \u2717 | \u2713 (fill, outline) |\n| `subscriptSizing` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `floatLabel` | \u2713 (auto, always, never) | \u2717 | \u2717 | \u2717 |\n| `hideRequiredMarker` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `hint` | \u2713 | \u2713 (as `helpText`) | \u2713 | \u2713 |\n\n### Container Fields\n\nContainer fields (`page`, `group`, `row`) do NOT support these properties:\n- `label` - Use `text` field type for headings instead\n- `required`, `email`, `min`, `max`, etc. - Validation is for leaf fields only\n\n### Common Mistakes to Avoid\n\n1. **Using `expressions` on fields** - This property is NOT supported on standard fields. Use `derivation` for computed values or `logic` for conditional behavior.\n\n2. **Adding `label` to container fields** - Pages, groups, and rows don't have labels:\n ```typescript\n // \u274C WRONG\n { key: 'address', type: 'group', label: 'Address', fields: [...] }\n\n // \u2705 CORRECT - Use a text field for the heading\n { key: 'addressTitle', type: 'text', label: 'Address', props: { elementType: 'h3' } }\n { key: 'address', type: 'group', fields: [...] }\n ```\n\n3. **Adding `id` to FormConfig** - The root FormConfig does not accept an `id` property.\n\n4. **Putting slider min/max in props** - For slider fields, `min`, `max`, and `step` are field-level properties:\n ```typescript\n // \u274C WRONG\n { key: 'volume', type: 'slider', label: 'Volume', props: { min: 0, max: 100 } }\n\n // \u2705 CORRECT\n { key: 'volume', type: 'slider', label: 'Volume', min: 0, max: 100 }\n ```\n\n5. **Using unsupported field types** - Each UI library supports specific field types. Use `ngforge_get_field_schema` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate_form_config` tool before using it. This tool uses the actual TypeScript/Zod schemas, so if validation passes, the config will work correctly at runtime.\n\n```typescript\n// Always specify the UI integration you're using\nngforge_validate_form_config({\n uiIntegration: 'material', // or 'bootstrap', 'primeng', 'ionic'\n config: { fields: [...] }\n})\n```\n\nTo understand what properties are supported for each field type, use `ngforge_get_field_schema`:\n\n```typescript\nngforge_get_field_schema({ uiIntegration: 'material' })\n```\n";
8
8
  //# sourceMappingURL=instructions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/instructions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,YAAY,49aA6dxB,CAAC"}
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/instructions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,YAAY,q7dAiexB,CAAC"}