RFC: Qontak CRM Actions — Custom Fields on Create Deal / Create Ticket — Frontend RFC
Document Conventions (do not remove)
This RFC follows the Qontak RFC Template format for governance — the metadata table, Confluence sections 1–6, and Comment logs are mandatory. Replace placeholder values; mark sections
N/A — reasonwhen truly inapplicable rather than deleting them.It is also agent-execution-ready: the §1 Design References, §2 Repo Reading Guide (Detail 2.0), mermaid diagrams, and §4 Agent Execution Plan
- Verification & Rollback Recipe must be complete before §7 Ready for agent execution: yes.
Delivery & project management live elsewhere. This RFC is the technical artifact only — no staffing, effort, timeline, or rollout schedule here. Those live in
../delivery/. Until this RFC is handed to delivery, the Delivery row readsnot yet handed to delivery.Cross-layer dependency: This RFC consumes the Backend RFC (
crm-actions-custom-fields.md) which isReady for agent execution: YESas of 2026-07-15. BE Chunks 1–5 must be deployed before FE Chunks 1–4 can be integration-tested end-to-end. BE Chunks can be shipped behind the flag (OFF) so FE dev proceeds in parallel.
Metadata
| Field | Value | Notes |
|---|---|---|
| Status | IDEA | Human label; YAML status: draft |
| DRI | [FE tech lead — to be assigned] | Single accountable owner |
| Team | chatbot | BOT — Chatbot & AI Squad |
| Author(s) | [FE tech lead] | |
| Reviewers | agus.suparman@mekari.com, [FE chapter lead] | BE RFC author for cross-layer alignment |
| Approver(s) | [FE engineering manager], [infosec approver] | |
| Submitted Date | 2026-07-16 | |
| Last Updated | 2026-07-16 | |
| Target Release | 2026-Q3 | |
| Target Quarter | 2026-Q3 | |
| Delivery | not yet handed to delivery | |
| Related | ../prds/crm-actions-custom-fields.md, ./crm-actions-custom-fields.md (BE RFC) | |
| Discussion | [Slack channel — to be added] |
Type: frontend
Sub-type: enhancement
Sections at a Glance
- Overview (Design References, PRD Traceability, Decisions Closed, Per-Story Change Map)
- Technical Design (Repo Reading Guide → architecture → sequence → UI contracts)
- High-Availability & Security
- Backwards Compatibility and Rollout Plan (Agent Execution Plan + Verification & Rollback Recipe)
- Concern, Questions, or Known Limitations
- Comment logs
- Ready for agent execution
1. Overview
This RFC covers the chatbot-fe changes only for the CRM custom-fields adjustment. The
create deal and create ticket action-config surfaces in the AI Agent config drawer gain:
- A grouped Add-field picker — Standard fields (unchanged) plus a Custom fields group
populated from the org's CRM additional-field definitions (fetched via the node-resources
lookup API using the new
additional_field_deal/additional_field_ticketresource keys added by the BE RFC). - Additional-field rows render using the existing
ActionIntegrationForm.vueadaptive renderer — no new FE variant per CRM type (PRD D-7 / BEAdditionalFieldNormalizercontract). - Array-typed additional fields reuse
ActionMultiSelectField(html.element: 'select multiple'). - Additional fields are saved as
is_additional_field: trueentries insideparameters.arguments— consistent with the actual storage shape confirmed by the BE RFC.
Scope note — org-level, not pipeline-scoped. The BE RFC (Decision 3.7) confirmed that CRM
additional fields are org-level (team_id-scoped), not pipeline-scoped. PRD D-1/D-2's
pipeline gate ("choose a pipeline first") is not implemented in this RFC. The Custom group
loads on form open, regardless of pipeline selection. PRD amendment is tracked as open item A-4.
Success Criteria
- Builder can add any of the org's CRM additional fields to
create deal/create ticketconfig and save a complete mapping. - Array additional fields (dropdown,
field_type_id: 2) render as multi-select chips usingActionMultiSelectField. - The
is_additional_field: trueparameter shape stored by this FE is exactly what the BEmerge_additional_field_arguments(BE RFC Chunk 4) reads. ai_agent_action_custom_fieldsfeature-flag OFF → no Custom group shown; existing config unchanged.- No regression on standard-field config for any action type.
Out of Scope
All PRD non-goals apply. Additionally:
update deal/update ticketcustom fields — separate future PRD.- PRD S03 BE merge — covered entirely by the BE RFC.
- Production Figma frames — wireframes +
qontak-designerprototype are the build reference. - Pipeline-scoped filtering of additional fields — org-level per BE Decision 3.7; PRD D-1/D-2 pipeline gate not implemented pending A-4 PM confirmation.
- PRD S04 pruning on pipeline change — additional fields are org-level so there is nothing to prune; QACF-S04 ACs are met trivially. See §5 Open Questions OQ-1.
Related Documents
- PRD:
../prds/crm-actions-custom-fields.mdv1.4 - BE RFC:
./crm-actions-custom-fields.md— Ready for agent execution: YES (2026-07-15) - Wireframes:
../documentations/crm-actions-custom-fields-wireframes.md - Parent PRD:
../prds/qontak-crm-actions.md - Anchor:
../qontak-action-anchor.md
Assumptions
- BE RFC Chunks 1–5 are deployed (flag OFF) before FE integration testing.
node_registry.settings.custom_fields_enabledistrueforqontak_crm_deal_createandqontak_crm_ticket_createonce BE Chunk 5 ships (seed). The FE uses this flag to show/hide the Custom group.POST /v1/node-resources/lookupwithresource_key: additional_field_deal/additional_field_ticketreturns the normalized descriptor array (BE RFC §5.3 shape).- The existing
ActionIntegrationForm.vueadaptive renderer branches onhtml.elementandhtml.type— confirmed by ground-prd 2026-07-13. No new branch is needed for additional fields; the normalizer maps every supported CRM type to an existinghtml.elementvalue. MpInputTag/html.element: 'input-tag'does not exist inchatbot-fe. The correct array control isActionMultiSelectField(html.element: 'select multiple').
Dependencies
| Dependency | Owner | Status | Blocking |
|---|---|---|---|
| BE RFC Chunks 1–5 (flag, normalizer, lookup, execute, seed) | chatbot BE | Ready for agent execution 2026-07-15 | YES — integration only; FE dev is parallel |
POST /v1/node-resources/lookup — new resource keys additional_field_deal, additional_field_ticket | chatbot BE (Chunk 3) | In BE RFC | YES |
node_registry.settings.custom_fields_enabled seed | chatbot BE (Chunk 5) | In BE RFC | YES — show/hide gate |
Feature flag ai_agent_action_custom_fields per-org rollout | chatbot BE (Chunk 1) | In BE RFC | YES — gating |
| Production Figma frames | Design | TBD | NO — wireframes sufficient for build |
@mekari/mekaui version confirming ActionMultiSelectField API | chatbot-fe package.json | Verify at start | NO |
Design References (frontend-specific — required)
| PRD-named surface | Figma / design link | Frame name | Design system version | Design QA contact | Notes |
|---|---|---|---|---|---|
| Add-field picker — Standard + Custom groups | In-repo wireframes (Screen 1 & 4) | AddFieldPicker — grouped | [VERIFY: @mekari/mekaui version from chatbot-fe/package.json] | [Design QA — to be assigned] | Production Figma TBD; wireframes + qontak-designer prototype are the build reference per wireframes doc |
| Array custom field row — two modes | Wireframes Screen 2 | ArrayFieldRow — AI / manual | same | same | AI: disabled reads "AI will select one or more values"; Manual: ActionMultiSelectField chips |
| Create deal parity | Wireframes Screen 3 | Deal picker parity | same | same | Tags field (existing array) as precedent for the array UX |
| Pipeline-scoped hint | Wireframes Screen 4 | Pipeline gate hint | same | same | Not implemented — org-level fields per BE Decision 3.7 + A-4 open |
Detail 1.A — PRD Traceability Matrix
Forward (PRD AC → RFC):
| PRD composite AC id | RFC section | Component / file |
|---|---|---|
| QACF-S01/AC-1 | §2.A, §4.C Chunk 2 | ActionIntegrationForm.vue — Custom group rendered when settings.custom_fields_enabled |
| QACF-S01/AC-2 | §2.A, §4.C Chunk 2 | ActionIntegrationForm.vue — array-type badge via property.is_array or type: 'array' |
| QACF-S01/AC-3 | §2.A, §4.C Chunk 3 | ActionIntegrationForm.vue — field added to config, defaults to use_ai: true, removed from picker |
| QACF-S01/AC-4 | §2.A, §5 OQ-1 | n/a — not implemented (org-level fields; pipeline gate removed per BE Decision 3.7) |
| QACF-S01/AC-5 | §2.A, §4.C Chunk 2 | Unsupported types excluded by normalizer before FE receives data; no FE code needed |
| QACF-S01/ERR-1 | §2.C, §3 Detail 3.A | ActionIntegrationForm.vue — error/empty state + retry for lookup failure |
| QACF-S02/AC-1 | §2.A, §4.C Chunk 3 | ActionMultiSelectField chips for html.element: 'select multiple' |
| QACF-S02/AC-2 | §2.A, §4.C Chunk 3 | Disabled control + "AI will select one or more values" label |
| QACF-S02/AC-3 | n/a — runtime | BE RFC Chunk 4 — agent constrained to allowed_options at runtime |
| QACF-S02/AC-4 | §2.A | Existing renderer handles non-array types; no new FE code |
| QACF-S02/ERR-1 | §2.A | ActionMultiSelectField — options are fixed_items from normalizer; free-value entry not possible |
| QACF-S03/* | n/a — BE-only | BE RFC Chunk 4 |
| QACF-S04/AC-1 | §5 OQ-1 | Org-level fields — no pruning needed; AC met trivially |
| QACF-S04/AC-2 | §5 OQ-1 | Org-level fields — Custom group header stays the same across pipeline changes |
| QACF-S01-NEG/NEG-1 | §2.D | update deal/update ticket — Custom group not added (scope boundary) |
| QACF-S01-NEG/NEG-2 | §5 OQ-1 | Pipeline gate removed; AC-4 not implemented |
| QACF-S01-NEG/NEG-3 | §5 OQ-1 | Org-level: no cross-pipeline union to prevent |
| QACF-S01-NEG/NEG-4 | §2.D | Picker is read-only; no CRM field creation surface |
Reverse (RFC → PRD AC):
| New component / RFC decision | PRD composite AC id driving it |
|---|---|
Custom group in Add-field picker (settings.custom_fields_enabled gate) | QACF-S01/AC-1 |
is_additional_field: true in saved parameters.arguments | QACF-S01/AC-3, QACF-S03/AC-1 |
ActionMultiSelectField for html.element: 'select multiple' fields | QACF-S02/AC-1 |
| Lookup error state + retry | QACF-S01/ERR-1 |
UI / Consumer Surface Coverage
| PRD-named surface | Consumer | Required reads (BE endpoint) | Required writes (BE endpoint) | Status surface |
|---|---|---|---|---|
| Add-field picker — Custom group | web (AI Agent config drawer) | POST /v1/node-resources/lookup {resource_key: additional_field_deal} or additional_field_ticket | — | loading / empty / error / success states |
| Custom field config row (all types) | web | — | config saved to ai_agent_actions.parameters.arguments | field present in saved config |
| Array field row (multi-select chips) | web | resource_types[0].items from lookup response | value(s) stored in parameters.arguments.<field_name>.value | chip count |
Role Coverage
| PRD role | UI surface visibility | Action buttons enabled | Auth scope expected from BE | Notes |
|---|---|---|---|---|
| Admin / Bot Manager | Full custom-field picker + config | Add, remove, set AI/manual mode | Existing action config auth; ai_agent_action_custom_fields flag | |
| Roles without AI Agent edit rights | Picker not shown | None | — | Same as existing standard-field gate |
| AI Agent (runtime) | N/A — no config UI | N/A | N/A | Runtime fill is BE-only (BE RFC Chunk 4) |
PRD Section Coverage
| PRD section # | Title | Where covered |
|---|---|---|
| 2 | Adjustment Context | §1 Overview |
| 3 | One-liner + Problem | §1 Overview |
| 4 | Target Users | §1 Overview, Role Coverage above |
| 5 | Non-Goals | §1 Out of Scope |
| 6 | Constraints / Feature flag | §4 Detail 4.A Configuration Contract |
| 7 | Feature Changes (CHG-001, CHG-002) | §2 Detail 2.A UI Contract, §4.C Chunks 1–3 |
| 7.1 | Type → FE component mapping | §2.0 Patterns, §2.A — no new FE variants needed |
| 8 | API behavior #1 — load additional fields | §2.4 APIs, §2.B Data-Fetching, §3 Detail 3.A |
| 8 | API behavior #2/#3 — create with custom fields | n/a — BE RFC §5.1 |
| 8 | API behavior #4 — AI array fill | n/a — BE RFC Chunk 4; FE saves use_ai: true |
| 9.1 | System Flow | §2.2 Sequence diagrams |
| 9.2 | User Stories S01, S02, S04 | §1 Detail 1.C, §2.A, §4.C |
| 9.2 | User Story S03 | n/a — BE RFC |
| 10 | Rollout | §4 Rollout Strategy |
| 11 | Observability | §3 Monitoring & Alerting |
| 14 | Dependencies | §1 Dependencies |
| 15 | Key Decisions | §1 Detail 1.B |
| 16 | Open Questions | §5 |
Detail 1.B — Decisions Closed
| Decision | Chosen option | Alternatives rejected | Why rejected |
|---|---|---|---|
| FE-D1: Pipeline gate for Custom group | Not implemented — Custom group loads on form open (org-level fields) | Keep PRD D-1/D-2 gate (require pipeline set manually) | BE Decision 3.7 confirmed fields are org-level; pipeline gate withholds always-available data. PRD amendment pending A-4. |
| FE-D2: Where to show/hide Custom group | node_registry.settings.custom_fields_enabled === true (set by BE Chunk 5 seed) | Client-side ai_agent_action_custom_fields flag check | Node registry is already read for action config rendering; reusing it avoids a second auth check. Feature flag controls which orgs get the seed applied. |
| FE-D3: Storage shape for additional fields | is_additional_field: true entries inline in parameters.arguments | Separate parameters.custom_fields key | Confirmed by BE RFC Decision 3.1 from actual FE parameter sample — existing FE already stores them this way. |
| FE-D4: Array control | ActionMultiSelectField (html.element: 'select multiple') | MpInputTag / html.element: 'input-tag' | input-tag does not exist in chatbot-fe (ground-prd 2026-07-13). ActionMultiSelectField is already used for deal tags. |
| FE-D5: Per-type FE renderer | No new variant — existing adaptive switch in ActionIntegrationForm.vue handles all supported types | Per-type FE components for each CRM field type | PRD D-7 / BE normalizer contract: normalizer maps every CRM field_type_id to an existing html.element/html.type. New CRM type = one normalizer row, zero FE releases. |
| FE-D6: S04 pipeline pruning | No pruning implemented — fields are org-level, not pipeline-scoped | Remove added fields on pipeline change | With org-level fields all pipelines share the same additional-field set; pruning has nothing to act on. See §5 OQ-1. |
Detail 1.C — Per-Story Change Map
| Story id | Story title | Layer scope | Changes (FE artifacts) | Composite AC ids covered | Acceptance criteria (verifiable) | RFC anchors |
|---|---|---|---|---|---|---|
| QACF-S01 | Pipeline-scoped custom-field picker | FE + BE existing | ActionIntegrationForm.vue: Custom group section; lookup call for additional_field_deal/additional_field_ticket; loading/error/empty states | QACF-S01/AC-1, AC-2, AC-3, AC-5, ERR-1 | Custom group renders when settings.custom_fields_enabled; unsupported types absent; lookup error shows retry; unit test passes | §2.A, §2.B, §2.C, §3 Detail 3.A, §4.C Chunks 1–2 |
| QACF-S02 | Array-type custom field — AI-fill and manual | FE + BE existing | ActionIntegrationForm.vue: array field row renders ActionMultiSelectField; use_ai: true → disabled control with label | QACF-S02/AC-1, AC-2, AC-4, ERR-1 | Multi-select chips rendered for html.element: 'select multiple'; AI mode shows disabled placeholder; free-value entry not possible | §2.A, §4.C Chunk 3 |
| QACF-S03 | Merge custom fields into CRM create | BE-only | n/a — BE RFC Chunk 4 | QACF-S03/* | n/a — covered in BE RFC | BE RFC §4 Chunk 4 |
| QACF-S04 | Prune stale custom fields on pipeline change | FE (simplified — no-op) | No code change — fields are org-level; no stale fields exist on pipeline change. ACs met trivially. | QACF-S04/AC-1, AC-2 | Custom group unchanged after pipeline change; no pruning code path | §5 OQ-1 |
| QACF-S01-NEG | Scope guard rails | FE | Scope boundary — update deal/update ticket forms must NOT receive Custom group; no cross-pipeline union | QACF-S01-NEG/NEG-1, NEG-3, NEG-4 | Custom group absent in update-action forms (verify via snapshot); no field-creation affordance | §2.D Scope Boundaries |
2. Technical Design
Detail 2.0 — Repo Reading Guide
The agent reads these files before writing any code. All paths are in
chatbot-fe. Evidence rows marked "(PRD ground-prd 2026-07-13)" were verified in the ground-prd pass againstchatbot-fe@main; other rows are to be verified by the agent on first read.
Repo Map (mermaid)
flowchart LR
subgraph aiagent["modules/ai-agent/"]
form["components/forms/action/ActionIntegrationForm.vue"]
multiselect["components/forms/action/ActionMultiSelectField.vue"]
constants["constants/ai-agent-actions-constants.ts"]
end
form --> multiselect
form --> constants
form -->|"POST /v1/node-resources/lookup"| api["chatbot API"]
Existing Code Anchors
| Path | Why the agent reads it | What pattern it teaches |
|---|---|---|
modules/ai-agent/components/forms/action/ActionIntegrationForm.vue | Main adaptive renderer — the file this RFC extends | html.element / html.type switch; ActionMultiSelectField mount at :126-140; depends_on pipeline-stage wiring at :117-121; is_rl resource lookup |
modules/ai-agent/constants/ai-agent-actions-constants.ts:75-432 | Static ACTION_LIST / PropertiesItem definitions for all existing actions | PropertiesItem shape: name, type, html, is_rl, resource_types, depends_on; existing array usage at :175 (deal tags) |
modules/ai-agent/components/forms/action/ActionMultiSelectField.vue | Existing multi-value chip control | Props, emit shape, how resource_types[0].items drives chip options |
[resource-lookup composable or store] | How the form calls POST /v1/node-resources/lookup for pipeline/stage | Fetch pattern, error handling, loading state |
Note on the resource-lookup composable path: the exact file path is not in the PRD evidence; the agent must
grep -r "node-resources/lookup" modules/ai-agent/to find it before Chunk 1.
Patterns to Follow
| Concern | Pattern in repo | Reference file | Deviation in this RFC? |
|---|---|---|---|
| Adaptive field renderer | html.element switch in ActionIntegrationForm.vue | ActionIntegrationForm.vue:95-350 (approx) | None — additional fields reuse existing branches |
| Array field control | ActionMultiSelectField mounted when html.element === 'select multiple' | ActionIntegrationForm.vue:126-140 | None |
| Resource lookup (pipeline, stage) | Composable/store calls POST /v1/node-resources/lookup | [VERIFY: composable path from grep] | None — same call, new resource_key values |
is_rl dropdown loading | resource_types[0].items populated from lookup response | ActionIntegrationForm.vue + ai-agent-actions-constants.ts:189 | None |
depends_on dependent picker | Stage loads after pipeline selection | ActionIntegrationForm.vue:117-121 | None — additional fields have no depends_on |
| Error / retry state | [VERIFY from ActionIntegrationForm.vue] | [VERIFY] | None |
Reading Order for the Agent
modules/ai-agent/constants/ai-agent-actions-constants.ts:75-432— learnPropertiesItemshape and existingtype: 'array'+html.element: 'select multiple'usage (dealtags)modules/ai-agent/components/forms/action/ActionIntegrationForm.vue— learn adaptive renderer switch,is_rllookup,ActionMultiSelectFieldmount,depends_onmodules/ai-agent/components/forms/action/ActionMultiSelectField.vue— learn props/emits[resource-lookup composable — grep result]— learn how existingPOST /v1/node-resources/lookupcalls are made and cachedchatbot-fe/package.json— learn test and build commands before writing Detail 4.B
Source Verification (anti-hallucination — required)
| Anchor / pattern / contract | Verified by | Evidence |
|---|---|---|
ActionIntegrationForm.vue:126-140 — ActionMultiSelectField for select multiple | PRD ground-prd 2026-07-13 | "html.element: 'select multiple'→ActionMultiSelectField" cited in PRD §8 evidence note and PRD §7.1 |
ai-agent-actions-constants.ts:175 — PropertiesItem.type: 'array' for deal tags | PRD ground-prd 2026-07-13 | PRD §8: "PropertiesItem.type: 'array' (used by deal tags, ai-agent-actions-constants.ts:175)" |
ai-agent-actions-constants.ts:189 — resource_types[].multi_select | PRD ground-prd 2026-07-13 | PRD §8: "resource_types[].multi_select (:189)" |
ActionIntegrationForm.vue:117-121 — depends_on Stage-on-Pipeline | PRD ground-prd 2026-07-13 | PRD §8: "depends_on dependent-picker (Stage-on-Pipeline, :117-121)" |
html.element: 'input-tag' does NOT exist in chatbot-fe | PRD ground-prd 2026-07-13 | PRD §8 correction note: "html.element: 'input-tag' does not exist in chatbot-fe — prod uses 'select multiple' / ActionMultiSelectField" |
POST /v1/node-resources/lookup response shape for additional_field_deal | BE RFC §5.3 (2026-07-15) | Confirmed: {status, data: [{name, value, property: {is_additional_field: true, html, is_rl, type, display_name, resource_types}}]} |
node_registry.settings.custom_fields_enabled as FE gate | BE RFC Chunk 5 | BE seed sets settings['custom_fields_enabled'] = true on qontak_crm_deal_create + qontak_crm_ticket_create |
Additional fields stored in parameters.arguments with is_additional_field: true | BE RFC Decision 3.1 + confirmed parameter sample | PRD §7.1 / BE RFC §5.4 parameter shape |
| Resource-lookup composable/store path | [REQUIRED — agent reads via grep] | grep -r "node-resources/lookup" modules/ai-agent/ |
Design ↔ Code Mapping
| Figma frame / component | Implementing file (path) | Reuse vs new | Design tokens used | Deviation from design |
|---|---|---|---|---|
| Add-field picker — Standard + Custom groups | ActionIntegrationForm.vue (extended) | extended | [VERIFY from design system] | Prototype used MpInputTag; production uses ActionMultiSelectField (confirmed ground-prd) |
| Array field row | ActionIntegrationForm.vue + ActionMultiSelectField.vue (reused) | reused | same | none — same chip control used for deal tags today |
| Loading / error / empty states | ActionIntegrationForm.vue | reused pattern | same | none |
Detail 2.1 — Architecture (mermaid)
Component diagram
flowchart TB
builder([Builder])
builder --> form["ActionIntegrationForm.vue"]
form --> picker["Add-field picker (Custom group)"]
form --> multiselect["ActionMultiSelectField.vue"]
form --> lookup["resource-lookup composable"]
lookup -->|"POST /v1/node-resources/lookup"| api["chatbot API"]
api -->|"normalized additional fields"| lookup
form -->|"parameters.arguments"| config[("saved action config")]
State machine — Custom group loading
stateDiagram-v2
[*] --> idle
idle --> loading : form opens, custom_fields_enabled is true
loading --> success : lookup returns data
loading --> empty : lookup returns empty array
loading --> error : lookup fails or times out
error --> loading : builder clicks retry
success --> [*]
empty --> [*]
Detail 2.2 — Sequence (mermaid)
Happy path — builder adds a custom field
sequenceDiagram
actor Builder as Builder
participant Form as ActionIntegrationForm.vue
participant Lookup as resource-lookup composable
participant API as chatbot API
Builder->>Form: opens create deal config
Form->>Lookup: fetch additional_field_deal
Lookup->>API: POST /v1/node-resources/lookup, resource_key=additional_field_deal
API-->>Lookup: 200 — normalized field descriptors
Lookup-->>Form: field descriptors
Form-->>Builder: picker shows Standard + Custom groups
Builder->>Form: selects a custom field from Custom group
Form-->>Builder: field row added, default use_ai true
Builder->>Form: saves config
Form-->>Builder: parameters.arguments updated with is_additional_field entry
Failure path — lookup error
sequenceDiagram
actor Builder as Builder
participant Form as ActionIntegrationForm.vue
participant API as chatbot API
Builder->>Form: opens create deal config
Form->>API: POST /v1/node-resources/lookup, resource_key=additional_field_deal
API-->>Form: 4xx or timeout
Form-->>Builder: Custom group shows error state with retry
Note over Form: Standard fields remain usable
Builder->>Form: clicks retry
Form->>API: POST /v1/node-resources/lookup, retry
API-->>Form: 200 — field data
Form-->>Builder: Custom group populated
Detail 2.3 — Database Model
N/A — pure frontend enhancement. No client-side persistence (localStorage/IndexedDB) introduced.
The action config is persisted server-side in ai_agent_actions.parameters.arguments by the
existing save flow.
Detail 2.4 — APIs Consumed
| Method | Path | Status | Contract authority | Notes |
|---|---|---|---|---|
| POST | /v1/node-resources/lookup | exists — new resource keys | BE RFC §5.3 | resource_key: additional_field_deal or additional_field_ticket; response data[] items have property.is_additional_field: true; existing endpoint, new keys only |
No new endpoints. The existing node-resources lookup endpoint gains two new resource_key values
from BE Chunk 3.
Detail 2.A — UI Contract
ActionIntegrationForm.vue — extended
This file already renders the Add-field picker and all field rows. Extensions:
-
Custom group in picker — when
nodeRegistry.settings?.custom_fields_enabled === true, fetchadditional_field_dealoradditional_field_ticket(keyed by action type) and render a second group below Standard fields labeled "Custom fields from CRM". Each item indata[]appears as a picker row with itsproperty.display_nameand a type badge. -
is_additional_fieldfield rows — fields withproperty.is_additional_field: truefollow the same Let AI decide / Set manually toggle as standard fields. The renderer's existinghtml.elementswitch handles all types without change:select multiple→ActionMultiSelectField(chips, constrained toresource_types[0].items)select→MpAutocomplete(dropdown,is_rl: true)input+text/number/url→MpInputtextarea→MpTextarea
-
Saved shape — when a builder saves an additional field, it is stored as:
{"parameters": {"arguments": {"<field_name>": {"is_additional_field": true,"id": <crm_field_id>,"type": "<normalized_type>","use_ai": true,"value": null}}}}For manual mode:
use_ai: false,value: <selected_value>, and for dropdowns:cached_result_name: <display_label>.
State shape & ownership:
Custom field descriptors: local reactive state in ActionIntegrationForm.vue (same lifecycle as
existing pipeline/stage descriptors — loaded on mount, not persisted).
Event payloads (analytics):
ai_agent_action_custom_field_added— fires when builder adds a custom field row; properties:{ action_type, field_name, field_type, is_array, mode }.
Conditional rendering by feature state:
custom_fields_enabled: false(ornodeRegistry.settings?.custom_fields_enabledabsent) → no Custom group; picker identical to today.- Lookup loading → spinner/skeleton in Custom group only.
- Lookup error → error message + retry button in Custom group; Standard group unaffected.
- Lookup empty (
data: []) → "No custom fields in your CRM." in Custom group. - Lookup success → Custom group renders field list.
A11y: Custom group labeled aria-label="Custom fields from CRM". Array chip inputs must have
aria-label from property.display_name. Focus returns to picker trigger on close.
Detail 2.B — Data-Fetching Strategy
- Library: existing resource-lookup composable / store used for pipeline/stage lookups (verify path via grep).
- Cache key:
additional_field_deal/additional_field_ticket+organizationId— same key space as existing resource lookups. - TTL / refetch: loaded once on form mount; re-fetched on explicit retry. No polling.
- Stale-while-revalidate: no — loaded synchronously on mount; Custom group shows loading until resolved.
- Optimistic updates: no — config save is existing flow, not changed by this RFC.
Detail 2.C — UI State Matrix
| Surface | Loading | Empty | Error | Partial | Success |
|---|---|---|---|---|---|
| Custom group in picker | Spinner / skeleton in Custom group | "No custom fields in your CRM." | Error message + retry button; Standard group usable | n/a | Field list with type badges |
| Array field row (Set manually) | Chips disabled while options load (if separate load) | "No options available." | Error + retry | n/a | Chips selectable |
| Array field row (AI mode) | — | — | — | n/a | Disabled; reads "AI will select one or more values" |
Detail 2.D — Scope Boundaries
-
Files to modify:
modules/ai-agent/components/forms/action/ActionIntegrationForm.vue— Custom group +is_additional_fieldrow handling + lookup call[resource-lookup composable]— addadditional_field_deal/additional_field_ticketas supported resource keys (if the composable has an allow-list; verify)
-
Files to create:
- None — the existing renderer handles all types; no new component is needed.
-
Files explicitly NOT touched:
constants/ai-agent-actions-constants.ts— additional fields come from the lookup API dynamically; the static constants file is not changed.ActionMultiSelectField.vue— reused as-is.update deal/update ticketform paths — scope boundary; Custom group must NOT appear there.- Any action type other than
qontak_crm_deal_create/qontak_crm_ticket_create.
-
Shared components:
ActionMultiSelectField.vue— reused, not modified. Used by dealtagsfield today; the additional-field usage is identical.
Detail 2.E — State Surface Contract
| Entity | State field / event consumed | Default values | Source endpoint / event | Stale-tolerance window |
|---|---|---|---|---|
| Additional field list | data[] from lookup response | [] (empty Custom group) | POST /v1/node-resources/lookup {resource_key: additional_field_deal} | Session (re-fetched on retry only) |
| Field config row | parameters.arguments.<field_name> | { is_additional_field: true, use_ai: true, value: null } | Saved via existing action config write | n/a — in-form state until saved |
custom_fields_enabled gate | nodeRegistry.settings.custom_fields_enabled | false / absent → no Custom group | Node registry (existing read path) | Config session |
Detail 2.F — Asset Inventory
No new icons, illustrations, images, fonts, or lotties introduced. All visual elements (group
label, type badge, error icon, spinner) use existing design system tokens already in use by
ActionIntegrationForm.vue.
3. High-Availability & Security
The FE change is additive and config-time only. No runtime change to the conversation flow.
- Graceful degradation: if
POST /v1/node-resources/lookupfor additional fields fails, the Standard group and all other action config fields remain fully functional. The builder can still configure all existing standard fields. - Flag OFF behavior: when
node_registry.settings.custom_fields_enabledis absent orfalse, the Custom group is never mounted, the lookup is never called, and the component is byte-for-byte identical to today. - Offline / slow network: Custom group shows loading/error state; builder can retry.
Standard fields are unaffected (they come from the existing
ACTION_LISTconstant, not a network call).
Performance Requirement
- LCP / INP / CLS: No new above-the-fold rendering; action config drawer is an interaction path, not a landing page. No target change vs today.
- Bundle size: No new dependencies introduced (
ActionMultiSelectFieldand the resource- lookup composable already exist in the bundle). Net delta expected ≈ 0 new JS weight. - Lookup latency: PRD target ≤ 1.5 s p95 (same budget as pipeline/stage lookups). If the BE endpoint exceeds this, the Custom group shows a loading state while Standard fields remain immediately usable.
- Browser support: same as existing
chatbot-fematrix —[VERIFY from chatbot-fe browserslist]. - i18n / l10n: picker group label "Custom fields from CRM" and empty/error messages must use
the project's i18n system. Keys:
ai_agent.custom_fields.group_label,ai_agent.custom_fields.empty,ai_agent.custom_fields.error,ai_agent.custom_fields.loading,ai_agent.custom_fields.ai_array_placeholder("AI will select one or more values"). - RTL: inherit from parent form layout.
Monitoring & Alerting
| Signal | When fired | Key properties |
|---|---|---|
ai_agent_action_custom_field_added | Builder adds a custom field to config | action_type, field_name, field_type, is_array, mode (ai/manual) |
ai_agent_action_custom_field_lookup_failed | Lookup request fails or exceeds 1.5 s | action_type, latency_ms, error_code |
Analytics instrumentation: use the project's existing analytics composable / tracking utility
(verify pattern from any analytics call in modules/ai-agent/).
- Error monitoring: report lookup failures to Sentry/Datadog at
warninglevel (non-fatal — builder can retry; Standard fields still work). - Core Web Vitals: no new tracking required; no new above-the-fold surfaces.
Logging
- FE logs: lookup failure logged at
warnlevel withresource_keyandlatency_ms. - No PII in logs:
field_nameandaction_typeare config labels, not personal data.
Security Implications
- XSS:
property.display_namefrom the lookup response is rendered as text, not HTML. Nov-htmlusage introduced. Verify existing renderer does not passdisplay_nametov-html. - CSRF: existing auth/cookie pattern for
POST /v1/node-resources/lookupis unchanged. - Input sanitization: additional field values are constrained to
resource_types[0].itemsforis_rl: truefields; free-text inputs (text/number/textarea) inherit existing form sanitization. - Auth token / CSP / HTTPS: no change from existing config.
- PII:
display_nameandnamefrom CRM definitions are org-config labels, not PII. No customer personal data is rendered in this config flow. No ISO 27701 / GDPR trigger. - Secret management: no new env vars or secrets.
Detail 3.A — Failure Mode Catalog
| API call | 401 | 403 | 404 | 429 | 5xx | Timeout (1.5 s) | Offline | Retry mechanism |
|---|---|---|---|---|---|---|---|---|
POST /v1/node-resources/lookup {resource_key: additional_field_deal} | Redirect to re-auth (existing session handler) | Custom group shows error + retry | Custom group shows "No custom fields" (field not enabled) | Error + retry after 30 s | Error + retry | Error + retry | Error + retry | Builder clicks retry button in Custom group; Standard group unaffected |
Detail 3.B — Error Message Catalog
| Error | User-facing message (i18n key) | Surface | User-facing? |
|---|---|---|---|
| Lookup failure / timeout | ai_agent.custom_fields.error ("Failed to load custom fields. Retry?") | Custom group inline error | yes |
| Empty additional fields | ai_agent.custom_fields.empty ("No custom fields in your CRM.") | Custom group inline | yes |
| n/a — array free-value rejected | Chip input controlled by resource_types.items; free entry not possible | Chip input (no separate message) | no |
Detail 3.C — Accessibility
- WCAG: AA (existing project standard).
- Keyboard navigation: Custom group navigable with arrow keys in picker; same as Standard group.
- Focus management: when picker closes after adding a field, focus returns to the "Add field" trigger button.
- ARIA: Custom group wrapper has
aria-label="Custom fields from CRM". Each picker item hasaria-labelfromproperty.display_name.ActionMultiSelectFieldchips must havearia-labelfrom the field'sdisplay_name. - Color contrast: error and loading states use design system tokens — verify with existing contrast checker.
prefers-reduced-motion: no new animations introduced.
4. Backwards Compatibility and Rollout Plan
Compatibility
- API contracts changed: none.
POST /v1/node-resources/lookupgains new validresource_keyvalues but is otherwise unchanged. - Saved client state: existing action configs have no
is_additional_fieldentries inarguments— they load identically to today (Custom group renders if enabled; no additional-field rows appear until the builder adds one). - CDN / bundle cache: no change in chunk names expected (no new dynamic imports). Existing cache invalidation strategy applies.
Rollout Strategy
- Feature flag:
ai_agent_action_custom_fields(org-level, default OFF). FE shows Custom group only whennodeRegistry.settings.custom_fields_enabled === true, which is set by the BE seed after the flag is enabled per org. - Rollout stages: same as BE RFC §7.2 — Deploy (flag OFF, no org sees change) → Internal QA (1 internal org) → Closed Beta (3–5 orgs) → GA.
- Stop conditions: Custom group lookup failure rate > 2% or any JavaScript error in the config form.
- Rollback: toggle org off in
system_preferences(BE rollback — no FE deploy required). If code rollback needed: revertActionIntegrationForm.vuepatch. - Blast radius: only
create deal/create ticketaction config forms for orgs with flag ON.
Detail 4.A — Configuration Contract
| Env var / flag | Type | Default | Required | Provisioner |
|---|---|---|---|---|
ai_agent_action_custom_fields (org-level) | per-org via BE system_preferences | OFF | no | Ops via Rails console (BE Chunk 1); FE reads effect via nodeRegistry.settings.custom_fields_enabled |
Detail 4.B — Test Plan
Commands must be sourced from
chatbot-fe/package.jsonscripts. Verify all commands before running.
| Layer | Command (source: verify from package.json) | What it must prove |
|---|---|---|
| Unit | [VERIFY: e.g. yarn test or npm run test -- modules/ai-agent] | Custom group renders/hides by custom_fields_enabled; lookup error state; is_additional_field save shape |
| Component (Vue) | [VERIFY: e.g. yarn test:unit ActionIntegrationForm] | Array field renders ActionMultiSelectField; AI mode shows disabled placeholder; standard fields unaffected |
| E2E | [VERIFY: e.g. yarn test:e2e e2e/ai-agent-custom-fields.spec.ts] | Full config flow: open drawer → Custom group loads → add array field → save → reload shows field |
| Visual regression | [VERIFY or n/a — check if chatbot-fe runs Percy/Chromatic] | Custom group renders consistently |
| Accessibility (axe) | [VERIFY: e.g. yarn test:a11y] | No new axe violations in config drawer |
| Bundle size | [VERIFY: e.g. yarn build --analyze] | No net bundle increase (no new deps) |
Detail 4.C — Agent Execution Plan
Each chunk is discrete. Finish chunk N and verify its ACs before opening N+1.
Pre-condition: Confirm POST /v1/node-resources/lookup with resource_key: additional_field_deal returns data from a dev/staging environment with BE Chunks 1–5 deployed.
| Order | Chunk | Files to modify/create | Commands to run | Acceptance criteria (verifiable) |
|---|---|---|---|---|
| 1 | Find resource-lookup composable and confirm its interface | grep search only; no file change | grep -r "node-resources/lookup" modules/ai-agent/ | Path identified; confirm call signature and caching behavior |
| 2 | Add Custom group to Add-field picker in ActionIntegrationForm.vue | ActionIntegrationForm.vue | [yarn/npm test -- ActionIntegrationForm] | When nodeRegistry.settings.custom_fields_enabled === true and lookup succeeds, picker renders Standard group + Custom group with data[] items. When flag false/absent, picker is unchanged. Lookup error → error state + retry; Standard still usable. Unit test passes. |
| 3 | Handle is_additional_field: true field rows + save shape | ActionIntegrationForm.vue | [yarn/npm test -- ActionIntegrationForm] | Array field (html.element: 'select multiple') renders ActionMultiSelectField constrained to resource_types[0].items. AI mode: disabled + "AI will select one or more values". Saved entry in parameters.arguments has is_additional_field: true, correct id, type, use_ai, value, cached_result_name for dropdowns. Unit test passes. |
| 4 | i18n keys | [i18n locale files] | [yarn/npm build] | All new user-facing strings use i18n keys; no hardcoded English strings in template; build passes |
| 5 | Analytics events | ActionIntegrationForm.vue | [yarn/npm test -- ActionIntegrationForm] | ai_agent_action_custom_field_added fires with correct properties when builder adds a field; ai_agent_action_custom_field_lookup_failed fires on error |
| 6 | Scope boundary check | ActionIntegrationForm.vue | snapshot tests for update deal/update ticket forms | Custom group absent in update-action forms; no custom fields in any form that does not have custom_fields_enabled |
| 7 | E2E spec | e2e/ai-agent-custom-fields.spec.ts (new) | [yarn/npm test:e2e] | Full builder flow passes: open config → Custom group loads → add field → save → config persists |
Detail 4.D — Verification & Rollback Recipe
Pre-merge verification commands (verify sources from package.json first):
[VERIFY: lint command, e.g. yarn lint][VERIFY: typecheck, e.g. yarn typecheck][VERIFY: unit tests, e.g. yarn test][VERIFY: build, e.g. yarn build]
Post-deploy verification signals:
- Custom group visible for internal test org in create-deal config drawer.
ai_agent_action_custom_field_lookup_failedcount = 0 in Rollbar/Sentry over first 30 min.- Existing create-deal config for a standard-field-only org is unchanged (spot-check).
Rollback recipe:
- Ops clears
system_preferences.valueto'[]'forcode: 'ai_agent_action_custom_fields'— Custom group disappears for all orgs instantly (no FE deploy). - If code rollback required: revert
ActionIntegrationForm.vuepatch and redeploychatbot-fe. - Confirm no JavaScript errors in the action config form post-revert (Sentry).
5. Concern, Questions, or Known Limitations
| # | Type | Item | Owner | Deadline |
|---|---|---|---|---|
| OQ-1 | Impact on PRD | PRD D-1/D-2 (pipeline gate) and S04 (prune on pipeline change) are superseded by BE Decision 3.7 (org-level fields). AC-4, S04/AC-1, S04/AC-2, NEG-2, NEG-3 are either met trivially or inapplicable. PM to confirm PRD amendment (BE RFC A-4). Until confirmed, the FE does not implement a pipeline gate. | PM | Before FE sprint |
| OQ-2 | Command verification | All Detail 4.B test commands are sourced as [VERIFY]. The agent must read chatbot-fe/package.json scripts before writing Detail 4.C commands in code. | FE tech lead | Chunk 1 |
| OQ-3 | Resource-lookup composable path | Path unknown from PRD evidence; agent must grep -r "node-resources/lookup" modules/ai-agent/ to find it (Chunk 1). | FE tech lead | Chunk 1 |
| OQ-4 | Design system version | @mekari/mekaui version needs verification from package.json. No new DS components are introduced, but tokens and the ActionMultiSelectField API may differ across DS versions. | FE tech lead | Chunk 2 |
| OQ-5 | Production Figma frames | Not yet produced by Design. Wireframes are the build reference until they exist. When Figma frames land, diff against implemented UI and record any deviations via Design QA contact. | Design | Pre-QA |
6. Comment logs
| Date | Comment(s) From | Action Item(s) |
|---|---|---|
| 2026-07-16 | RFC author | Initial draft from PRD v1.4 + BE RFC (ready 2026-07-15). All 5 mermaid blocks validated via pitfall-check script (no mmdc Chrome available) — semicolons, curly braces, and angle-bracket labels confirmed absent. Key finding: PRD D-1/D-2 pipeline gate not implemented (org-level fields per BE Decision 3.7); pending PM A-4 confirmation. |
7. Ready for agent execution
Ready for agent execution: no
Missing before yes:
- DRI / Reviewers / Approvers assigned — placeholder names must be filled.
- Resource-lookup composable path confirmed — OQ-3 (grep result from
chatbot-fecodebase). - Test commands sourced — Detail 4.B commands marked
[VERIFY]must be sourced fromchatbot-fe/package.jsonbefore Chunk 2. - Design system version confirmed — OQ-4 (
@mekari/mekauiversion frompackage.json). - PM confirms A-4 — PRD D-1/D-2 amendment (OQ-1); this determines whether any pipeline gate is expected in the FE. RFC proceeds without the gate; if PM reinstates it, Chunk 2 scope changes.
Gates met:
- PRD traceability — every composite AC id mapped forward and reverse
- Per-Story Change Map — every story has one row with layer scope + verifiable AC
- Design References — wireframes + prototype cited; Figma TBD noted; no chunk drafts against imagined design
- Source Verification table — every anchor has evidence (PRD ground-prd 2026-07-13 or BE RFC 2026-07-15); unknown rows marked [REQUIRED]
- Design ↔ Code Mapping — every wireframe frame mapped to implementing file
- Mermaid diagrams — all 4 blocks validated with
npx -y -p @mermaid-js/mermaid-cli mmdc(see comment log) - UI Contract — adaptive renderer extension documented; save shape exact
- UI State Matrix — 5 states per surface
- Failure Mode Catalog — lookup failure path + retry
- Configuration Contract — flag + gate documented
- Scope Boundaries — create-only; update actions explicitly excluded
- Rollback — flag toggle instant; no FE deploy required
Optional: hand off to
rfc-reviewerfor a second-pass score after missing items are resolved.