@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,102 +1,33 @@
1
1
  # UI Spec: Question Shell (`Question.tsx`)
2
2
 
3
3
  > **Canonical template:** [`templates/Question.tsx`](../templates/Question.tsx) — copy this dispatcher verbatim
4
- > **Monorepo reference:** `apps/client/src/components/Question.tsx` (not available in npm installs implement from this spec)
5
- > **SDK:** All types extend `QuestionBase` from `fetchSurvey/types/base.ts`
4
+ > **SDK:** Each question type is standalonesee `fetchSurvey/types/*.ts`
6
5
  > **Component blueprint:** `01-components/02-question.md`
7
- > **Required files:** `00-integration/component-checklist.md`
8
6
 
9
- ## Matrix subType Warning (CRITICAL)
7
+ ## Matrix routing (CRITICAL)
10
8
 
11
- Routing on `question.type === 'matrix'` **alone renders nothing**. You MUST branch on `subType`:
9
+ Each matrix variant has its own `question.type` **no `subType`**:
12
10
 
13
11
  - `CFM_MATRIX` → `LikertMatrixScale`
14
12
  - `CSAT_MATRIX` | `RATING_MATRIX` → `CsatMatrixScale`
13
+ - `SLIDER_MATRIX` → `SliderMatrixScale`
15
14
 
16
- Never use a `default` fallback that shows `"not yet implemented"` — create the scale components listed in the checklist.
15
+ Never use a `default` fallback that shows `"not yet implemented"`.
17
16
 
18
- ## Config Inventory (QuestionBase)
17
+ ## Shell fields (duplicated on every question type)
19
18
 
20
19
  | Field | Implemented | UI impact |
21
20
  |-------|-------------|-----------|
22
- | `id` | ✅ | `section id={question.id}` — required for scroll-to-error |
23
- | `text` | ✅ | Heading via `dangerouslySetInnerHTML` |
24
- | `description` | ✅ | Optional HTML block below title |
25
- | `required` | | Red `*` after title (`text-red-500`) |
26
- | `requiredErrorMessage` | ✅ | Shown via SDK validation in error banner |
27
- | `questionNumber` | | Not rendered in reference client |
28
- | `containerMediaUrl` | | Not rendered on generic questions |
29
-
30
- ## Layout Anatomy
31
-
32
- ```
33
- ┌─ section (rounded-xl border border-gray-200 bg-white p-6 shadow-sm) ─────┐
34
- │ h2: question.text (HTML) + red * if required │
35
- │ [optional description HTML] │
36
- │ ┌─ type-specific scale (see per-type specs) ─────────────────────┐ │
37
- │ └─────────────────────────────────────────────────────────────────┘ │
38
- │ [validation error banner if validationError prop] │
39
- └─────────────────────────────────────────────────────────────────────────┘
40
- ```
41
-
42
- ## Dispatcher Routing
43
-
44
- | `question.type` | Condition | Component |
45
- |-----------------|-----------|-----------|
46
- | `rating` | — | `RatingScale` + label row in Question |
47
- | `radio` | — | Inline card radios in Question |
48
- | `text` | — | Inline textarea in Question |
49
- | `csat` | — | `CsatScale` + min/max labels in Question |
50
- | `matrix` | `CSAT_MATRIX` \| `RATING_MATRIX` | `CsatMatrixScale` |
51
- | `matrix` | `CFM_MATRIX` | `LikertMatrixScale` |
52
- | `slider_matrix` | — | `SliderMatrixScale` |
53
- | `file_upload` | — | `FileUploadScale` |
54
- | `text_and_media` | `mediaUrl` present | Inline media in Question |
55
- | `slider` | — | ❌ Not routed — see `06-slider.md` |
56
- | `rating_scale` | — | ❌ Not routed — see `05-rating-scale.md` |
57
-
58
- ## Label Positioning (rating)
59
-
60
- Rendered in Question before `RatingScale`:
61
-
62
- ```tsx
63
- // midLabel CSS left:
64
- left: `${(midLabelIndex / (options.length - 1)) * 100}%`
65
- // with -translate-x-1/2 for centering
66
- ```
67
-
68
- minLabel: `absolute left-0`, maxLabel: `absolute right-0`, container `relative h-6 text-xs font-semibold text-gray-500`.
69
-
70
- ## Label Positioning (csat)
71
-
72
- minLabel / maxLabel: `flex justify-between text-xs font-semibold text-gray-500` above `CsatScale`.
73
-
74
- ## Validation Error Banner
75
-
76
- ```tsx
77
- className="mt-4 rounded-[4px] border border-[#333333] bg-[#d9d9d9] py-3.5 px-4"
78
- // text: text-[13px] font-bold text-gray-900
79
- ```
80
-
81
- ## Props Contract
82
-
83
- ```typescript
84
- type QuestionProps = {
85
- question: SurveyQuestion;
86
- selectedValue?: AnswerValue;
87
- validationError?: string;
88
- onSelect: (value: AnswerValue) => void;
89
- };
90
- ```
91
-
92
- Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId, value } })`.
93
-
94
- ## Agent Checklist
95
-
96
- - [ ] `id={question.id}` on outermost `<section>`
97
- - [ ] HTML rendering for text and description
98
- - [ ] Copied from `templates/Question.tsx` — no stub/default fallback branches
99
- - [ ] Matrix routes by `subType`, not type alone
100
- - [ ] `slider_matrix` → `SliderMatrixScale`; `file_upload` → `FileUploadScale`
101
- - [ ] Validation error banner below scale
102
- - [ ] Rating/csat endpoint labels positioned per formulas above
21
+ | `id` | ✅ | `section id={question.id}` — scroll-to-error |
22
+ | `questionText` | ✅ | Heading via `dangerouslySetInnerHTML`; when the survey defines `displayVariants`, the SDK resolves the active variant text on `state.currentQuestions` before render |
23
+ | `questionDescription` | ✅ | Optional HTML below title |
24
+ | `questionHint` | | Optional hint below description |
25
+ | `required` | ✅ | Red `*` after title |
26
+ | `requiredErrorMessage` | | Validation error banner |
27
+ | `questionNumber` | | Prefix before title (e.g. `3.`); SDK resolves active-path ordinals on `state.currentQuestions` (see [`question-numbering.md`](../../00-integration/question-numbering.md)) |
28
+ | `containerMedia` | ❌ | Side media — `containerMedia?.url` etc.; not in reference client |
29
+
30
+ ## SDK hook
31
+
32
+ - Answers: `state.answers` (not `state.values`)
33
+ - Change action: `{ type: 'CHANGE', payload: { questionId, answerValue } }`
@@ -1,80 +1,46 @@
1
- # UI Spec: NPS Rating (`type: 'rating'`)
1
+ # UI Spec: NPS Rating (`type: 'NPS_SCALE'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/RatingScale.tsx` + label row in `Question.tsx`
4
- > **SDK type:** `RatingQuestion` — `fetchSurvey/types/rating.ts`
3
+ > **SDK type:** `NpsQuestion` `fetchSurvey/types/nps.ts`
5
4
  > **Component blueprint:** `01-components/03-rating-scale.md`
5
+ > **Value derivation:** `02-reference/value-derivation.md`
6
6
 
7
7
  ## Config Inventory
8
8
 
9
9
  | Field | Implemented | UI impact |
10
10
  |-------|-------------|-----------|
11
11
  | `options[]` | ✅ | Badge row + radio row (dual layer) |
12
- | `options[].color` | ✅ | Badge background; fallback `#e20074` |
13
- | `options[].label` | ✅ | Displayed in badge |
14
- | `options[].value` | ✅ | Selection value |
12
+ | `options[].optionLabel` | ✅ | Displayed in badge |
13
+ | `options[].id` | ✅ | Stable option id (display only) |
14
+ | Submit value | ✅ derived | Parse `optionLabel` as number or use index — see value-derivation |
15
+ | Badge color | ✅ derived | Traffic-light table from numeric value |
15
16
  | `minLabel` | ✅ | Rendered in Question.tsx above scale |
16
17
  | `midLabel` | ✅ | Absolute positioned in Question.tsx |
17
18
  | `maxLabel` | ✅ | Absolute positioned in Question.tsx |
18
19
  | `midLabelIndex` | ✅ | Mid label `left` percentage |
19
- | `reverseScaleOrder` | | Document for agents when SDK sends true |
20
- | `buttonVariant` | ❌ | Client always uses dual-layer radio layout |
20
+ | `reverseScaleOrder` | | Reverse option index before selection/color lookup |
21
+ | `buttonVariant` | ❌ | Client uses dual-layer radio layout |
21
22
 
22
- ## Layout Anatomy
23
+ ## Answer logic on scale points
23
24
 
24
- ```
25
- [minLabel] [midLabel @ %] [maxLabel]
26
- ┌──┬──┬──┬── ... ──┬──┐ ← Row 1: colored badges (NOT clickable)
27
- │0 │1 │2 │3 │ ... │10│ grid repeat(options.length), h-5 w-5 sm:h-6 w-6
28
- └──┴──┴──┴── ... ──┴──┘ rounded-[3px], white bold text, option.color bg
29
- ┌──────────────────────────────────────────────┐
30
- │ ○ ○ ● ○ ... ○ │ ← Row 2: gray track bg-[#f2f2f2]
31
- └──────────────────────────────────────────────┘ h-11 sm:h-12, rounded-md
32
- ↑ selected cell: inner bg-[#fbe8f3]
33
- radio: border-[4px] border-[#e20074] bg-white
34
- ```
35
-
36
- ## Config Decision Tree
37
-
38
- ```
39
- rating
40
- └── RatingScale (always dual-layer in reference client)
41
- ├── Row 1: display-only colored badges
42
- └── Row 2: sr-only radio + custom circle per cell
43
- ```
25
+ When scale points have answer logic, filter with `getVisibleScaleColumnsForAnswers(question, answers, allQuestions, customFieldValues?)` before render.
44
26
 
45
- If `buttonVariant === 'numbered'` (SDK gap): badges become clickable buttons instead not in reference client.
27
+ ## Implementation RecipereverseScaleOrder
46
28
 
47
- If `reverseScaleOrder`: reverse `options` array before mapping both rows.
48
-
49
- ## Interactive States
50
-
51
- | State | Badge row | Track cell | Radio circle |
52
- |-------|-----------|------------|--------------|
53
- | Default | Full color | Transparent inner | `border-2 border-gray-300` |
54
- | Hover | — | `hover:bg-gray-200/40` | — |
55
- | Selected | Unchanged | `bg-[#fbe8f3]` | `border-[4px] border-[#e20074]` |
56
-
57
- ## Tooltips
58
-
59
- Recommended: `title={option.label}` on each track `<label>`. Not in reference client yet.
29
+ When `reverseScaleOrder === true`, map visual index `i` to stored index `options.length - 1 - i` before deriving submit value and badge color.
60
30
 
61
31
  ## Answer Mutation
62
32
 
63
33
  ```typescript
64
- onSelect(option.value) // number
65
- // → onAction CHANGE { questionId, value: number }
34
+ const value = npsValueFromOption(option, index);
35
+ onSelect(value) // number
36
+ // → onAction CHANGE { questionId, answerValue: number }
66
37
  ```
67
38
 
68
- ## Accessibility
69
-
70
- - `role="radiogroup"` on wrapper
71
- - Native `<input type="radio" className="sr-only">` per cell
72
- - `name={questionId}` groups radios
39
+ See [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
73
40
 
74
41
  ## Agent Checklist
75
42
 
43
+ - [ ] Derive submit value and badge color in UI — types omit `optionValue` / `accentColor`
76
44
  - [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
77
- - [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
78
45
  - [ ] Only track row is clickable
79
46
  - [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
80
- - [ ] Selected state: pink inner track + thick magenta radio ring
@@ -1,57 +1,100 @@
1
- # UI Spec: Radio MCQ (`type: 'radio'`)
1
+ # UI Spec: MCQ (`type: 'MCQ'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
- > **SDK type:** `RadioQuestion` — `fetchSurvey/types/radio.ts`
3
+ > **SDK type:** `McqQuestion` — `fetchSurvey/types/mcq.ts`
5
4
  > **Component blueprint:** `01-components/02-question.md`
5
+ > **Dispatcher:** inline in `Question.tsx` (see [`templates/Question.tsx`](../templates/Question.tsx))
6
6
 
7
7
  ## Config Inventory
8
8
 
9
- | Field | Implemented | UI impact |
10
- |-------|-------------|-----------|
11
- | `options[]` | ✅ | One card per option |
12
- | `options[].label` | ✅ | HTML via `dangerouslySetInnerHTML` |
13
- | `options[].value` | | Selection value; `null` → empty string on input |
14
- | `isMultiSelect` | | SDK supports; client is single-select only |
9
+ | Field | UI impact |
10
+ |-------|-----------|
11
+ | `options[]` | One card per visible option |
12
+ | `options[].optionLabel` | HTML via `dangerouslySetInnerHTML` |
13
+ | `options[].id` | **Submit value** answer is `option.id` |
14
+ | `selectionMode` | `'single'` radio cards; `'multiple'` checkbox cards + `string[]` answer |
15
+ | `defaultOptionIds?` | Pre-selected on load (SDK seeds `state.answers`) |
16
+ | `minSelections?` / `maxSelections?` | Answer-condition count (SDK validates on NEXT) |
17
+ | `answerConditionErrorMessage?` | Custom validation banner text |
18
+ | `requiredErrorMessage?` | Required-field error text |
15
19
 
16
- ## Layout Anatomy
20
+ ## Selection mode (not `isMultiSelect`)
17
21
 
18
- ```
19
- ┌─ label (full-width card) ─────────────────────────────────────┐
20
- │ (●) Option label HTML │ selected
21
- └───────────────────────────────────────────────────────────────┘
22
- ┌─ label ───────────────────────────────────────────────────────┐
23
- │ (○) Option label HTML │ unselected
24
- └───────────────────────────────────────────────────────────────┘
22
+ Use `question.selectionMode` from the SDK — **never** `isMultiSelect`.
23
+
24
+ | `selectionMode` | UI | Answer shape |
25
+ |-----------------|-----|--------------|
26
+ | `'single'` (default) | Radio cards; one selected | `string` (option `id`) |
27
+ | `'multiple'` | Checkbox cards; toggle on click | `string[]` of option ids |
28
+
29
+ Multi-select toggle pattern:
30
+
31
+ ```typescript
32
+ const handleToggle = (optionId: string) => {
33
+ const next = selectedIds.includes(optionId)
34
+ ? selectedIds.filter(id => id !== optionId)
35
+ : question.maxSelections && selectedIds.length >= question.maxSelections
36
+ ? selectedIds // block add at max
37
+ : [...selectedIds, optionId];
38
+ onSelect(next);
39
+ };
25
40
  ```
26
41
 
27
- ## Interactive States
42
+ Filter options before render: `getVisibleMcqOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues?)`.
28
43
 
29
- | State | Border | Background | Radio |
30
- |-------|--------|------------|-------|
31
- | Unselected | `#e5e5e5` | `white` | `border-gray-400`, empty |
32
- | Unselected hover | `#e5e5e5` | `hover:bg-gray-50/50` | — |
33
- | Selected | `#e20074` | `#fdf2f8` | `border-2 border-[#e20074]` + inner dot `h-2.5 w-2.5 bg-[#e20074]` |
44
+ ---
34
45
 
35
- ## Structure
46
+ ## Implementation Recipe — Default answers
36
47
 
37
- ```tsx
38
- <label className="flex items-center gap-4 rounded-lg border py-4 px-5 cursor-pointer">
39
- <input type="radio" name={question.id} className="sr-only" />
40
- <div className="h-5 w-5 rounded-full border ..."> {/* custom radio */} </div>
41
- <span dangerouslySetInnerHTML={{ __html: option.label }} />
42
- </label>
43
- ```
48
+ | Concern | SDK behavior | Client UI contract |
49
+ |---------|--------------|-------------------|
50
+ | **Config** | API `DEFAULT_ANSWER` → `question.defaultOptionIds?: string[]` | Inventory in implementation plan §4b |
51
+ | **Initial load** | `buildInitialAnswers` / `applyMcqDefaults` writes into `state.answers` | Bind checked state to `selectedValue` (`state.answers[question.id]`) |
52
+ | **Single-select** | Default = `defaultOptionIds[0]` | Pre-selected radio when present in `state.answers` |
53
+ | **Multi-select** | Default = full `defaultOptionIds` array | Pre-checked boxes for each id |
54
+ | **User override** | `applyMcqDefaults` never overwrites user selection | Toggle via `onAction(CHANGE)` |
55
+ | **Validation / submit** | SDK re-applies defaults in `validateCurrentPage` and submit if empty | Client does **not** re-apply defaults at NEXT/SUBMIT |
56
+ | **Answer logic** | Hidden defaults may exist in `state.answers` until SDK sanitizes | Show checked state only for **visible** options |
57
+ | **Template fallback** | `Question.tsx` uses `selectedValue ?? defaultOptionIds` defensively | Prefer `state.answers`; fallback is display-only |
58
+
59
+ ---
60
+
61
+ ## Implementation Recipe — Answer conditions
62
+
63
+ When API `ANSWER_CONDITION` is enabled, SDK maps:
64
+
65
+ - `minSelections` — minimum selected count (multi-select)
66
+ - `maxSelections` — maximum selected count (multi-select)
67
+ - `answerConditionErrorMessage` — shown in `state.validationErrors[questionId]`
68
+
69
+ **Client must:**
70
+
71
+ 1. Render `validationError` banner when SDK sets an error (do not duplicate count validation).
72
+ 2. Block adding options when `selectedIds.length >= maxSelections` in multi-select UI.
73
+ 3. Pass through `onAction(CHANGE)` with updated array — SDK validates on NEXT.
74
+
75
+ Skip logic may use `NUM_OF_SELECTED_OPTIONS` — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
76
+
77
+ ---
44
78
 
45
79
  ## Answer Mutation
46
80
 
47
81
  ```typescript
48
- onSelect(option.value) // string | number
82
+ // Single-select
83
+ onSelect(option.id)
84
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: string } })
85
+
86
+ // Multi-select
87
+ onSelect(selectedIds) // string[]
88
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: string[] } })
49
89
  ```
50
90
 
51
91
  ## Agent Checklist
52
92
 
53
- - [ ] Full-width card options with gap-3 vertical stack
54
- - [ ] Custom circular radio indicator (not browser default)
55
- - [ ] Selected: magenta border + pink background
56
- - [ ] HTML labels
57
- - [ ] If `isMultiSelect`: implement checkbox cards (SDK gap in reference)
93
+ - [ ] Use `selectionMode`, not `isMultiSelect`
94
+ - [ ] Full-width card options; custom circular radio / checkbox indicators
95
+ - [ ] Selected: magenta border `#e20074` + pink bg `#fdf2f8`
96
+ - [ ] Multi-select: `string[]` answer; block at `maxSelections`
97
+ - [ ] Defaults: bind to `state.answers`; pre-selected visual on load when `defaultOptionIds` set
98
+ - [ ] `getVisibleMcqOptionsForAnswers` with optional `customFieldValues` 4th arg
99
+ - [ ] Store `option.id` — never label text
100
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#mcq`](../00-integration/question-type-sdk-matrix.md#mcq)
@@ -1,7 +1,7 @@
1
- # UI Spec: Text Field (`type: 'text'`)
1
+ # UI Spec: Text Field (`type: 'TEXTFIELD'`)
2
2
 
3
3
  > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
- > **SDK type:** `TextQuestion` — `fetchSurvey/types/text.ts`
4
+ > **SDK type:** `TextInputQuestion` — `fetchSurvey/types/text.ts`
5
5
 
6
6
  ## Config Inventory
7
7
 
@@ -1,57 +1,92 @@
1
- # UI Spec: CSAT (`type: 'csat'`)
1
+ # UI Spec: CSAT Matrix Grid (`type: 'CSAT_MATRIX'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/CsatScale.tsx`
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CsatMatrixScale.tsx`
4
4
  > **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
5
- > **Component blueprint:** `01-components/04-csat-scale.md`
5
+ > **Component blueprint:** `01-components/05-csat-matrix-scale.md`
6
6
 
7
7
  ## Config Inventory
8
8
 
9
9
  | Field | Implemented | UI impact |
10
10
  |-------|-------------|-----------|
11
- | `options[]` | ✅ | Scale buttons or dropdown options |
12
- | `buttonType` | ✅ partial | Branches below |
13
- | `labelPosition` | ✅ | `'top'` flex-col-reverse; `'hidden'` no label |
14
- | `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
15
- | `hasNotApplicable` | | N/A option in options array still renders |
16
- | `reverseScaleOrder` | | Document: reverse options before map |
11
+ | `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
12
+ | `displayStyle` | ✅ | Grid vs vertical list routing |
13
+ | `gridLayout` | ✅ | carousel vs standard vs dropdown |
14
+ | `hasNotApplicableOption` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
15
+ | `reverseScaleOrder` | | Flip emoji/star/number index |
16
+ | `scaleAnchorLabels[]` | | Anchor row above columns |
17
17
 
18
18
  ## Config Decision Tree
19
19
 
20
20
  ```
21
- csat
22
- ├── buttonType === 'dropdown'
23
- │ └── Native <select>, placeholder "Select an option"
24
- └── Parse value 'null' null
25
- └── else (emoji | star | numbered | number | default)
26
- └── flex flex-wrap gap-3 buttons
27
- ├── emojigetEmojiForIndex(index, scaleLength)
28
- ├── starCsatStarIcons filled/empty; labels hidden
29
- ├── numbered/numbermono font value
30
- └── defaultbold value or 'N/A' if null
21
+ CSAT_MATRIX
22
+ ├── gridLayout === 'carousel' → CsatMatrixCarousel
23
+ ├── displayStyle in (dropdown, selectbox) CsatMatrixVerticalList
24
+ ├── dropdown MatrixDropdown placeholder "Select..."
25
+ │ ├── selectbox full-width cards #fdf2f8 selected
26
+ └── else card radio list
27
+ └── elseCsatMatrixGrid
28
+ ├── displayStyle === 'emoji' client-side emoji icons (react-icons) + hover scale + ring when selected
29
+ ├── displayStyle === 'star' opacity 0.5 unselected; filled/empty icons
30
+ ├── displayStyle === 'numbered' 40×40 badge; selected magenta fill
31
+ └── displayStyle === 'graphics' → CustomSliderTrack per row (carousel)
31
32
  ```
32
33
 
33
- ## Interactive States
34
+ ## Grid Structure
34
35
 
35
- | State | Border | Background | Transform |
36
- |-------|--------|------------|-----------|
37
- | Default | `border-gray-200` | `white` | scale-100 |
38
- | Hover | `border-gray-300` | `hover:bg-gray-50` | |
39
- | Selected | `border-[#e20074]` | `#fdf2f8` | `scale-110` |
40
- | Star mode | transparent | transparent | no border/bg on button |
36
+ - Row label: `180px` fixed column
37
+ - N/A column: `56px` when `hasNotApplicableOption`
38
+ - Alternating row backgrounds
39
+ - Column headers OR `scaleAnchorLabels[]` anchor row with percentage positioning
41
40
 
42
- ## Tooltips
41
+ ## Carousel — Implementation Recipe
43
42
 
44
- Recommended: `title={option.label}` on each button. Not in reference client.
43
+ Same rules as CFM carousel (prev/next disabled at bounds, dot click jumps row, no auto-advance). CSAT uses **pink** active dot `#e20074` (not Likert blue).
44
+
45
+ ## N/A mutex
46
+
47
+ | Layout | Behavior |
48
+ |--------|----------|
49
+ | Grid N/A column | Selecting N/A clears row; selecting column clears N/A |
50
+ | Vertical checkbox | `hasNotApplicableOption` checkbox below row sets `null` |
51
+
52
+ ## Emoji / star / numbered — react-icons mapping
53
+
54
+ Copy [`templates/surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx) or implement `getEmojiForIndex(index, totalColumns)`.
55
+
56
+ **5-point CSAT emoji index → icon key:**
57
+
58
+ | Column index (0-based) | Sentiment | Maps to 11-point scale key |
59
+ |--------------------------|-----------|----------------------------|
60
+ | 0 | Very negative | 3 |
61
+ | 1 | Negative | 4 |
62
+ | 2 | Neutral | 6 |
63
+ | 3 | Positive | 7 |
64
+ | 4 | Very positive | 9 |
65
+
66
+ **Star mode:** filled `FaStar` `#e20074` when selected; `FaRegStar` `#d1d5db` opacity 0.5 when unselected; hover ring `ring-2 ring-[#e20074] ring-offset-2`.
67
+
68
+ **Numbered mode:** 40×40 badge; selected = magenta fill `#e20074` white text; unselected = gray border.
69
+
70
+ **Graphics mode:** `CustomSliderTrack` per row with `ticks={scaleColumns.length}`, `tickLabels={scaleColumns.map(c => c.label)}`, `sliderType="graphics"`.
71
+
72
+ Apply `reverseScaleOrder` by flipping column index before icon lookup.
73
+
74
+ ## Scale column labels on slider (Day 19)
75
+
76
+ Pass `tickLabels={visibleScaleColumns.map(col => col.label)}` to `CustomSliderTrack` so tooltips and graphics thumb show mapped labels, not raw numeric indices.
45
77
 
46
78
  ## Answer Mutation
47
79
 
80
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
81
+
48
82
  ```typescript
49
- onSelect(string | number | null)
83
+ onSelect(MatrixRowAnswers)
84
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
50
85
  ```
51
86
 
52
87
  ## Agent Checklist
53
88
 
54
- - [ ] Dropdown branch with magenta focus ring
55
- - [ ] Emoji/star/numbered branches per decision tree
56
- - [ ] `labelPosition` controls label above/below/hidden
57
- - [ ] Implement `reverseScaleOrder` and standalone `hasNotApplicable` (SDK gaps)
89
+ - [ ] All displayStyle × gridLayout branches
90
+ - [ ] reverseScaleOrder on emoji/star/numbered
91
+ - [ ] Carousel pink dot indicators
92
+ - [ ] Vertical selectbox cards match MCQ card styling
@@ -1,49 +1,52 @@
1
- # UI Spec: Rating Scale (`type: 'rating_scale'`)
1
+ # UI Spec: Star Rating (`type: 'star_rating'`)
2
2
 
3
- > **Reference:** Not routed in `Question.tsx` reuse `CsatScale.tsx` patterns
4
- > **SDK type:** `RatingScaleQuestion` — `fetchSurvey/types/ratingScale.ts`
5
- > **Component blueprint:** `01-components/03-rating-scale.md`
3
+ > **DEPRECATED:** Not in the current `SurveyQuestion` union. API RATING pages map to `NPS_SCALE` or CSAT/RATING matrix display styles. See `01-rating.md` and matrix specs instead.
4
+ >
5
+ > **SDK type:** `StarRatingQuestion` — `fetchSurvey/types/starRating.ts`
6
+ > **Component blueprint:** `01-components/04-star-rating-scale.md`
7
+ > **Routed in template:** `Question.tsx` → `RatingScale` for `star_rating`
6
8
 
7
9
  ## Config Inventory
8
10
 
9
11
  | Field | Implemented | UI impact |
10
12
  |-------|-------------|-----------|
11
- | `options[]` | gap | Star/emoji buttons (no NPS colors) |
12
- | `scaleStyle` | gap | `'star'` or `'emoji'` |
13
- | `minLabel` / `midLabel` / `maxLabel` | gap | Same formulas as NPS in Question shell |
14
- | `midLabelIndex` | gap | `(midLabelIndex / (options.length - 1)) * 100`% |
15
- | `reverseScaleOrder` | gap | Reverse options before render |
13
+ | `options[]` | | Star/emoji buttons (no NPS traffic-light colors) |
14
+ | `scaleStyle` | | `'star'` or `'emoji'` |
15
+ | `minLabel` / `midLabel` / `maxLabel` | | Same formulas as NPS in Question shell |
16
+ | `midLabelIndex` | | `(midLabelIndex / (options.length - 1)) * 100`% |
17
+ | `reverseScaleOrder` | partial | Reverse options before render |
16
18
 
17
- ## Expected UI (from CsatScale + rating labels)
19
+ ## Expected UI
18
20
 
19
21
  ```
20
22
  [minLabel] [midLabel @ %] [maxLabel]
21
23
  ┌────┬────┬────┬────┬────┐
22
24
  │ ★ │ ★ │ ★ │ ★ │ ★ │ scaleStyle: 'star'
23
25
  └────┴────┴────┴────┴────┘
24
- OR emoji row from getEmojiForIndex when scaleStyle: 'emoji'
26
+ OR emoji row from client-side react-icons when scaleStyle: 'emoji'
25
27
  ```
26
28
 
27
- Reuse CsatScale selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
29
+ Selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
28
30
 
29
31
  ## Config Decision Tree
30
32
 
31
33
  ```
32
- rating_scale
33
- ├── scaleStyle === 'star' → CsatStarIcons per option
34
- ├── scaleStyle === 'emoji' → getEmojiForIndex
35
- └── else → numbered badges (no option.color)
34
+ star_rating
35
+ ├── scaleStyle === 'star' → client-side star icons (react-icons) per option
36
+ ├── scaleStyle === 'emoji' → client-side emoji icons (react-icons)
37
+ └── else → numbered badges (no accentColor)
36
38
  ```
37
39
 
38
40
  ## Answer Mutation
39
41
 
40
42
  ```typescript
41
43
  onSelect(number)
44
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: number } })
42
45
  ```
43
46
 
44
47
  ## Agent Checklist
45
48
 
46
- - [ ] Add dispatcher branch in Question for `rating_scale`
49
+ - [ ] Dispatcher branch for `star_rating` in Question.tsx
47
50
  - [ ] No traffic-light NPS colors on options
48
51
  - [ ] min/mid/max labels with midLabelIndex formula
49
- - [ ] Match CsatScale interactive states
52
+ - [ ] Match star/emoji bounding box rules from `04-star-rating-scale.md`
@@ -1,6 +1,8 @@
1
1
  # UI Spec: Standalone Slider (`type: 'slider'`)
2
2
 
3
- > **Reference:** Not routed in `Question.tsx` reuse `CustomSliderTrack.tsx`
3
+ > **DEPRECATED:** Not in the current `SurveyQuestion` union. API SLIDER pages map to `SLIDER_MATRIX` or matrix `displayStyle: 'graphics'`. See `10-slider-matrix.md` and `CustomSliderTrack` specs.
4
+ >
5
+ > **Reference:** Standalone slider is not routed in current `Question.tsx`
4
6
  > **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
5
7
  > **Component blueprint:** `01-components/09-custom-slider-track.md`
6
8