@explorer02/cfm-survey-sdk 0.2.1 → 0.2.3

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 (95) 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 -69
  9. package/templates/AGENT.md +18 -8
  10. package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
  11. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  12. package/templates/docs/00-integration/aws-deploy.md +281 -0
  13. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  14. package/templates/docs/00-integration/component-checklist.md +55 -22
  15. package/templates/docs/00-integration/constraints.md +49 -4
  16. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  17. package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
  18. package/templates/docs/00-integration/end-page-logic.md +64 -0
  19. package/templates/docs/00-integration/file-upload-aws.md +116 -0
  20. package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
  21. package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
  22. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  23. package/templates/docs/00-integration/progress.md +61 -0
  24. package/templates/docs/00-integration/question-display-variants.md +65 -0
  25. package/templates/docs/00-integration/question-numbering.md +51 -0
  26. package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
  27. package/templates/docs/00-integration/setup.md +84 -0
  28. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  29. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  30. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  31. package/templates/docs/01-components/01-survey-page.md +76 -54
  32. package/templates/docs/01-components/02-question.md +61 -58
  33. package/templates/docs/01-components/03-rating-scale.md +11 -1
  34. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  35. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  36. package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
  37. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  38. package/templates/docs/01-components/08-file-upload-scale.md +23 -51
  39. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  40. package/templates/docs/01-components/11-progress-bar.md +10 -4
  41. package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
  42. package/templates/docs/01-components/14-intro-page.md +34 -0
  43. package/templates/docs/01-components/15-end-page.md +31 -0
  44. package/templates/docs/01-components/16-paused-page.md +27 -0
  45. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  46. package/templates/docs/01-components/18-rank-order-scale.md +93 -0
  47. package/templates/docs/01-components/README.md +42 -36
  48. package/templates/docs/02-reference/config-field-index.md +141 -139
  49. package/templates/docs/02-reference/exports.md +149 -0
  50. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  51. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  52. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  53. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  54. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  55. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  56. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  57. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  58. package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
  59. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  60. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  61. package/templates/docs/02-reference/question-types/README.md +96 -47
  62. package/templates/docs/02-reference/routing-table.md +33 -18
  63. package/templates/docs/02-reference/value-derivation.md +63 -0
  64. package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
  65. package/templates/docs/03-ui-specs/01-rating.md +18 -52
  66. package/templates/docs/03-ui-specs/02-radio.md +80 -37
  67. package/templates/docs/03-ui-specs/03-text.md +2 -2
  68. package/templates/docs/03-ui-specs/04-csat.md +68 -33
  69. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  70. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  71. package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
  72. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
  73. package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
  74. package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
  75. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  76. package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
  77. package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
  78. package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
  79. package/templates/docs/03-ui-specs/README.md +16 -14
  80. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
  81. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  82. package/templates/docs/MANIFEST.json +476 -69
  83. package/templates/docs/index.md +82 -30
  84. package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
  85. package/templates/docs/templates/MatrixDropdown.tsx +216 -0
  86. package/templates/docs/templates/Question.tsx +221 -154
  87. package/templates/docs/templates/RankOrderScale.tsx +353 -0
  88. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  89. package/templates/docs/templates/heatmapCoords.ts +58 -0
  90. package/templates/docs/templates/implementation_plan.md +126 -67
  91. package/templates/docs/templates/survey-inventory.schema.json +77 -0
  92. package/templates/docs/templates/surveyUiIcons.tsx +52 -0
  93. package/templates/docs/templates/verify-agent-build.sh +85 -0
  94. package/templates/docs/01-components/04-csat-scale.md +0 -40
  95. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -1,34 +1,60 @@
1
1
  # File Upload Question
2
2
 
3
- > **Type**: `'file_upload'` | **API Source**: `FILE_UPLOAD`
3
+ > **Type**: `'FILE_UPLOAD'` | **API Source**: `FILE_UPLOAD`
4
4
  > **Mapper**: `fileUploadMapper.ts` | **Component**: `FileUploadScale`
5
+ > **Upload flow**: [`00-integration/file-upload-aws.md`](../../00-integration/file-upload-aws.md)
5
6
 
6
7
  ## TypeScript Type
7
8
 
8
9
  ```typescript
9
10
  type FileUploadQuestion = QuestionBase & {
10
- type: 'file_upload';
11
- uploadMessage?: string; // Instruction text (HTML)
12
- supportedFileFormats?: string[]; // ['PDF', 'PNG', 'DOCX'] (uppercase, no dots)
13
- maxFileCount?: number; // Max files allowed
14
- fileSizeLimit?: number; // Max size in MB
11
+ type: 'FILE_UPLOAD';
12
+ uploadMessage?: string;
13
+ supportedFileFormats?: string[];
14
+ maxFileCount?: number;
15
+ fileSizeLimit?: number;
15
16
  fileSizeLimitType?: 'PER_FILE' | 'IN_TOTAL';
16
17
  };
17
18
 
18
- type UploadedFile = {
19
- id: string; name: string; mediaUrl: string;
20
- mimeType?: string; size?: number;
19
+ /** Stored in state.answers — required for submit */
20
+ type UploadedFileAnswer = {
21
+ id: string;
22
+ mediaUrl: string;
23
+ name?: string;
24
+ mimeType?: string;
25
+ size?: number;
21
26
  };
22
27
  ```
23
28
 
24
- ## Answer Shape: `UploadedFile[]`
25
- Array of uploaded file descriptors. Submitted via `quesIdVsAttachmentDetails` (NOT `questionToAnswers`).
29
+ ## Answer Shape: `UploadedFileAnswer[]`
30
+
31
+ Submitted via `quesIdVsAttachmentDetails` (NOT `questionToAnswers`). SDK submit formatter only accepts objects with `id` and `mediaUrl` — **raw browser `File[]` is dropped at submit**.
32
+
33
+ ## Two-phase client flow
34
+
35
+ 1. **Pick + validate locally** — extension, count, size (`PER_FILE` / `IN_TOTAL`)
36
+ 2. **Upload each file** — `POST /get-upload-url` → presigned PUT → collect `{ id, mediaUrl, name, mimeType, size }`
37
+ 3. **`onAction(CHANGE)`** — pass `UploadedFileAnswer[]` to SDK
38
+
39
+ See [`file-upload-aws.md`](../../00-integration/file-upload-aws.md) for API contract.
26
40
 
27
41
  ## Rendering Guidance
28
- - Drag-and-drop zone + click-to-upload button
42
+
43
+ - Drag-and-drop zone + click-to-upload
29
44
  - Show accepted formats from `supportedFileFormats`
30
- - Set `<input type="file" accept=".pdf,.png,.docx">`
31
- - Enforce `maxFileCount` in UI
32
- - Show file list with name, size, and remove button
33
- - Enforce `fileSizeLimit` with appropriate error messages
34
- - `onSelect` receives the full `UploadedFile[]` array
45
+ - Set `<input type="file" accept=".pdf,.png,...">` from formats
46
+ - Enforce `maxFileCount` and `fileSizeLimit` before upload
47
+ - Show file list with name, size, remove button, uploading spinner per file
48
+ - `onSelect(undefined)` when all files removed
49
+
50
+ ## Skip logic
51
+
52
+ Presence-only (`EXISTS` / `MISSING`) — evaluated on page entry; no discrete value-change events required.
53
+
54
+ ## SDK Integration Checklist (agent)
55
+
56
+ - [ ] Type literal: `QUESTION_TYPE.FILE_UPLOAD`
57
+ - [ ] Answer stored as: `UploadedFileAnswer[]` after upload completes
58
+ - [ ] onAction CHANGE: `{ questionId, answerValue: UploadedFileAnswer[] }`
59
+ - [ ] Do **not** store raw `File[]` as final answer
60
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../../00-integration/question-type-sdk-matrix.md#file_upload)
@@ -0,0 +1,59 @@
1
+ # Heatmap Question (`HEATMAP`)
2
+
3
+ > **SDK type:** `HeatmapQuestion` in `fetchSurvey/types/heatmap.ts`
4
+ > **UI spec:** `03-ui-specs/13-heatmap.md`
5
+
6
+ ## API Source
7
+
8
+ - `answerType: "HEATMAP"`
9
+ - `questionAssets[0]` → image URL
10
+ - `surveyHeatmapMetadata` → regions, max clicks, canvas dimensions
11
+
12
+ ## Question Fields
13
+
14
+ | Field | Type | Description |
15
+ |-------|------|-------------|
16
+ | `type` | `'HEATMAP'` | Dispatch literal |
17
+ | `imageUrl` | `string` | Required image for click map |
18
+ | `regions` | `HeatmapRegion[]` | Normalized 0–1 rects for logic |
19
+ | `maxClicksAllowed` | `number` | 1–20, from `totalClickAllowed` |
20
+ | `referenceWidth` | `number` | Canvas width used for region normalization |
21
+ | `referenceHeight` | `number` | Canvas height used for region normalization |
22
+
23
+ ## Answer Shape
24
+
25
+ ```typescript
26
+ type HeatmapClickPoint = { id: string; x: number; y: number };
27
+ type HeatmapAnswer = HeatmapClickPoint[];
28
+ ```
29
+
30
+ Stored in `state.answers[questionId]`. Submit serializes to:
31
+
32
+ ```typescript
33
+ questionToAnswers[questionId] = [JSON.stringify(clicks)];
34
+ ```
35
+
36
+ ## Logic Fields
37
+
38
+ | Field | Operators | Semantics |
39
+ |-------|-----------|-----------|
40
+ | `HEATMAP` | EXISTS / MISSING | Any click points present |
41
+ | `HEATMAP_REGION` | CONTAINS / DOES_NOT_CONTAIN | Any listed region label has ≥1 click inside |
42
+
43
+ Logic values use region **labels** (e.g. `"Region 1"`), not shape ids.
44
+
45
+ ## Component
46
+
47
+ `HeatmapScale` — see `01-components/17-heatmap-scale.md`
48
+
49
+ ## SDK Integration Checklist (agent)
50
+
51
+ - [ ] Type literal: `QUESTION_TYPE.HEATMAP`
52
+ - [ ] Answer stored as: `HeatmapAnswer` (`HeatmapClickPoint[]`)
53
+ - [ ] onAction CHANGE payload: `{ questionId, answerValue: HeatmapClickPoint[] }` on click add/remove
54
+ - [ ] Visibility helper: N/A
55
+ - [ ] Custom fields: skip/display rules only
56
+ - [ ] Logic features: skip ✓ display ✓ answer ✗ variants ✓ numbering ✓
57
+ - [ ] Performance: normalize coords 0–1; copy [`templates/heatmapCoords.ts`](../../templates/heatmapCoords.ts)
58
+ - [ ] Respect `maxClicksAllowed`; use `findSpotNearClick` for spot hit testing
59
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#heatmap`](../../00-integration/question-type-sdk-matrix.md#heatmap)
@@ -0,0 +1,101 @@
1
+ # Rank Order Question (`RANK_ORDER`)
2
+
3
+ > **SDK type:** `RankOrderQuestion` in `fetchSurvey/types/rankOrder.ts`
4
+ > **UI spec:** `03-ui-specs/14-rank-order.md`
5
+
6
+ ## API Source
7
+
8
+ - `answerType: "RANK_ORDER"`
9
+ - `answerDetails[]` → options (text + optional `answerAsset`)
10
+ - `questionConfigs`:
11
+ - `RANK_ORDER_ANSWER_TYPE` → `DROPDOWN` | `DRAG_AND_DROP`
12
+ - `OPTION_TYPE` → `TEXT_ONLY` | `IMAGE_ONLY` | `TEXT_AND_IMAGE`
13
+ - `REQUIRE_RANK_ALL` → boolean
14
+ - `SHUFFLE_ORDER` → boolean
15
+ - `additionalInfo.hasOptionLevelAnswers: true` → submit uses `optionLevelAnswers`
16
+
17
+ ## Question Fields
18
+
19
+ | Field | Type | API config |
20
+ |-------|------|------------|
21
+ | `type` | `'RANK_ORDER'` | `answerType` |
22
+ | `options` | `RankOrderOption[]` | `answerDetails[]` |
23
+ | `interactionMode` | `'dropdown' \| 'dragAndDrop'` | `RANK_ORDER_ANSWER_TYPE` |
24
+ | `optionDisplay` | `'textOnly' \| 'imageOnly' \| 'textAndImage'` | `OPTION_TYPE` |
25
+ | `requireRankAll` | `boolean` | `REQUIRE_RANK_ALL` |
26
+ | `shuffleOptions` | `boolean` | `SHUFFLE_ORDER` |
27
+
28
+ ### RankOrderOption
29
+
30
+ | Field | Source |
31
+ |-------|--------|
32
+ | `id` | `answerDetails[].id` |
33
+ | `optionLabel` | `answerDetails[].answerText` |
34
+ | `imageUrl` | `answerAsset.source` |
35
+ | `previewImageUrl` | `answerAsset.previewImageUrl` |
36
+ | `answerLogic` | option `controlConfig.displayLogic` |
37
+
38
+ ## Answer Shape
39
+
40
+ ```typescript
41
+ /** optionId → 1-based rank. Partial map allowed when requireRankAll is false. */
42
+ type RankOrderAnswers = Record<string, number>;
43
+ ```
44
+
45
+ Stored in `state.answers[questionId]`.
46
+
47
+ ## Submit Shape
48
+
49
+ Both fields are populated for rank-order questions:
50
+
51
+ ```typescript
52
+ // Ordered option ids from rank 1 → N
53
+ questionToAnswers[questionId] = ['optionIdRank1', 'optionIdRank2', ...];
54
+
55
+ // Per-option rank (API key uses -:- separator)
56
+ optionLevelAnswers[`${questionId}-:-${optionId}`] = [String(rank)];
57
+ ```
58
+
59
+ ## Logic Field: `RANK_ORDER`
60
+
61
+ Conditions target a **specific option** via `details.subConditionEntity` (`entityType: 'OPTION'`).
62
+
63
+ | UI label | API operator | Values | Semantics |
64
+ |----------|--------------|--------|-----------|
65
+ | Ranked as | `EQUALS` | rank number | Subject option rank equals value |
66
+ | Ranked more than | `GT` | rank number | Subject rank > value |
67
+ | Ranked less than | `LT` | rank number | Subject rank < value |
68
+ | Ranked above | `BEFORE` | reference option id | Subject rank < reference rank |
69
+ | Ranked below | `AFTER` | reference option id | Subject rank > reference rank |
70
+
71
+ Also supports `EXISTS` / `MISSING` for the whole question answer.
72
+
73
+ Works in skip, display, answer, question variant, and end-page logic via the shared `evaluateCondition` pipeline.
74
+
75
+ ## SDK Helpers (client-safe)
76
+
77
+ ```typescript
78
+ import {
79
+ assignRankWithoutDuplicates,
80
+ buildRankOrderFromOrderedOptionIds,
81
+ getOrderedOptionIdsFromRanks,
82
+ getVisibleRankOrderOptionsForAnswers,
83
+ normalizeRankOrderAnswers,
84
+ } from '@explorer02/cfm-survey-sdk';
85
+ ```
86
+
87
+ ## Component
88
+
89
+ `RankOrderScale` — see `01-components/18-rank-order-scale.md`
90
+
91
+ ## SDK Integration Checklist (agent)
92
+
93
+ - [ ] Type literal: `QUESTION_TYPE.RANK_ORDER`
94
+ - [ ] Answer stored as: `RankOrderAnswers` (`Record<optionId, rank>`)
95
+ - [ ] onAction CHANGE payload: `{ questionId, answerValue: RankOrderAnswers }` after reorder
96
+ - [ ] Visibility helper: `getVisibleRankOrderOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`
97
+ - [ ] Custom fields: 4th arg on rank visibility helper when logic references CRM fields
98
+ - [ ] Logic features: skip ✓ display ✓ answer ✓ (options) variants ✓ numbering ✓
99
+ - [ ] Performance: prefer `option.imageUrl` over `previewImageUrl`; install `@dnd-kit/*` for drag mode
100
+ - [ ] Value utils: `assignRankWithoutDuplicates`, `buildRankOrderFromOrderedOptionIds`, `getOrderedOptionIdsFromRanks`, `normalizeRankOrderAnswers`
101
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#rank_order`](../../00-integration/question-type-sdk-matrix.md#rank_order)
@@ -1,71 +1,120 @@
1
1
  # Question Types Reference
2
2
 
3
3
  > Read **on demand** — only for types present in the fetched survey.
4
- > UI implementation: `01-components/`. Routing table: `02-reference/routing-table.md`.
5
4
 
6
- ## Discriminated Union
5
+ ## Discriminated Union (11 types)
7
6
 
8
7
  ```typescript
9
8
  type SurveyQuestion =
10
- | RatingQuestion // 'rating'
11
- | RadioQuestion // 'radio'
12
- | TextQuestion // 'text'
13
- | CsatQuestion // 'csat'
14
- | RatingScaleQuestion // 'rating_scale'
15
- | SliderQuestion // 'slider'
16
- | MatrixQuestion // 'matrix' (check subType)
17
- | RatingMatrixQuestion // 'rating_matrix'
18
- | SliderMatrixQuestion // 'slider_matrix'
19
- | FileUploadQuestion // 'file_upload'
20
- | TextAndMediaQuestion; // 'text_and_media'
9
+ | McqQuestion // 'MCQ'
10
+ | NpsQuestion // 'NPS_SCALE'
11
+ | TextInputQuestion // 'TEXTFIELD'
12
+ | CsatQuestion // 'CSAT_MATRIX'
13
+ | CfmMatrixQuestion // 'CFM_MATRIX'
14
+ | RatingMatrixQuestion // 'RATING_MATRIX'
15
+ | SliderMatrixQuestion // 'SLIDER_MATRIX'
16
+ | FileUploadQuestion // 'FILE_UPLOAD'
17
+ | TextAndMediaQuestion // 'TEXT_AND_MEDIA'
18
+ | HeatmapQuestion // 'HEATMAP'
19
+ | RankOrderQuestion; // 'RANK_ORDER'
21
20
  ```
22
21
 
23
- ## Shared `QuestionBase` Properties
22
+ Use `QUESTION_TYPE` from the SDK for dispatch comparisons.
24
23
 
25
- | Property | Type | Notes |
26
- |----------|------|-------|
27
- | `id` | `string` | **MUST** be DOM `id` on wrapper |
28
- | `text` | `string` | HTML — use `dangerouslySetInnerHTML` |
29
- | `description` | `string?` | HTML |
30
- | `required` | `boolean?` | |
31
- | `requiredErrorMessage` | `string?` | Custom validation message |
32
- | `questionNumber` | `number?` | 1-indexed |
24
+ Each type is **standalone** open its file in exported SDK types for the complete field list.
25
+
26
+ > **Legacy:** `StarRatingQuestion` / `SliderQuestion` mapper types are not in the `SurveyQuestion` union. API RATING/SLIDER pages map into `NPS_SCALE`, matrix display styles, or `SLIDER_MATRIX`.
27
+
28
+ ## Shared field names (duplicated per type file)
29
+
30
+ | Property | Meaning |
31
+ |----------|---------|
32
+ | `id` | DOM `id` on wrapper |
33
+ | `questionText` | Main prompt (HTML) |
34
+ | `questionDescription?` | Supplementary HTML |
35
+ | `questionHint?` | Hint text |
36
+ | `required` | Mandatory flag |
37
+ | `requiredErrorMessage?` | Custom validation message |
38
+ | `questionNumber?` | 1-indexed position |
39
+ | `skipLogics?` | Forward skip rules evaluated on page leave |
40
+ | `displayLogic?` | Question-level show/hide (dynamic or page-level) |
41
+ | `displayVariants?` | Alternate `questionText` prompts selected by condition (SDK resolves on `currentQuestions`; see [`question-display-variants.md`](../../00-integration/question-display-variants.md)) |
42
+ | `answerLogic?` | On `McqOption`, `StatementRow`, or `ScaleColumn` — sub-entity show/hide (see [`answer-logic-and-navigation.md`](../../00-integration/answer-logic-and-navigation.md)) |
33
43
 
34
44
  ---
35
45
 
36
- ## radio {#radio}
46
+ ## MCQ {#mcq}
47
+
48
+ - **Type:** `McqQuestion` — `type: 'MCQ'`
49
+ - **Answer:** `string` or `string[]` (`option.id` values)
50
+ - **Options:** `McqOption[]` with `id`, `optionLabel`
51
+
52
+ | Field | Type | API source |
53
+ |-------|------|------------|
54
+ | `selectionMode` | `'single' \| 'multiple'` | multi-select config |
55
+ | `defaultOptionIds?` | `string[]` | `DEFAULT_ANSWER` when enabled |
56
+ | `minSelections?` | `number` | `ANSWER_CONDITION` / `MIN_ANSWER` |
57
+ | `maxSelections?` | `number` | `ANSWER_CONDITION` / `MAX_ANSWER` |
58
+ | `answerConditionErrorMessage?` | `string` | `ANSWER_CONDITION` error message |
59
+
60
+ **Default answers:** On survey load, SDK `buildInitialAnswers` / `applyMcqDefaults` pre-populates `state.answers[questionId]` from `defaultOptionIds` (single: first id; multi: full array). Client binds UI checked state to `state.answers` — do not maintain separate default state. SDK re-applies defaults on validation/submit if answer is still empty.
37
61
 
38
- - **Type:** `RadioQuestion` `type: 'radio'`
39
- - **Answer:** `string | number` (option id or value)
40
- - **UI:** Inline MCQ radio cards in `Question.tsx` — see `01-components/02-question.md`
41
- - **No separate reference doc needed**
62
+ ### SDK Integration Checklist (agent)
42
63
 
43
- ## text {#text}
64
+ - [ ] Type literal: `QUESTION_TYPE.MCQ`
65
+ - [ ] Answer stored as: `option.id` (string/number) or array when `selectionMode === 'multiple'`
66
+ - [ ] onAction CHANGE payload: `{ questionId, answerValue: optionId | optionId[] }`
67
+ - [ ] Visibility helper: `getVisibleMcqOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`
68
+ - [ ] Custom fields: 4th arg when logic references CRM fields
69
+ - [ ] Logic features: skip ✓ display ✓ answer ✓ (options) variants ✓ numbering ✓
70
+ - [ ] Performance: filter options via helper before render
71
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#mcq`](../../00-integration/question-type-sdk-matrix.md#mcq)
44
72
 
45
- - **Type:** `TextQuestion` — `type: 'text'`
73
+ ## TEXTFIELD {#textfield}
74
+
75
+ - **Type:** `TextInputQuestion` — `type: 'TEXTFIELD'`
46
76
  - **Answer:** `string`
47
- - **UI:** Inline `<textarea>` or `<input>` in `Question.tsx`
48
- - **No separate reference doc needed**
49
77
 
50
- ## text_and_media {#text_and_media}
78
+ ### SDK Integration Checklist (agent)
79
+
80
+ - [ ] Type literal: `QUESTION_TYPE.TEXTFIELD`
81
+ - [ ] Answer stored as: `string`
82
+ - [ ] onAction CHANGE payload: `{ questionId, answerValue: string }`
83
+ - [ ] Visibility helper: N/A
84
+ - [ ] Custom fields: skip/display only
85
+ - [ ] Logic features: skip ✓ display ✓ answer ✗ variants ✓ numbering ✓
86
+ - [ ] Performance: debounce long textarea onChange if partial save enabled
87
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#textfield`](../../00-integration/question-type-sdk-matrix.md#textfield)
88
+
89
+ ## TEXT_AND_MEDIA {#text_and_media}
51
90
 
52
- - **Type:** `TextAndMediaQuestion` — `type: 'text_and_media'`
53
- - **Answer:** none — display-only, skipped in validation and submission
54
- - **UI:** Inline `<video>` or `<img>` from `question.mediaUrl` — no scale component
55
- - **No separate reference doc needed**
91
+ - **Type:** `TextAndMediaQuestion` — `type: 'TEXT_AND_MEDIA'`
92
+ - **Answer:** none — display-only
93
+
94
+ ### SDK Integration Checklist (agent)
95
+
96
+ - [ ] Type literal: `QUESTION_TYPE.TEXT_AND_MEDIA`
97
+ - [ ] Answer stored as: none — no CHANGE action
98
+ - [ ] onAction: user advances with NEXT only
99
+ - [ ] Visibility helper: N/A
100
+ - [ ] Custom fields: skip/display only
101
+ - [ ] Logic features: skip ✓ display ✓ answer ✗ variants ✓ numbering ✓
102
+ - [ ] Performance: lazy-load media; respect `media.alignment`
103
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#text_and_media`](../../00-integration/question-type-sdk-matrix.md#text_and_media)
56
104
 
57
105
  ---
58
106
 
59
107
  ## Detailed Reference Docs
60
108
 
61
- | File | Type | When to read |
62
- |------|------|--------------|
63
- | `01-rating.md` | `rating` | NPS / numeric scales |
64
- | `04-csat.md` | `csat` | CSAT emoji/star (≤5 options) |
65
- | `05-rating-scale.md` | `rating_scale` | Star/emoji rating |
66
- | `06-slider.md` | `slider` | Single range slider |
67
- | `07-matrix-cfm.md` | `matrix` + `CFM_MATRIX` | Likert text grid |
68
- | `08-matrix-csat.md` | `matrix` + CSAT/RATING | Satisfaction grid |
69
- | `09-matrix-rating.md` | `rating_matrix` | NPS-style grid |
70
- | `10-slider-matrix.md` | `slider_matrix` | Slider grid |
71
- | `11-file-upload.md` | `file_upload` | File attachments |
109
+ | File | Type literal | When to read |
110
+ |------|--------------|--------------|
111
+ | `01-rating.md` | `NPS_SCALE` | NPS 0–10 scales |
112
+ | `04-csat.md` | `CSAT_MATRIX` | CSAT matrix grid |
113
+ | `07-matrix-cfm.md` | `CFM_MATRIX` | CFM matrix grid |
114
+ | `09-matrix-rating.md` | `RATING_MATRIX` | NPS-style rating matrix |
115
+ | `10-slider-matrix.md` | `SLIDER_MATRIX` | Slider grid |
116
+ | `11-file-upload.md` | `FILE_UPLOAD` | File attachments |
117
+ | `12-heatmap.md` | `HEATMAP` | Click-map heatmap |
118
+ | `13-rank-order.md` | `RANK_ORDER` | Rank order options |
119
+
120
+ Legacy docs (deprecated API mappings): `05-rating-scale.md`, `06-slider.md`
@@ -1,35 +1,50 @@
1
1
  # Question Type → Component Routing
2
2
 
3
- > Canonical mapping. For **UI implementation** (config branches, tokens, states), read `03-ui-specs/`. For wiring, read `01-components/`. For data shapes, read `02-reference/question-types/`. For field audit, read `config-field-index.md`.
4
-
5
- | `question.type` | `question.subType` | Component | Answer Shape | Reference Doc | UI Spec |
6
- |-----------------|-------------------|-----------|--------------|---------------|---------|
7
- | `'rating'` | | `RatingScale` | `number` | `question-types/01-rating.md` | `03-ui-specs/01-rating.md` |
8
- | `'radio'` | | Inline in `Question` | `string \| number` | `question-types/README.md#radio` | `03-ui-specs/02-radio.md` |
9
- | `'text'` | | Inline in `Question` | `string` | `question-types/README.md#text` | `03-ui-specs/03-text.md` |
10
- | `'csat'` | | `CsatScale` | `number \| null` | `question-types/04-csat.md` | `03-ui-specs/04-csat.md` |
11
- | `'rating_scale'` | | `RatingScale` | `number` | `question-types/05-rating-scale.md` | `03-ui-specs/05-rating-scale.md` |
12
- | `'slider'` | | `CustomSliderTrack` | `number` | `question-types/06-slider.md` | `03-ui-specs/06-slider.md` |
13
- | `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` | `question-types/07-matrix-cfm.md` | `03-ui-specs/07-matrix-cfm.md` |
14
- | `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` | `03-ui-specs/08-matrix-csat-rating.md` |
15
- | `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` | `03-ui-specs/08-matrix-csat-rating.md` |
16
- | `'rating_matrix'` | | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/09-matrix-rating.md` | `03-ui-specs/08-matrix-csat-rating.md` |
17
- | `'slider_matrix'` | | `SliderMatrixScale` | `MatrixAnswerMap` | `question-types/10-slider-matrix.md` | `03-ui-specs/09-slider-matrix.md` |
18
- | `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` | `question-types/11-file-upload.md` | `03-ui-specs/10-file-upload.md` |
19
- | `'text_and_media'` | | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` | `03-ui-specs/11-text-and-media.md` |
3
+ > Canonical mapping. Dispatch on `question.type` only each matrix variant is its own type (no `subType`).
4
+
5
+ | `question.type` | SDK Type | Component | Answer Shape | Reference Doc | UI Spec |
6
+ |-----------------|----------|-----------|--------------|---------------|---------|
7
+ | `MCQ` | `McqQuestion` | Inline in `Question` | `string \| string[]` | `question-types/README.md#mcq` | `03-ui-specs/02-radio.md` |
8
+ | `TEXTFIELD` | `TextInputQuestion` | Inline in `Question` | `string` | `question-types/README.md#textfield` | `03-ui-specs/03-text.md` |
9
+ | `NPS_SCALE` | `NpsQuestion` | `RatingScale` | `number` | `question-types/01-rating.md` | `03-ui-specs/01-rating.md` |
10
+ | `CFM_MATRIX` | `CfmMatrixQuestion` | `LikertMatrixScale` | `MatrixRowAnswers` | `question-types/07-matrix-cfm.md` | `03-ui-specs/07-matrix-cfm.md` |
11
+ | `CSAT_MATRIX` | `CsatQuestion` | `CsatMatrixScale` | `MatrixRowAnswers` | `question-types/04-csat.md` | `03-ui-specs/04-csat.md` |
12
+ | `RATING_MATRIX` | `RatingMatrixQuestion` | `CsatMatrixScale` | `MatrixRowAnswers` | `question-types/09-matrix-rating.md` | `03-ui-specs/08-matrix-csat-rating.md` |
13
+ | `SLIDER_MATRIX` | `SliderMatrixQuestion` | `SliderMatrixScale` | `MatrixRowAnswers` | `question-types/10-slider-matrix.md` | `03-ui-specs/09-slider-matrix.md` |
14
+ | `FILE_UPLOAD` | `FileUploadQuestion` | `FileUploadScale` | `UploadedFileAnswer[]` | `question-types/11-file-upload.md` | `03-ui-specs/10-file-upload.md` |
15
+ | `TEXT_AND_MEDIA` | `TextAndMediaQuestion` | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` | `03-ui-specs/11-text-and-media.md` |
16
+ | `HEATMAP` | `HeatmapQuestion` | `HeatmapScale` | `HeatmapAnswer` | `question-types/12-heatmap.md` | `03-ui-specs/13-heatmap.md` |
17
+ | `RANK_ORDER` | `RankOrderQuestion` | `RankOrderScale` | `RankOrderAnswers` | `question-types/13-rank-order.md` | `03-ui-specs/14-rank-order.md` |
18
+
19
+ Use `QUESTION_TYPE` from the SDK for dispatch:
20
+
21
+ ```typescript
22
+ import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
23
+
24
+ {question.type === QUESTION_TYPE.NPS_SCALE && <RatingScale ... />}
25
+ ```
26
+
27
+ > **Legacy note:** API RATING/SLIDER page types are mapped into `NPS_SCALE`, matrix display styles, or `SLIDER_MATRIX` — not separate `question.type` values.
28
+
29
+ ## SDK hook contract
30
+
31
+ - Read answers from `state.answers` (`SurveyAnswers`)
32
+ - Dispatch `{ type: 'CHANGE', payload: { questionId, answerValue } }`
33
+ - Pass `allAnswers` and `allQuestions` to `Question` for placeholder resolution
20
34
 
21
35
  ## Component Blueprint Docs
22
36
 
23
37
  | Component | Blueprint |
24
38
  |-----------|-----------|
25
39
  | `SurveyPage` | `01-components/01-survey-page.md` |
40
+ | `IntroPage` / `EndPage` / `PausedPage` | `01-components/14-intro-page.md`, `15-end-page.md`, `16-paused-page.md` |
26
41
  | `Question` | `01-components/02-question.md` |
27
42
  | `RatingScale` | `01-components/03-rating-scale.md` |
28
- | `CsatScale` | `01-components/04-csat-scale.md` |
29
43
  | `CsatMatrixScale` | `01-components/05-csat-matrix-scale.md` |
30
44
  | `LikertMatrixScale` | `01-components/06-likert-matrix-scale.md` |
31
45
  | `SliderMatrixScale` | `01-components/07-slider-matrix-scale.md` |
32
46
  | `FileUploadScale` | `01-components/08-file-upload-scale.md` |
47
+ | `HeatmapScale` | `01-components/17-heatmap-scale.md` |
33
48
  | `CustomSliderTrack` | `01-components/09-custom-slider-track.md` |
34
49
  | `Header` / `Footer` | `01-components/10-header-footer.md` |
35
50
  | `ProgressBar` | `01-components/11-progress-bar.md` |
@@ -0,0 +1,63 @@
1
+ # Client-Side Value Derivation
2
+
3
+ > SDK types expose **display** fields only for scale options and matrix columns. Submit values are derived at submit/partial-save time.
4
+
5
+ ## Matrix columns (`ScaleColumn`)
6
+
7
+ **Import from SDK** (preferred):
8
+
9
+ ```typescript
10
+ import { matrixColumnStoredValue } from '@explorer02/cfm-survey-sdk';
11
+ // monorepo: import { matrixColumnStoredValue } from '@repo/sdk';
12
+ ```
13
+
14
+ ```typescript
15
+ type ScaleColumn = { id: string; label: string };
16
+
17
+ // matrixColumnStoredValue(column) → column.id, except N/A → null
18
+ ```
19
+
20
+ - **Stored matrix answers** use `matrixColumnStoredValue(col)` → always the column **`id`** (string), except N/A → `null`
21
+ - Matrix answers: `MatrixRowAnswers = Record<statementRowId, columnId | columnId[] | null>`
22
+ - **Submit / partial save**: SDK `resolveSubmitValues` maps stored column ids → API label or numeric string (id-only lookup)
23
+ - N/A column: SDK adds `{ id: 'na', label: 'N/A' }` when `hasNotApplicableOption` is true
24
+ - Filter N/A from scale pickers: `scaleColumns.filter(col => col.id !== 'na')`
25
+ - Slider matrix rows still store numeric slider positions (unchanged)
26
+
27
+ Monorepo client mirror: `apps/client/src/lib/surveyUi/scaleValues.ts` exports the same helper for matrix UI components.
28
+
29
+ ## NPS / star rating options
30
+
31
+ Options are `{ id: string; optionLabel: string }` — no `optionValue` on the type.
32
+
33
+ ```typescript
34
+ function npsValueFromOption(option: NpsScalePoint, index: number): number {
35
+ const numeric = Number(option.optionLabel);
36
+ return Number.isFinite(numeric) && option.optionLabel.trim() !== '' ? numeric : index;
37
+ }
38
+ ```
39
+
40
+ NPS questions use `question.type === 'NPS_SCALE'`. Store a **number** in `SurveyAnswers`. Badge colors: traffic-light table keyed by numeric value (0–10).
41
+
42
+ ## MCQ
43
+
44
+ - Selected answer: **`option.id`** (string), not a separate `optionValue` field
45
+
46
+ ## Rank order
47
+
48
+ - Stored answer: **`RankOrderAnswers`** — `Record<optionId, rankNumber>` (1-based)
49
+ - Dropdown: use `assignRankWithoutDuplicates(current, optionId, rank)` before `onSelect`
50
+ - Drag-and-drop: use `buildRankOrderFromOrderedOptionIds(orderedOptionIds)` after reorder
51
+ - Submit / partial save:
52
+ - `questionToAnswers[questionId]` → ordered **option id** array (rank 1 → N)
53
+ - `optionLevelAnswers[`${questionId}-:-${optionId}`]` → `[String(rank)]`
54
+ - Filter visible options: `getVisibleRankOrderOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`
55
+
56
+ ## Media
57
+
58
+ - Shell side media: `question.containerMedia?.url`, `containerMedia?.mimeType`, etc.
59
+ - Text & media block: `question.media?.url`, `question.media?.title`, etc.
60
+
61
+ ## Shared types (exported)
62
+
63
+ `StatementRow`, `ScaleColumn`, `ContainerMedia`, `QuestionMedia`, `SurveyAnswers`, `matrixColumnStoredValue` — see [`exports.md`](exports.md).