@explorer02/cfm-survey-sdk 0.2.1 → 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 -69
  9. package/templates/AGENT.md +14 -8
  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 +45 -22
  14. package/templates/docs/00-integration/constraints.md +49 -4
  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 +61 -58
  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 +42 -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 +19 -88
  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 +24 -21
  69. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +17 -45
  70. package/templates/docs/03-ui-specs/09-slider-matrix.md +1 -1
  71. package/templates/docs/03-ui-specs/10-file-upload.md +1 -1
  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 +13 -13
  77. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +2 -2
  78. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  79. package/templates/docs/MANIFEST.json +451 -69
  80. package/templates/docs/index.md +80 -29
  81. package/templates/docs/templates/Question.tsx +221 -154
  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 +108 -69
  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
@@ -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).
@@ -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,17 +1,18 @@
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 |
@@ -19,62 +20,19 @@
19
20
  | `reverseScaleOrder` | ❌ | Document for agents when SDK sends true |
20
21
  | `buttonVariant` | ❌ | Client always uses dual-layer radio layout |
21
22
 
22
- ## Layout Anatomy
23
-
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
- ```
44
-
45
- If `buttonVariant === 'numbered'` (SDK gap): badges become clickable buttons instead — not in reference client.
46
-
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.
60
-
61
23
  ## Answer Mutation
62
24
 
63
25
  ```typescript
64
- onSelect(option.value) // number
65
- // → onAction CHANGE { questionId, value: number }
26
+ const value = npsValueFromOption(option, index);
27
+ onSelect(value) // number
28
+ // → onAction CHANGE { questionId, answerValue: number }
66
29
  ```
67
30
 
68
- ## Accessibility
69
-
70
- - `role="radiogroup"` on wrapper
71
- - Native `<input type="radio" className="sr-only">` per cell
72
- - `name={questionId}` groups radios
31
+ See [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
73
32
 
74
33
  ## Agent Checklist
75
34
 
35
+ - [ ] Derive submit value and badge color in UI — types omit `optionValue` / `accentColor`
76
36
  - [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
77
- - [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
78
37
  - [ ] Only track row is clickable
79
38
  - [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
80
- - [ ] Selected state: pink inner track + thick magenta radio ring
@@ -1,7 +1,6 @@
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`
6
5
 
7
6
  ## Config Inventory
@@ -9,43 +8,26 @@
9
8
  | Field | Implemented | UI impact |
10
9
  |-------|-------------|-----------|
11
10
  | `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 |
11
+ | `options[].optionLabel` | ✅ | HTML via `dangerouslySetInnerHTML` |
12
+ | `options[].id` | ✅ | **Submit value** selected answer is `option.id` |
13
+ | `isMultiSelect` | ⚠️ | SDK supports multi-select; implement checkbox cards when `true` |
15
14
 
16
- ## Layout Anatomy
15
+ ## Multi-select note
17
16
 
18
- ```
19
- ┌─ label (full-width card) ─────────────────────────────────────┐
20
- │ (●) Option label HTML │ selected
21
- └───────────────────────────────────────────────────────────────┘
22
- ┌─ label ───────────────────────────────────────────────────────┐
23
- │ (○) Option label HTML │ unselected
24
- └───────────────────────────────────────────────────────────────┘
25
- ```
17
+ When `question.isMultiSelect === true`:
26
18
 
27
- ## Interactive States
19
+ - Answer shape is `string[]` (or `number[]`) of selected `option.id` values
20
+ - Toggle options on click; do not replace the entire array with a single id
21
+ - onAction CHANGE: `{ questionId, answerValue: selectedIds[] }`
22
+ - Still filter via `getVisibleMcqOptionsForAnswers` before render
28
23
 
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]` |
34
-
35
- ## Structure
36
-
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
- ```
24
+ When `false` (default), single-select radio behavior applies.
44
25
 
45
26
  ## Answer Mutation
46
27
 
47
28
  ```typescript
48
- onSelect(option.value) // string | number
29
+ onSelect(option.id) // string
30
+ // → onAction CHANGE { questionId, answerValue: string }
49
31
  ```
50
32
 
51
33
  ## Agent Checklist
@@ -54,4 +36,10 @@ onSelect(option.value) // string | number
54
36
  - [ ] Custom circular radio indicator (not browser default)
55
37
  - [ ] Selected: magenta border + pink background
56
38
  - [ ] HTML labels
57
- - [ ] If `isMultiSelect`: implement checkbox cards (SDK gap in reference)
39
+ - [ ] If `isMultiSelect`: implement checkbox cards with array answer shape
40
+
41
+ ## SDK Integration Checklist (agent)
42
+
43
+ - [ ] Visibility helper: `getVisibleMcqOptionsForAnswers`
44
+ - [ ] Store `option.id` — never option label text
45
+ - [ ] 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,62 @@
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
43
42
 
44
- Recommended: `title={option.label}` on each button. Not in reference client.
43
+ - Pink active dot expands: `20px × 8px` vs `8px` inactive
44
+ - Column labels below cells
45
+ - Emoji tooltip via hover state
46
+ - Graphics mode: slider maps column index
45
47
 
46
48
  ## Answer Mutation
47
49
 
50
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
51
+
48
52
  ```typescript
49
- onSelect(string | number | null)
53
+ onSelect(MatrixRowAnswers)
54
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
50
55
  ```
51
56
 
52
57
  ## Agent Checklist
53
58
 
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)
59
+ - [ ] All displayStyle × gridLayout branches
60
+ - [ ] reverseScaleOrder on emoji/star/numbered
61
+ - [ ] Carousel pink dot indicators
62
+ - [ ] 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