@explorer02/cfm-survey-sdk 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/cli/index.js +18 -18
  2. package/dist/cli/index.mjs +14 -14
  3. package/dist/index.d.mts +465 -580
  4. package/dist/index.d.ts +465 -580
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/package.json +6 -4
  8. package/postinstall.js +96 -64
  9. package/templates/AGENT.md +15 -7
  10. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  11. package/templates/docs/00-integration/aws-deploy.md +281 -0
  12. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  13. package/templates/docs/00-integration/component-checklist.md +84 -0
  14. package/templates/docs/00-integration/constraints.md +65 -3
  15. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  16. package/templates/docs/00-integration/display-logic-and-navigation.md +49 -0
  17. package/templates/docs/00-integration/end-page-logic.md +64 -0
  18. package/templates/docs/00-integration/partial-save-and-recovery.md +74 -0
  19. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  20. package/templates/docs/00-integration/progress.md +61 -0
  21. package/templates/docs/00-integration/question-display-variants.md +65 -0
  22. package/templates/docs/00-integration/question-numbering.md +51 -0
  23. package/templates/docs/00-integration/question-type-sdk-matrix.md +242 -0
  24. package/templates/docs/00-integration/setup.md +84 -0
  25. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  26. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  27. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  28. package/templates/docs/01-components/01-survey-page.md +76 -54
  29. package/templates/docs/01-components/02-question.md +77 -89
  30. package/templates/docs/01-components/03-rating-scale.md +11 -1
  31. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  32. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  33. package/templates/docs/01-components/06-likert-matrix-scale.md +12 -3
  34. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  35. package/templates/docs/01-components/08-file-upload-scale.md +10 -1
  36. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  37. package/templates/docs/01-components/11-progress-bar.md +10 -4
  38. package/templates/docs/01-components/13-matrix-dropdown.md +3 -1
  39. package/templates/docs/01-components/14-intro-page.md +34 -0
  40. package/templates/docs/01-components/15-end-page.md +31 -0
  41. package/templates/docs/01-components/16-paused-page.md +27 -0
  42. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  43. package/templates/docs/01-components/18-rank-order-scale.md +100 -0
  44. package/templates/docs/01-components/README.md +44 -36
  45. package/templates/docs/02-reference/config-field-index.md +141 -139
  46. package/templates/docs/02-reference/exports.md +149 -0
  47. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  48. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  49. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  50. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  51. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  52. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  53. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  54. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  55. package/templates/docs/02-reference/question-types/11-file-upload.md +13 -2
  56. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  57. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  58. package/templates/docs/02-reference/question-types/README.md +86 -47
  59. package/templates/docs/02-reference/routing-table.md +33 -18
  60. package/templates/docs/02-reference/value-derivation.md +63 -0
  61. package/templates/docs/03-ui-specs/00-question-shell.md +24 -80
  62. package/templates/docs/03-ui-specs/01-rating.md +12 -54
  63. package/templates/docs/03-ui-specs/02-radio.md +21 -33
  64. package/templates/docs/03-ui-specs/03-text.md +2 -2
  65. package/templates/docs/03-ui-specs/04-csat.md +38 -33
  66. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  67. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  68. package/templates/docs/03-ui-specs/07-matrix-cfm.md +25 -22
  69. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +18 -46
  70. package/templates/docs/03-ui-specs/09-slider-matrix.md +2 -2
  71. package/templates/docs/03-ui-specs/10-file-upload.md +2 -2
  72. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  73. package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
  74. package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
  75. package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
  76. package/templates/docs/03-ui-specs/README.md +14 -13
  77. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +3 -3
  78. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +6 -1
  79. package/templates/docs/MANIFEST.json +454 -67
  80. package/templates/docs/index.md +84 -29
  81. package/templates/docs/templates/Question.tsx +310 -0
  82. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  83. package/templates/docs/templates/heatmapCoords.ts +58 -0
  84. package/templates/docs/templates/implementation_plan.md +115 -49
  85. package/templates/docs/templates/survey-inventory.schema.json +72 -0
  86. package/templates/docs/templates/verify-agent-build.sh +53 -0
  87. package/templates/docs/01-components/04-csat-scale.md +0 -40
  88. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -0,0 +1,84 @@
1
+ # Required Component Files Checklist
2
+
3
+ > **Blocking gate before Phase 5 (BUILD).** After fetching the survey with `instanceId`, list every `question.type` and ensure all required files exist.
4
+
5
+ ## Step 1: Inventory Question Types
6
+
7
+ From `survey.pages`, collect unique `question.type` values — see `MANIFEST.json` `typeLiterals`:
8
+
9
+ | questionId | type |
10
+ |------------|------|
11
+ | | |
12
+
13
+ ## Step 2: Create Required Files
14
+
15
+ | If survey contains | Agent MUST create before `npm run build` | UI spec |
16
+ |--------------------|------------------------------------------|---------|
17
+ | `NPS_SCALE` | `RatingScale.tsx` | `03-ui-specs/01-rating.md` |
18
+ | `CFM_MATRIX` | `LikertMatrixScale.tsx`, `MatrixDropdown.tsx` | `03-ui-specs/07-matrix-cfm.md` |
19
+ | `CSAT_MATRIX` | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/04-csat.md` |
20
+ | `RATING_MATRIX` | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/08-matrix-csat-rating.md` |
21
+ | `SLIDER_MATRIX` | `SliderMatrixScale.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/09-slider-matrix.md` |
22
+ | `FILE_UPLOAD` | `FileUploadScale.tsx` | `03-ui-specs/10-file-upload.md` |
23
+ | `MCQ`, `TEXTFIELD`, `TEXT_AND_MEDIA` | Inline in `Question.tsx` | `03-ui-specs/02-radio.md`, `03-text.md`, `11-text-and-media.md` |
24
+ | `RANK_ORDER` | `RankOrderScale.tsx` (+ `@dnd-kit/*`) | `03-ui-specs/14-rank-order.md` |
25
+ | `HEATMAP` | `HeatmapScale.tsx`, `lib/heatmapCoords.ts` (copy from `templates/heatmapCoords.ts`) | `03-ui-specs/13-heatmap.md` |
26
+
27
+ **Survey chrome (when applicable):**
28
+
29
+ | Component | When |
30
+ |-----------|------|
31
+ | `IntroPage.tsx` | `survey.introPage` |
32
+ | `EndPage.tsx` | `survey.endPages` |
33
+ | `PausedPage.tsx` | `survey.phaseFlags.paused` |
34
+
35
+ Always create **`Question.tsx`** from [`templates/Question.tsx`](../templates/Question.tsx).
36
+
37
+ ## Flat Type Routing (CRITICAL)
38
+
39
+ Use `QUESTION_TYPE` — **no `subType`**:
40
+
41
+ ```tsx
42
+ import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
43
+
44
+ {question.type === QUESTION_TYPE.CFM_MATRIX && (
45
+ <LikertMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
46
+ )}
47
+
48
+ {(question.type === QUESTION_TYPE.CSAT_MATRIX ||
49
+ question.type === QUESTION_TYPE.RATING_MATRIX) && (
50
+ <CsatMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
51
+ )}
52
+ ```
53
+
54
+ ## Shared Primitives
55
+
56
+ | File | Used by |
57
+ |------|---------|
58
+ | `CustomSliderTrack.tsx` | `SLIDER_MATRIX`, matrix `displayStyle: graphics` |
59
+ | `MatrixDropdown.tsx` | `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX` dropdown/selectbox modes |
60
+
61
+ Store matrix values as column **`id`** — `matrixColumnStoredValue(col)` from SDK.
62
+
63
+ ## Answer logic (when survey has gated options / rows / columns)
64
+
65
+ - [ ] MCQ: filter options with `getVisibleMcqOptionsForAnswers(question, answers, allQuestions, customFieldValues)`
66
+ - [ ] Matrix (CSAT/CFM/RATING/SLIDER): filter rows (and CSAT/CFM columns) with `getVisibleMatrixItemsForAnswers(question, answers, allQuestions, customFieldValues)`
67
+ - [ ] Pass `customFieldValues` from `SurveyPage` → `Question` when survey logic references `CUSTOM_FIELD` entities
68
+ - [ ] Wire `onAction({ type: 'CHANGE' })` so answer logic re-evaluates when prior answers change
69
+
70
+ See [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) and [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
71
+
72
+ ## Step 3: Verify Before Build
73
+
74
+ - [ ] Every type in the survey inventory has a matching component file
75
+ - [ ] `Question.tsx` copied from `docs/templates/Question.tsx` with all **11** type branches wired
76
+ - [ ] `SurveyPage` handles all `state.phase` values
77
+ - [ ] No `default` fallback rendering stub text (see `constraints.md` §7)
78
+ - [ ] RANK_ORDER: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` in `package.json`
79
+ - [ ] Per-type wiring verified against `question-type-sdk-matrix.md`
80
+ - [ ] Grep project: `not yet implemented` → **zero matches**
81
+
82
+ ```bash
83
+ grep -r "not yet implemented" src/ components/ app/ 2>/dev/null || true
84
+ ```
@@ -36,9 +36,64 @@ Required for scroll-to-error when validation fails.
36
36
 
37
37
  Survey UI files importing the SDK must be client components. Next.js: `'use client'` at top of file.
38
38
 
39
- ## 6. Stable Placeholder References
39
+ ## 6. Stable Placeholder and Custom Field References
40
40
 
41
- Define `SURVEY_PLACEHOLDERS` outside the component — inline objects cause infinite re-fetch.
41
+ Define `SURVEY_PLACEHOLDERS` and `CUSTOM_FIELD_VALUES` **outside** the component — inline objects cause infinite re-fetch and unstable hook dependency arrays.
42
+
43
+ Wrap hook `options` in `useMemo`:
44
+
45
+ ```typescript
46
+ const surveyOptions = useMemo(
47
+ () => ({
48
+ instanceId,
49
+ language: selectedLanguage,
50
+ placeholders: SURVEY_PLACEHOLDERS,
51
+ customFieldValues: CUSTOM_FIELD_VALUES,
52
+ }),
53
+ [selectedLanguage]
54
+ );
55
+ ```
56
+
57
+ See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) when the survey uses custom-field logic.
58
+
59
+ ## 7. No Stub Placeholders (CRITICAL)
60
+
61
+ **FORBIDDEN** in `Question.tsx` or any scale component:
62
+
63
+ - Text like `"not yet implemented"`, `"coming soon"`, or any placeholder error message
64
+ - `default` / `else` branches in the dispatcher that render stub UI instead of the real component
65
+ - Empty `<div>` where an interactive scale component should be
66
+
67
+ If a question type appears in the fetched survey, its component **must** be fully implemented before shipping. Read the matching `03-ui-specs/` doc and create the required files listed in `component-checklist.md`.
68
+
69
+ **Phase 6 verification:** grep the project for stub text — must return zero matches:
70
+
71
+ ```bash
72
+ grep -r "not yet implemented" src/ components/ app/ 2>/dev/null || true
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 8. Deploy Constraints
78
+
79
+ - **Never embed AWS credentials** in client code — JWT goes in `Authorization: Bearer` header at deploy time only
80
+ - **Deploy JWT must match** `SURVEY_INSTANCE_ID` in `SurveyPage.tsx` (surveyId drives S3 prefix)
81
+ - **Next.js AWS deploy requires static export** — no server-only APIs (`getServerSideProps`, etc.)
82
+ - **Max 500 files** per deploy (EC2 API limit)
83
+ - **No absolute `/`-root asset paths** when deploying to nested CloudFront prefix — use `assetPrefix: "./"` and relative public assets
84
+
85
+ See [`aws-deploy.md`](aws-deploy.md) for the full AWS deploy procedure and troubleshooting.
86
+
87
+ ---
88
+
89
+ ## 9. SDK Integration Constraints
90
+
91
+ - **Never import internal evaluators** from `@explorer02/cfm-survey-sdk/src/**` or `@repo/sdk/src/**`
92
+ - **Always use public `getVisible*ForAnswers` helpers** for answer-logic UI filtering — never reimplement condition evaluation
93
+ - **Matrix cell values:** always store column **`id`** via `matrixColumnStoredValue(column)` — never raw label text
94
+ - **Rank order:** use SDK rank helpers (`assignRankWithoutDuplicates`, `normalizeRankOrderAnswers`, etc.) — do not hand-roll rank maps
95
+ - **CSAT emoji/star UI:** implement client-side with `react-icons` — no phantom SDK emoji exports
96
+ - **Read [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md)** before building any scale component
42
97
 
43
98
  ---
44
99
 
@@ -47,8 +102,15 @@ Define `SURVEY_PLACEHOLDERS` outside the component — inline objects cause infi
47
102
  | Symptom | Fix |
48
103
  |---------|-----|
49
104
  | `Cannot use import statement outside a module` | Add SDK to `transpilePackages` in `next.config.js` |
50
- | Infinite API requests | Move `placeholders` object outside component |
105
+ | Infinite API requests | Move `placeholders` / `customFieldValues` outside component; wrap options in `useMemo` |
106
+ | `useEffect` dependency array size changed | Unstable inline `options` object — use `useMemo` for hook options |
107
+ | Missing `dist/index.mjs` (monorepo) | Run `npm run build` in `packages/sdk` before client dev |
51
108
  | `window is not defined` | Add `'use client'` to SDK-consuming files |
52
109
  | Validation errors but no scroll | Add `id={question.id}` to question wrapper |
53
110
  | Survey data is null, no error | Invalid/expired `instanceId` — ask client for fresh JWT |
54
111
  | CSAT emojis missing | `npm install react-icons` |
112
+ | Grey "not yet implemented" text on questions | Copy `docs/templates/Question.tsx`; create scale components per `component-checklist.md`; route each matrix variant by flat `question.type` |
113
+ | 403 on `/_next/static/...` after AWS deploy | Add `assetPrefix: "./"` to `next.config.js`, rebuild — see `aws-deploy.md` |
114
+ | 403 on survey images `*_p.png` | Use `option.imageUrl` before `option.previewImageUrl` in RankOrderScale |
115
+ | 403 on `index.html.txt?_rsc` | Add `cp out/index.txt out/index.html.txt` to build script |
116
+ | AWS deploy PUT failures | Presigned URLs expired — restart from `/deploy/start` within 5–15 min |
@@ -0,0 +1,174 @@
1
+ # Custom Field Logic and Navigation
2
+
3
+ > Integrator-facing contract for CRM/transaction custom fields used in skip, display, and answer logic. Evaluation runs inside the SDK — pass values via hook options; do not import internal `surveyLogic` paths.
4
+
5
+ ## When this applies
6
+
7
+ Survey logic rules may reference **custom fields** instead of survey questions:
8
+
9
+ ```json
10
+ {
11
+ "conditionEntity": {
12
+ "id": "_c_6a436c1244764c9d4a6ce2fc",
13
+ "entityType": "CUSTOM_FIELD",
14
+ "fieldType": "TEXT"
15
+ }
16
+ }
17
+ ```
18
+
19
+ Inspect fetched survey data: any `skipLogics`, `displayLogic`, or `answerLogic` with `entityType: 'CUSTOM_FIELD'` requires client-supplied values.
20
+
21
+ ---
22
+
23
+ ## Client value map
24
+
25
+ Pass via `useSurveySDK({ options: { customFieldValues } })`. Keys are the `_c_…` ids from logic rules.
26
+
27
+ ```typescript
28
+ import type { CustomFieldValueMap } from '@explorer02/cfm-survey-sdk';
29
+
30
+ const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {
31
+ '_c_6a436c1244764c9d4a6ce2fc': 'q5', // TEXT
32
+ '_c_6a436c3644764c9d4a6ce3f5': 2, // NUMBER
33
+ '_c_6a436c9e44764c9d4a6ce717': 'option-id-uuid', // PICKLIST
34
+ '_c_6a436cbb44764c9d4a6ce7e5': ['opt-a', 'opt-b'], // PICKLIST_MULTISELECT
35
+ '_c_6a4386dc44208eb8b08a9056': 1781029800000, // DATETIME (epoch ms)
36
+ };
37
+ ```
38
+
39
+ Define **outside** the component (stable ref — same rule as `placeholders`). Wrap hook `options` in `useMemo`.
40
+
41
+ | Custom field type (`fieldType`) | Client value shape |
42
+ |--------------------------------|-------------------|
43
+ | TEXT / TEXT_MULTI / TEXTAREA | `string` |
44
+ | NUMBER | `number` or numeric string (`0` is valid, not empty) |
45
+ | PICKLIST | `string` (option id) |
46
+ | PICKLIST_MULTISELECT | `string[]` (option ids) |
47
+ | DATETIME | epoch ms (`number` or numeric string); ISO date strings also accepted |
48
+
49
+ Missing keys in the map are treated as **empty** for logic evaluation.
50
+
51
+ ---
52
+
53
+ ## Operator contract
54
+
55
+ | fieldType | Logic `field` | Operators |
56
+ |-----------|---------------|-----------|
57
+ | TEXT / TEXT_MULTI / TEXTAREA | `TEXT` or `CASE_SENSITIVE_TEXT` | EQUALS, NOT_EQUALS, CONTAINS, DOES_NOT_CONTAIN, MISSING, EXISTS |
58
+ | NUMBER | `NUMBER` | LT, LTE, GT, GTE, EQUALS, NOT_EQUALS, MISSING, EXISTS |
59
+ | PICKLIST / PICKLIST_MULTISELECT | `SELECTED_OPTIONS` | CONTAINS, DOES_NOT_CONTAIN, MISSING, EXISTS |
60
+ | DATETIME | `SELECTED_DATETIME` | LT, LTE, GT, GTE, EQUALS, NOT_EQUALS |
61
+
62
+ Filter `values` in API rules use strings (e.g. `'1781029800000'` for datetime). The SDK normalizes client datetime values to epoch ms for comparison.
63
+
64
+ Datetime filters may include `valueAdditional.timezone` (e.g. `"Asia/Kolkata"`) — reserved for future timezone-aware parsing; comparisons currently use epoch ms numbers.
65
+
66
+ ---
67
+
68
+ ## Two wiring points (CRITICAL)
69
+
70
+ Custom field values must reach **both** the hook and the question dispatcher:
71
+
72
+ ```mermaid
73
+ flowchart LR
74
+ CRM["CRM / transaction payload"]
75
+ Map["CUSTOM_FIELD_VALUES map"]
76
+ Hook["useSurveySDK options"]
77
+ Question["Question.tsx"]
78
+ Nav["Skip / display / validation / progress"]
79
+ AnswerUI["Answer-logic option/row filtering"]
80
+
81
+ CRM --> Map
82
+ Map --> Hook --> Nav
83
+ Map --> Question --> AnswerUI
84
+ ```
85
+
86
+ ### 1. Hook options (navigation layer)
87
+
88
+ ```typescript
89
+ useSurveySDK({
90
+ options: {
91
+ customFieldValues: CUSTOM_FIELD_VALUES,
92
+ },
93
+ });
94
+ ```
95
+
96
+ SDK uses this for:
97
+
98
+ - Skip logic path simulation and jumps
99
+ - Question-level display logic (`state.currentQuestions`)
100
+ - Submit / Next validation on the active path
101
+ - Progress bar denominator
102
+
103
+ ### 2. Question visibility helpers (answer-logic UI)
104
+
105
+ When answer logic conditions reference custom fields, pass the **same map** to exported helpers:
106
+
107
+ ```typescript
108
+ import {
109
+ getVisibleMcqOptionsForAnswers,
110
+ getVisibleMatrixItemsForAnswers,
111
+ getVisibleStatementRowsForAnswers,
112
+ getVisibleScaleColumnsForAnswers,
113
+ } from '@explorer02/cfm-survey-sdk';
114
+
115
+ getVisibleMcqOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues);
116
+ getVisibleMatrixItemsForAnswers(question, allAnswers, allQuestions, customFieldValues);
117
+ ```
118
+
119
+ Without the 4th argument, answer-logic UI defaults to an empty custom-field map — options/rows gated on CRM data may render incorrectly even though navigation logic works.
120
+
121
+ **SurveyPage → Question prop threading:**
122
+
123
+ ```tsx
124
+ <Question
125
+ customFieldValues={CUSTOM_FIELD_VALUES}
126
+ allAnswers={state.answers}
127
+ allQuestions={allQuestions}
128
+ // ...
129
+ />
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Discovering field ids
135
+
136
+ After fetching the survey with `instanceId`, search logic payloads for `_c_` ids:
137
+
138
+ - `question.skipLogics[].condition.filters`
139
+ - `question.displayLogic.condition.filters`
140
+ - `question.options[].answerLogic` / matrix row `answerLogic` / column `answerLogic`
141
+
142
+ Record each id in `implementation_plan.md` §3c and populate `CUSTOM_FIELD_VALUES` from your CRM/transaction API.
143
+
144
+ ---
145
+
146
+ ## Example — skip rule on custom field
147
+
148
+ Rule: if TEXT custom field `_c_abc` equals `"premium"`, skip to Q6.
149
+
150
+ ```typescript
151
+ const CUSTOM_FIELD_VALUES = { '_c_abc': 'premium' };
152
+
153
+ useSurveySDK({ options: { instanceId, customFieldValues: CUSTOM_FIELD_VALUES } });
154
+ ```
155
+
156
+ User clicks Next on page 1 → SDK evaluates condition → jumps to Q6's page. No extra client code per question.
157
+
158
+ If the user later changes a CRM field value at runtime, update `customFieldValues` via a new stable map and remount or pass updated options through `useMemo` deps.
159
+
160
+ ---
161
+
162
+ ## Multiple skip logic blocks
163
+
164
+ When a question has several `skipLogics[]` entries (including custom-field rules), the SDK evaluates them **top-to-bottom** and applies the **first match**. See [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md).
165
+
166
+ ---
167
+
168
+ ## Related docs
169
+
170
+ - [`useSurveySDK.md`](useSurveySDK.md) — hook options and stable refs
171
+ - [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) — skip behavior with custom fields
172
+ - [`display-logic-and-navigation.md`](display-logic-and-navigation.md) — question-level gating
173
+ - [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) — option/row/column gating + visibility helpers
174
+ - [`client-integration-guide.md`](client-integration-guide.md) — full wiring map
@@ -0,0 +1,49 @@
1
+ # Display Logic and Navigation
2
+
3
+ > Integrator-facing behavior contract. Display evaluation is handled inside the SDK — do not import internal `surveyLogic` paths directly unless using exported helpers.
4
+
5
+ ## Shared engine with skip logic
6
+
7
+ Skip logic, display logic, and answer logic use the **same condition tree** (`LogicFilterGroup` with nested `AND` / `OR` groups) and the **same evaluator** (`evaluateCondition`). The SDK distinguishes them with `LOGIC_KIND`:
8
+
9
+ | Kind | When true |
10
+ |------|-----------|
11
+ | `SKIP` | Navigate to skip target or end of survey |
12
+ | `DISPLAY` | Show the gated **question** (whole question / page-level) |
13
+ | `ANSWER` | Show a gated **option, statement row, or scale column** within a question |
14
+
15
+ > **Note:** Statement-row and scale-column filtering is **answer logic**, not display logic. See [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md).
16
+
17
+ ## Dynamic vs non-dynamic
18
+
19
+ | Mode | API flag | Behavior |
20
+ |------|----------|----------|
21
+ | **Dynamic** | `isDynamicAppearanceEnabled: true` | Gated question stays on the **same page** as trigger questions. Hidden until condition is true. Page stays on active path; hidden questions are excluded from progress denominator. |
22
+ | **Non-dynamic** | `isDynamicAppearanceEnabled: false` | Gated question is on a **separate page**. When condition is false, that page is skipped in navigation and excluded from active path. |
23
+
24
+ ## Nested conditions
25
+
26
+ Both skip and display support nested AND/OR groups from the API. Flat skip rows (`Q3 = 1 OR Q3 = 2 OR Q3 > 6`) and nested display condition groups map to the same recursive tree.
27
+
28
+ ## Client responsibilities
29
+
30
+ - Use `state.currentQuestions` from `useSurveySDK` — the SDK filters hidden questions automatically.
31
+ - Matrix UIs must filter statement rows with `getVisibleStatementRowsForAnswers(question, answers, allQuestions)` (or `getVisibleMatrixItemsForAnswers` for rows + scale columns).
32
+ - Wire `onAction({ type: 'CHANGE' })` so display conditions re-evaluate when answers change.
33
+
34
+ ## Progress bar
35
+
36
+ Progress counts **visible answerable questions on the active path**. When display logic hides a question:
37
+
38
+ - **Dynamic:** page remains on path; hidden question excluded from total until visible.
39
+ - **Non-dynamic:** gated page excluded from path until condition is true.
40
+
41
+ ## Custom field conditions
42
+
43
+ Display logic may gate questions on **custom field** values (`conditionEntity.entityType: 'CUSTOM_FIELD'`). See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) for the full integration contract.
44
+
45
+ ## Question display variants (distinct from show/hide)
46
+
47
+ Alternate question wording (`questionDisplayVariants`) uses the same condition engine but **does not hide** the question — the SDK resolves `questionText` on `state.currentQuestions`. See [`question-display-variants.md`](question-display-variants.md).
48
+
49
+ See also: [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md), [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`progress.md`](progress.md).
@@ -0,0 +1,64 @@
1
+ # End Page Logic
2
+
3
+ > Integrator-facing contract for surveys with **multiple end pages**. Selection runs inside the SDK at submit time — do not import internal `surveyLogic` paths.
4
+
5
+ ## When it runs
6
+
7
+ After a **successful submit**, the SDK evaluates each end page's `displayLogic` (mapped from API `controlConfig.displayLogic`) and sets `state.phase` to `{ kind: 'end', endPageId }`. The client renders `state.specialPage` — no extra wiring.
8
+
9
+ ## Selection rules
10
+
11
+ End pages are evaluated in **API order**:
12
+
13
+ 1. **Conditional pages** — pages with `displayLogic`: return the **first** whose condition is true (same evaluator as question display logic: nested AND/OR, question answers, custom fields).
14
+ 2. **Default page** — if no conditional page matches, use the **first page without** `displayLogic`.
15
+ 3. **Safety fallback** — if every page has logic and none match, use `endPages[0]`.
16
+
17
+ `isDynamicAppearanceEnabled` on end pages is ignored (evaluation happens once at submit).
18
+
19
+ ## Default vs conditional pages
20
+
21
+ Typical builder setup:
22
+
23
+ | End page | `displayLogic` | Role |
24
+ |----------|----------------|------|
25
+ | End Page | none | Default thank-you screen |
26
+ | End Page 2 | OR/AND conditions | Shown when rules match |
27
+
28
+ Example: show **End Page 2** when multiselect MCQ **contains** Option 1 **and** Option 3 (both must be selected). Otherwise show the default end page.
29
+
30
+ For `SELECTED_OPTIONS` / `CONTAINS`, multiple values in the rule mean **all** listed options must be selected (Sprinklr multiselect semantics). A single listed option still matches when only that option is selected.
31
+
32
+ ## Custom fields
33
+
34
+ When end-page rules reference `conditionEntity.entityType: 'CUSTOM_FIELD'`, pass the same `options.customFieldValues` map used for skip/display logic:
35
+
36
+ ```typescript
37
+ useSurveySDK(instanceId, {
38
+ customFieldValues: {
39
+ '_c_6a436c1244764c9d4a6ce2fc': 'end2',
40
+ '_c_6a436c3644764c9d4a6ce3f5': 2,
41
+ },
42
+ });
43
+ ```
44
+
45
+ Without this map, custom-field branches evaluate as empty/missing.
46
+
47
+ ### OR branches and static mock values
48
+
49
+ Conditional end pages often use **OR** groups (any branch true → show that page). If `customFieldValues` includes a value that satisfies one branch unconditionally—e.g. number field `= 2` while the survey has `NUMBER EQUALS 2` in an OR branch—that end page will **always** win at submit, even when MCQ/NPS answers would otherwise show the default page.
50
+
51
+ When testing answer-driven branches, omit unrelated custom-field keys or set them to values that do not match other OR branches.
52
+
53
+ ## Client responsibilities
54
+
55
+ - Render `<EndPage page={state.specialPage} />` when `state.phase.kind === 'end'`.
56
+ - Use `state.phase.endPageId` (and `options.debug`) during development to verify selection when headers match.
57
+ - Pass `customFieldValues` via hook options when the survey's end pages reference custom fields.
58
+ - No logic evaluation in the client — the SDK resolves `endPageId` before setting phase.
59
+
60
+ ## Supported condition fields
61
+
62
+ Same as skip/display logic: `SELECTED_OPTIONS`, `NUM_OF_SELECTED_OPTIONS`, `TEN_POINT_SCALE`, `TEXT`, `CASE_SENSITIVE_TEXT`, custom-field `NUMBER`, `PICKLIST`, `DATETIME`, etc.
63
+
64
+ See also: [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md), [`01-components/15-end-page.md`](../01-components/15-end-page.md).
@@ -0,0 +1,74 @@
1
+ # Partial Save and Recovery
2
+
3
+ > Covers `sdkFeatures.partialSave` — debounced autosave of in-progress responses.
4
+
5
+ ---
6
+
7
+ ## When to enable
8
+
9
+ Pass in `useSurveySDK` options:
10
+
11
+ ```typescript
12
+ useSurveySDK({
13
+ ...surveyOptions,
14
+ savePartialResponse: true, // default debounce
15
+ // or
16
+ savePartialResponse: { debounceMs: 1500 },
17
+ });
18
+ ```
19
+
20
+ Enable when the survey config or client prompt requires resume/recovery UX. If not requested, omit the option.
21
+
22
+ ---
23
+
24
+ ## `partialSaveResults` shape
25
+
26
+ Same surface as submit results (type inferred from hook return):
27
+
28
+ | Field | Meaning |
29
+ |-------|---------|
30
+ | `partialSaveResults.data` | Last successful partial save payload (`SubmitResult \| null`) |
31
+ | `partialSaveResults.isLoading` | POST in flight |
32
+ | `partialSaveResults.error` | Last failure |
33
+
34
+ Monitor these for optional "Saving…" / error toasts. Do **not** reimplement the POST endpoint logic client-side.
35
+
36
+ ---
37
+
38
+ ## Interaction with skip / display logic
39
+
40
+ When skip or display logic hides a question, the SDK **sanitizes** answers for questions no longer on the active path before partial save. Client components should not manually delete hidden answers — the SDK handles consistency on path changes.
41
+
42
+ After a partial save succeeds, reloading the survey instance may restore prior answers depending on backend behavior (outside SDK scope).
43
+
44
+ ---
45
+
46
+ ## When partial save starts
47
+
48
+ Partial saves begin only after the **first user interaction** (a real `CHANGE` action). MCQ default seeding or programmatic answer init does not trigger an immediate save.
49
+
50
+ Each subsequent debounced answer change triggers another partial save with `surveySubmitted: false`.
51
+
52
+ ---
53
+
54
+ ## Error UX patterns
55
+
56
+ | Scenario | Recommended UX |
57
+ |----------|----------------|
58
+ | Transient network error | Silent retry on next debounced change; optional subtle indicator |
59
+ | Persistent error | Non-blocking banner: "Unable to save progress" |
60
+ | Success | Usually no UI — optional checkmark on footer |
61
+
62
+ Do not block navigation (NEXT/PREVIOUS) on partial save failure unless product explicitly requires it.
63
+
64
+ ---
65
+
66
+ ## Verify checklist
67
+
68
+ - [ ] `savePartialResponse` only set when required
69
+ - [ ] No duplicate fetch/submit logic outside SDK
70
+ - [ ] `TEXT_AND_MEDIA` questions produce no CHANGE (correct — no answer to save)
71
+ - [ ] Hidden-path answers not manually managed in client state
72
+ - [ ] Optional UI wired to `partialSaveResults.isLoading` / `.error`
73
+
74
+ See [`useSurveySDK.md`](useSurveySDK.md) for hook signature and [`client-integration-guide.md`](client-integration-guide.md) for full wiring map.
@@ -0,0 +1,116 @@
1
+ # Placeholders and Tokens
2
+
3
+ > Covers `sdkFeatures.placeholderResolution` — how CRM and dynamic answer tokens resolve in question text.
4
+
5
+ ---
6
+
7
+ ## Token formats
8
+
9
+ | Token pattern | Example | Resolved by |
10
+ |---------------|---------|-------------|
11
+ | CRM / static placeholder | `{{FIRST_NAME}}`, `{{COMPANY}}` | SDK at map time via `options.placeholders` |
12
+ | Survey question answer | `{{SURVEY_QUESTION-:-questionId}}` | Client at render time via `resolveSurveyQuestionPlaceholders` |
13
+ | Display variant text | Variant bodies may contain both | SDK resolves static tokens on variants; client resolves answer tokens |
14
+
15
+ Question IDs in answer tokens match the fetched survey's `question.id` (not display order).
16
+
17
+ ---
18
+
19
+ ## Stable `placeholders` ref
20
+
21
+ Define placeholders **outside** the React component (module scope or parent file):
22
+
23
+ ```typescript
24
+ const SURVEY_PLACEHOLDERS = {
25
+ FIRST_NAME: 'Alex',
26
+ COMPANY: 'Acme Corp',
27
+ } as const;
28
+ ```
29
+
30
+ Wrap hook options in `useMemo` so the SDK does not re-fetch on every render:
31
+
32
+ ```typescript
33
+ const surveyOptions = useMemo(
34
+ () => ({
35
+ instanceId: SURVEY_INSTANCE_ID,
36
+ language: selectedLanguage,
37
+ placeholders: SURVEY_PLACEHOLDERS,
38
+ customFieldValues: SURVEY_CUSTOM_FIELDS,
39
+ }),
40
+ [selectedLanguage]
41
+ );
42
+ ```
43
+
44
+ **Failure mode:** Inline `placeholders={{ FIRST_NAME: '...' }}` inside the component causes infinite fetch loops.
45
+
46
+ ---
47
+
48
+ ## Where SDK resolves vs client responsibility
49
+
50
+ | Stage | What happens |
51
+ |-------|--------------|
52
+ | **Fetch / map time** | SDK applies language translation + static CRM placeholders to question text and display variants |
53
+ | **`currentQuestions`** | Each question's `questionText` is already variant-resolved for the active path |
54
+ | **Render time (client)** | `Question.tsx` calls `resolveSurveyQuestionPlaceholders(text, answers, questionsById)` for `{{SURVEY_QUESTION-:-id}}` tokens |
55
+
56
+ Do **not** reimplement CRM token substitution in components — only answer-reference tokens need client resolution.
57
+
58
+ ---
59
+
60
+ ## `resolveSurveyQuestionPlaceholders`
61
+
62
+ Import from package root:
63
+
64
+ ```typescript
65
+ import { resolveSurveyQuestionPlaceholders } from '@explorer02/cfm-survey-sdk';
66
+ ```
67
+
68
+ Typical usage in `Question.tsx`:
69
+
70
+ ```typescript
71
+ const resolvedText = resolveSurveyQuestionPlaceholders(
72
+ question.questionText,
73
+ allAnswers,
74
+ questionsById
75
+ );
76
+ ```
77
+
78
+ Pass **all** answers and a `questionsById` map built from the full survey (not just `currentQuestions`) so cross-page answer tokens resolve.
79
+
80
+ ---
81
+
82
+ ## `formatAnswerForDisplay`
83
+
84
+ When a token references another question's answer, the SDK formats the stored answer for human-readable display:
85
+
86
+ ```typescript
87
+ import { formatAnswerForDisplay } from '@explorer02/cfm-survey-sdk';
88
+
89
+ // Used internally by resolveSurveyQuestionPlaceholders; call directly only for custom UI
90
+ const label = formatAnswerForDisplay(question, answerValue);
91
+ ```
92
+
93
+ Matrix answers format as row/column labels; MCQ as option text; rank order as ordered list.
94
+
95
+ ---
96
+
97
+ ## Wiring checklist
98
+
99
+ - [ ] `SURVEY_PLACEHOLDERS` defined at module scope (stable ref)
100
+ - [ ] `surveyOptions` wrapped in `useMemo`
101
+ - [ ] `Question` receives `allAnswers` + `allQuestions` (or `questionsById`)
102
+ - [ ] Question shell renders `resolveSurveyQuestionPlaceholders` output, not raw `questionText` when answer tokens may appear
103
+ - [ ] Display variants: trust SDK-resolved text on `currentQuestions`; still run answer-token pass at render
104
+
105
+ ---
106
+
107
+ ## Failure modes
108
+
109
+ | Symptom | Cause | Fix |
110
+ |---------|-------|-----|
111
+ | Token shows literally `{{FIRST_NAME}}` | Missing key in `placeholders` map | Add key matching token name (without braces) |
112
+ | Answer token empty | Referenced question unanswered or hidden | Expected until answered; hidden answers sanitized on skip path change |
113
+ | Infinite API requests | Unstable `placeholders` ref | Move outside component; `useMemo` on options |
114
+ | Expired JWT / fetch error | Instance ID invalid | Refresh token; placeholders unrelated |
115
+
116
+ See also [`question-display-variants.md`](question-display-variants.md) and [`client-integration-guide.md`](client-integration-guide.md).