@explorer02/cfm-survey-sdk 0.1.9 → 0.2.0

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 (36) hide show
  1. package/package.json +1 -1
  2. package/templates/docs/01-components/01-survey-page.md +2 -0
  3. package/templates/docs/01-components/02-question.md +2 -0
  4. package/templates/docs/01-components/03-rating-scale.md +2 -0
  5. package/templates/docs/01-components/04-csat-scale.md +3 -1
  6. package/templates/docs/01-components/05-csat-matrix-scale.md +2 -0
  7. package/templates/docs/01-components/06-likert-matrix-scale.md +2 -0
  8. package/templates/docs/01-components/07-slider-matrix-scale.md +2 -0
  9. package/templates/docs/01-components/08-file-upload-scale.md +2 -0
  10. package/templates/docs/01-components/09-custom-slider-track.md +2 -0
  11. package/templates/docs/01-components/10-header-footer.md +3 -1
  12. package/templates/docs/01-components/11-progress-bar.md +2 -0
  13. package/templates/docs/01-components/12-language-selector.md +2 -0
  14. package/templates/docs/01-components/13-matrix-dropdown.md +2 -0
  15. package/templates/docs/01-components/README.md +2 -2
  16. package/templates/docs/02-reference/config-field-index.md +158 -0
  17. package/templates/docs/02-reference/routing-table.md +16 -16
  18. package/templates/docs/03-ui-specs/00-question-shell.md +89 -0
  19. package/templates/docs/03-ui-specs/01-rating.md +80 -0
  20. package/templates/docs/03-ui-specs/02-radio.md +57 -0
  21. package/templates/docs/03-ui-specs/03-text.md +51 -0
  22. package/templates/docs/03-ui-specs/04-csat.md +57 -0
  23. package/templates/docs/03-ui-specs/05-rating-scale.md +49 -0
  24. package/templates/docs/03-ui-specs/06-slider.md +42 -0
  25. package/templates/docs/03-ui-specs/07-matrix-cfm.md +66 -0
  26. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +65 -0
  27. package/templates/docs/03-ui-specs/09-slider-matrix.md +61 -0
  28. package/templates/docs/03-ui-specs/10-file-upload.md +54 -0
  29. package/templates/docs/03-ui-specs/11-text-and-media.md +49 -0
  30. package/templates/docs/03-ui-specs/12-survey-chrome.md +60 -0
  31. package/templates/docs/03-ui-specs/README.md +79 -0
  32. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +52 -0
  33. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +38 -0
  34. package/templates/docs/MANIFEST.json +22 -1
  35. package/templates/docs/index.md +14 -3
  36. package/templates/docs/templates/implementation_plan.md +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorer02/cfm-survey-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -1,5 +1,7 @@
1
1
  # Survey Page Orchestrator Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
+ > **Reference implementation:** `apps/client/src/components/SurveyPage.tsx`
3
5
  > **Target Component**: `SurveyPage`
4
6
  > **Role**: The main entry point that wires the UI to the `useSurveySDK` hook, manages loading/error states, orchestrates pagination, and controls submission.
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Question Dispatcher Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/00-question-shell.md`
4
+ > **Reference implementation:** `apps/client/src/components/Question.tsx`
3
5
  > **Target Component**: `Question`
4
6
  > **Role**: Wrapper card component that routes to the correct scale component based on `question.type`.
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Rating Scale Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/01-rating.md` (NPS) · `03-ui-specs/05-rating-scale.md` (star/emoji gap)
4
+ > **Reference implementation:** `apps/client/src/components/RatingScale.tsx`
3
5
  > **Target Component**: `RatingScale`
4
6
  > **Handles**: `type: 'rating'`, `type: 'rating_scale'`
5
7
 
@@ -1,6 +1,8 @@
1
1
  # CSAT & Rating Architectural Blueprints
2
2
 
3
- > **Target Components**: `CsatScale`, `RatingScale`, `CsatMatrixScale`
3
+ > **UI spec (authoritative):** `03-ui-specs/04-csat.md`
4
+ > **Reference implementation:** `apps/client/src/components/CsatScale.tsx`
5
+ > **Target Components**: `CsatScale`
4
6
  > **Handles**: `type: 'csat'`, `'rating'`, `'rating_scale'`, CSAT matrices.
5
7
 
6
8
  ## Core Responsibility
@@ -1,5 +1,7 @@
1
1
  # CSAT Matrix Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/08-matrix-csat-rating.md`
4
+ > **Reference implementation:** `apps/client/src/components/CsatMatrixScale.tsx`
3
5
  > **Target Component**: `CsatMatrixScale`
4
6
  > **Handles**: `type: 'matrix'` (CSAT_MATRIX and RATING_MATRIX subTypes)
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Likert Matrix Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/07-matrix-cfm.md`
4
+ > **Reference implementation:** `apps/client/src/components/LikertMatrixScale.tsx`
3
5
  > **Target Component**: `LikertMatrixScale`
4
6
  > **Handles**: `type: 'matrix'` (CFM_MATRIX subType)
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Slider Matrix Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/09-slider-matrix.md`
4
+ > **Reference implementation:** `apps/client/src/components/SliderMatrixScale.tsx`
3
5
  > **Target Component**: `SliderMatrixScale`
4
6
  > **Handles**: `type: 'slider_matrix'`
5
7
 
@@ -1,5 +1,7 @@
1
1
  # File Upload Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
4
+ > **Reference implementation:** `apps/client/src/components/FileUploadScale.tsx`
3
5
  > **Target Component**: `FileUploadScale`
4
6
  > **Handles**: `type: 'file_upload'`
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Custom Slider Track Architectural Blueprint
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/shared/custom-slider-track.md`
4
+ > **Reference implementation:** `apps/client/src/components/CustomSliderTrack.tsx`
3
5
  > **Target Component**: `CustomSliderTrack`
4
6
  > **Used By**: `SliderMatrixScale`, `CsatMatrixScale`, `CsatScale`
5
7
 
@@ -1,6 +1,8 @@
1
1
  # Theme & Logo Engineering Blueprint
2
2
 
3
- > **Target Components**: `Header`, Global Styles
3
+ > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
+ > **Reference implementation:** `apps/client/src/components/Header.tsx`, `Footer.tsx`
5
+ > **Target Components**: `Header`, `Footer`
4
6
  > **Handles**: Branding, Mockup Replication
5
7
 
6
8
  ## Core Responsibility
@@ -1,5 +1,7 @@
1
1
  # Progress Bar
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
+ > **Reference implementation:** `apps/client/src/components/ProgressBar.tsx`
3
5
  > **Component**: `ProgressBar`
4
6
  > **Role**: Displays completion status at the top of the survey.
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Language Selector
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
+ > **Reference implementation:** `apps/client/src/components/LanguageSelector.tsx`
3
5
  > **Component**: `LanguageSelector`
4
6
  > **Role**: Allows the user to switch the survey language.
5
7
 
@@ -1,5 +1,7 @@
1
1
  # Matrix Dropdown
2
2
 
3
+ > **UI spec (authoritative):** `03-ui-specs/shared/matrix-dropdown.md`
4
+ > **Reference implementation:** `apps/client/src/components/MatrixDropdown.tsx`
3
5
  > **Component**: `MatrixDropdown`
4
6
  > **Used by**: `CsatMatrixScale`, `LikertMatrixScale`
5
7
 
@@ -1,7 +1,7 @@
1
1
  # Client Components Overview
2
2
 
3
- > This folder documents the React client components that render the survey UI.
4
- > It explains the architecture, the dispatcher (`Question.tsx`), and all 11 individual question scale components.
3
+ > Wiring blueprints for React components. **Authoritative UI details** are in [`03-ui-specs/`](../03-ui-specs/README.md) (reference-client driven).
4
+ > This folder documents props, `onSelect` wiring, and dispatcher architecture.
5
5
 
6
6
  ## Component Architecture
7
7
 
@@ -0,0 +1,158 @@
1
+ # Config Field Index
2
+
3
+ > Master map: every SDK config field → reference client file → implemented? → UI spec doc.
4
+ > Cross-check this when building any question with non-default config.
5
+
6
+ ## QuestionBase (all types)
7
+
8
+ | Field | Types | Client file | Implemented? | Spec doc |
9
+ |-------|-------|-------------|--------------|----------|
10
+ | `id` | all | Question.tsx | ✅ | 00-question-shell |
11
+ | `text` | all | Question.tsx | ✅ | 00-question-shell |
12
+ | `description` | all | Question.tsx | ✅ | 00-question-shell |
13
+ | `required` | all | Question.tsx | ✅ | 00-question-shell |
14
+ | `requiredErrorMessage` | all | Question.tsx | ✅ | 00-question-shell |
15
+ | `questionNumber` | all | — | ❌ | 00-question-shell |
16
+ | `containerMediaUrl` | all | — | ❌ | 00-question-shell |
17
+ | `containerMediaMimeType` | all | — | ❌ | 00-question-shell |
18
+ | `containerMediaAlignment` | all | — | ❌ | 00-question-shell |
19
+ | `containerMediaSize` | all | — | ❌ | 00-question-shell |
20
+
21
+ ## rating
22
+
23
+ | Field | Values | Client file | Implemented? | Spec doc |
24
+ |-------|--------|-------------|--------------|----------|
25
+ | `options[]` | SurveyOption[] | RatingScale.tsx | ✅ | 01-rating |
26
+ | `options[].color` | hex | RatingScale.tsx | ✅ | 01-rating |
27
+ | `minLabel` | string | Question.tsx | ✅ | 01-rating |
28
+ | `midLabel` | string | Question.tsx | ✅ | 01-rating |
29
+ | `maxLabel` | string | Question.tsx | ✅ | 01-rating |
30
+ | `midLabelIndex` | number | Question.tsx | ✅ | 01-rating |
31
+ | `reverseScaleOrder` | boolean | — | ❌ | 01-rating |
32
+ | `buttonVariant` | radio/numbered/emoji | — | ❌ | 01-rating |
33
+
34
+ ## radio
35
+
36
+ | Field | Values | Client file | Implemented? | Spec doc |
37
+ |-------|--------|-------------|--------------|----------|
38
+ | `options[]` | SurveyOption[] | Question.tsx | ✅ | 02-radio |
39
+ | `isMultiSelect` | boolean | — | ❌ | 02-radio |
40
+
41
+ ## text
42
+
43
+ | Field | Values | Client file | Implemented? | Spec doc |
44
+ |-------|--------|-------------|--------------|----------|
45
+ | `placeholder` | string | Question.tsx | ✅ | 03-text |
46
+ | `maxCharacterCount` | number | Question.tsx | ✅ | 03-text |
47
+ | `inputVariant` | long/short | Question.tsx | partial | 03-text |
48
+
49
+ ## csat
50
+
51
+ | Field | Values | Client file | Implemented? | Spec doc |
52
+ |-------|--------|-------------|--------------|----------|
53
+ | `options[]` | SurveyOption[] | CsatScale.tsx | ✅ | 04-csat |
54
+ | `buttonType` | emoji/star/numbered/... | CsatScale.tsx | ✅ | 04-csat |
55
+ | `labelPosition` | top/bottom/hidden | CsatScale.tsx | ✅ | 04-csat |
56
+ | `minLabel` / `maxLabel` | string | Question.tsx | ✅ | 04-csat |
57
+ | `hasNotApplicable` | boolean | — | ❌ | 04-csat |
58
+ | `reverseScaleOrder` | boolean | — | ❌ | 04-csat |
59
+
60
+ ## rating_scale
61
+
62
+ | Field | Values | Client file | Implemented? | Spec doc |
63
+ |-------|--------|-------------|--------------|----------|
64
+ | `options[]` | SurveyOption[] | — | ❌ | 05-rating-scale |
65
+ | `scaleStyle` | star/emoji | — | ❌ | 05-rating-scale |
66
+ | `minLabel` / `midLabel` / `maxLabel` | string | — | ❌ | 05-rating-scale |
67
+ | `midLabelIndex` | number | — | ❌ | 05-rating-scale |
68
+ | `reverseScaleOrder` | boolean | — | ❌ | 05-rating-scale |
69
+
70
+ ## slider
71
+
72
+ | Field | Values | Client file | Implemented? | Spec doc |
73
+ |-------|--------|-------------|--------------|----------|
74
+ | `min` / `max` / `step` | number | — | ❌ | 06-slider |
75
+ | `defaultValue` | number | — | ❌ | 06-slider |
76
+ | `minLabel` / `maxLabel` | string | — | ❌ | 06-slider |
77
+ | `enableInputBox` | boolean | SliderMatrixScale.tsx | partial | 06-slider |
78
+ | `displayValues` | boolean | CustomSliderTrack.tsx | partial | 06-slider, shared/custom-slider-track |
79
+
80
+ ## matrix (shared)
81
+
82
+ | Field | Values | Client file | Implemented? | Spec doc |
83
+ |-------|--------|-------------|--------------|----------|
84
+ | `subType` | CFM/CSAT/RATING | Question.tsx | ✅ | 07, 08 |
85
+ | `rows[]` | MatrixRow[] | Likert/CsatMatrix | ✅ | 07, 08 |
86
+ | `columns[]` | MatrixColumn[] | Likert/CsatMatrix | ✅ | 07, 08 |
87
+ | `matrixFormat` | standard/carousel/dropdown | Likert/CsatMatrix | ✅ | 07, 08 |
88
+ | `hasNotApplicable` | boolean | Likert/CsatMatrix | ✅ | 07, 08 |
89
+ | `reverseScaleOrder` | boolean | CsatMatrixScale | ✅ | 08 |
90
+ | `labels[]` | string[] | Likert/CsatMatrix | ✅ | 07, 08 |
91
+ | `buttonType` | SurveyButtonType | CsatMatrixScale | ✅ | 08 |
92
+
93
+ ## matrix — CFM only
94
+
95
+ | Field | Values | Client file | Implemented? | Spec doc |
96
+ |-------|--------|-------------|--------------|----------|
97
+ | `matrixType` | likert/bipolar | LikertMatrixScale | ✅ | 07-matrix-cfm |
98
+ | `answerType` | single/multiple | LikertMatrixScale | ✅ | 07-matrix-cfm |
99
+ | `transposeTable` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
100
+ | `repeatScale` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
101
+ | `enableScale` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
102
+
103
+ ## slider_matrix
104
+
105
+ | Field | Values | Client file | Implemented? | Spec doc |
106
+ |-------|--------|-------------|--------------|----------|
107
+ | `rows[].min/max/step` | number | SliderMatrixScale | ✅ | 09-slider-matrix |
108
+ | `labels[]` | string[] | SliderMatrixScale | ✅ | 09-slider-matrix |
109
+ | `ticks` | number | SliderMatrixScale | ✅ | 09-slider-matrix |
110
+ | `tickValues[]` | TickValue[] | SliderMatrixScale | ✅ | 09-slider-matrix |
111
+ | `enableInputBox` | boolean | SliderMatrixScale | ✅ | 09-slider-matrix |
112
+ | `displayValues` | boolean | CustomSliderTrack | ✅ | 09-slider-matrix |
113
+ | `enableNotApplicable` | boolean | SliderMatrixScale | ✅ | 09-slider-matrix |
114
+ | `sliderType` | graphics | SliderMatrixScale | ✅ | 09-slider-matrix |
115
+
116
+ ## file_upload
117
+
118
+ | Field | Values | Client file | Implemented? | Spec doc |
119
+ |-------|--------|-------------|--------------|----------|
120
+ | `uploadMessage` | HTML string | FileUploadScale | ✅ | 10-file-upload |
121
+ | `supportedFileFormats` | string[] | FileUploadScale | ✅ | 10-file-upload |
122
+ | `maxFileCount` | number | FileUploadScale | ✅ | 10-file-upload |
123
+ | `fileSizeLimit` | number (MB) | FileUploadScale | ✅ | 10-file-upload |
124
+ | `fileSizeLimitType` | PER_FILE/IN_TOTAL | FileUploadScale | ✅ | 10-file-upload |
125
+
126
+ ## text_and_media
127
+
128
+ | Field | Values | Client file | Implemented? | Spec doc |
129
+ |-------|--------|-------------|--------------|----------|
130
+ | `mediaUrl` | string | Question.tsx | ✅ | 11-text-and-media |
131
+ | `mediaMimeType` | string | Question.tsx | ✅ | 11-text-and-media |
132
+ | `mediaTitle` | string | Question.tsx | ✅ | 11-text-and-media |
133
+ | `mediaAlignment` | string | Question.tsx | ✅ | 11-text-and-media |
134
+ | `mediaSize` | number (%) | Question.tsx | ✅ | 11-text-and-media |
135
+
136
+ ## Implementation Gaps (no client branch yet)
137
+
138
+ | Gap | SDK type/field | Extend from |
139
+ |-----|----------------|-------------|
140
+ | `rating_scale` dispatcher | `type: 'rating_scale'` | CsatScale star/emoji patterns |
141
+ | `slider` dispatcher | `type: 'slider'` | CustomSliderTrack + SliderMatrixScale row |
142
+ | `radio.isMultiSelect` | checkbox cards | 02-radio card styling |
143
+ | `text.inputVariant: 'short'` | single-line input | 03-text focus tokens |
144
+ | `rating.reverseScaleOrder` | reverse options array | 01-rating |
145
+ | `rating.buttonVariant` | numbered clickable badges | 01-rating |
146
+ | `csat.hasNotApplicable` / `reverseScaleOrder` | standalone CSAT | 04-csat, 08-matrix N/A |
147
+ | `containerMedia*` on any type | container asset | 11-text-and-media media block |
148
+
149
+ ## buttonType × matrixFormat Quick Reference
150
+
151
+ | buttonType | matrixFormat | Layout component | Spec |
152
+ |------------|--------------|------------------|------|
153
+ | (CFM default) | standard | LikertMatrixGrid | 07 |
154
+ | (CFM default) | carousel | LikertMatrixCarousel | 07 |
155
+ | (CFM default) | dropdown | LikertMatrixDropdown | 07 |
156
+ | emoji/star/numbered | standard | CsatMatrixGrid | 08 |
157
+ | emoji/star/numbered/graphics | carousel | CsatMatrixCarousel | 08 |
158
+ | dropdown/selectbox/radio | any | CsatMatrixVerticalList | 08 |
@@ -1,22 +1,22 @@
1
1
  # Question Type → Component Routing
2
2
 
3
- > Canonical mapping. For UI implementation details, read `01-components/`. For data shapes, read `02-reference/question-types/`.
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
4
 
5
- | `question.type` | `question.subType` | Component | Answer Shape | Reference Doc |
6
- |-----------------|-------------------|-----------|--------------|---------------|
7
- | `'rating'` | — | `RatingScale` | `number` | `question-types/01-rating.md` |
8
- | `'radio'` | — | Inline in `Question` | `string \| number` | `question-types/README.md#radio` |
9
- | `'text'` | — | Inline in `Question` | `string` | `question-types/README.md#text` |
10
- | `'csat'` | — | `CsatScale` | `number \| null` | `question-types/04-csat.md` |
11
- | `'rating_scale'` | — | `RatingScale` | `number` | `question-types/05-rating-scale.md` |
12
- | `'slider'` | — | `CustomSliderTrack` | `number` | `question-types/06-slider.md` |
13
- | `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` | `question-types/07-matrix-cfm.md` |
14
- | `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` |
15
- | `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` |
16
- | `'rating_matrix'` | — | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/09-matrix-rating.md` |
17
- | `'slider_matrix'` | — | `SliderMatrixScale` | `MatrixAnswerMap` | `question-types/10-slider-matrix.md` |
18
- | `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` | `question-types/11-file-upload.md` |
19
- | `'text_and_media'` | — | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` |
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` |
20
20
 
21
21
  ## Component Blueprint Docs
22
22
 
@@ -0,0 +1,89 @@
1
+ # UI Spec: Question Shell (`Question.tsx`)
2
+
3
+ > **Reference:** `apps/client/src/components/Question.tsx`
4
+ > **SDK:** All types extend `QuestionBase` from `fetchSurvey/types/base.ts`
5
+ > **Component blueprint:** `01-components/02-question.md`
6
+
7
+ ## Config Inventory (QuestionBase)
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `id` | ✅ | `section id={question.id}` — required for scroll-to-error |
12
+ | `text` | ✅ | Heading via `dangerouslySetInnerHTML` |
13
+ | `description` | ✅ | Optional HTML block below title |
14
+ | `required` | ✅ | Red `*` after title (`text-red-500`) |
15
+ | `requiredErrorMessage` | ✅ | Shown via SDK validation in error banner |
16
+ | `questionNumber` | ❌ | Not rendered in reference client |
17
+ | `containerMediaUrl` | ❌ | Not rendered on generic questions |
18
+
19
+ ## Layout Anatomy
20
+
21
+ ```
22
+ ┌─ section (rounded-xl border border-gray-200 bg-white p-6 shadow-sm) ─────┐
23
+ │ h2: question.text (HTML) + red * if required │
24
+ │ [optional description HTML] │
25
+ │ ┌─ type-specific scale (see per-type specs) ─────────────────────┐ │
26
+ │ └─────────────────────────────────────────────────────────────────┘ │
27
+ │ [validation error banner if validationError prop] │
28
+ └─────────────────────────────────────────────────────────────────────────┘
29
+ ```
30
+
31
+ ## Dispatcher Routing
32
+
33
+ | `question.type` | Condition | Component |
34
+ |-----------------|-----------|-----------|
35
+ | `rating` | — | `RatingScale` + label row in Question |
36
+ | `radio` | — | Inline card radios in Question |
37
+ | `text` | — | Inline textarea in Question |
38
+ | `csat` | — | `CsatScale` + min/max labels in Question |
39
+ | `matrix` | `CSAT_MATRIX` \| `RATING_MATRIX` | `CsatMatrixScale` |
40
+ | `matrix` | `CFM_MATRIX` | `LikertMatrixScale` |
41
+ | `slider_matrix` | — | `SliderMatrixScale` |
42
+ | `file_upload` | — | `FileUploadScale` |
43
+ | `text_and_media` | `mediaUrl` present | Inline media in Question |
44
+ | `slider` | — | ❌ Not routed — see `06-slider.md` |
45
+ | `rating_scale` | — | ❌ Not routed — see `05-rating-scale.md` |
46
+
47
+ ## Label Positioning (rating)
48
+
49
+ Rendered in Question before `RatingScale`:
50
+
51
+ ```tsx
52
+ // midLabel CSS left:
53
+ left: `${(midLabelIndex / (options.length - 1)) * 100}%`
54
+ // with -translate-x-1/2 for centering
55
+ ```
56
+
57
+ minLabel: `absolute left-0`, maxLabel: `absolute right-0`, container `relative h-6 text-xs font-semibold text-gray-500`.
58
+
59
+ ## Label Positioning (csat)
60
+
61
+ minLabel / maxLabel: `flex justify-between text-xs font-semibold text-gray-500` above `CsatScale`.
62
+
63
+ ## Validation Error Banner
64
+
65
+ ```tsx
66
+ className="mt-4 rounded-[4px] border border-[#333333] bg-[#d9d9d9] py-3.5 px-4"
67
+ // text: text-[13px] font-bold text-gray-900
68
+ ```
69
+
70
+ ## Props Contract
71
+
72
+ ```typescript
73
+ type QuestionProps = {
74
+ question: SurveyQuestion;
75
+ selectedValue?: AnswerValue;
76
+ validationError?: string;
77
+ onSelect: (value: AnswerValue) => void;
78
+ };
79
+ ```
80
+
81
+ Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId, value } })`.
82
+
83
+ ## Agent Checklist
84
+
85
+ - [ ] `id={question.id}` on outermost `<section>`
86
+ - [ ] HTML rendering for text and description
87
+ - [ ] Exhaustive type switch — all 12 types handled or explicitly noted as gap
88
+ - [ ] Validation error banner below scale
89
+ - [ ] Rating/csat endpoint labels positioned per formulas above
@@ -0,0 +1,80 @@
1
+ # UI Spec: NPS Rating (`type: 'rating'`)
2
+
3
+ > **Reference:** `apps/client/src/components/RatingScale.tsx` + label row in `Question.tsx`
4
+ > **SDK type:** `RatingQuestion` — `fetchSurvey/types/rating.ts`
5
+ > **Component blueprint:** `01-components/03-rating-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
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 |
15
+ | `minLabel` | ✅ | Rendered in Question.tsx above scale |
16
+ | `midLabel` | ✅ | Absolute positioned in Question.tsx |
17
+ | `maxLabel` | ✅ | Absolute positioned in Question.tsx |
18
+ | `midLabelIndex` | ✅ | Mid label `left` percentage |
19
+ | `reverseScaleOrder` | ❌ | Document for agents when SDK sends true |
20
+ | `buttonVariant` | ❌ | Client always uses dual-layer radio layout |
21
+
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
+ ## Answer Mutation
62
+
63
+ ```typescript
64
+ onSelect(option.value) // number
65
+ // → onAction CHANGE { questionId, value: number }
66
+ ```
67
+
68
+ ## Accessibility
69
+
70
+ - `role="radiogroup"` on wrapper
71
+ - Native `<input type="radio" className="sr-only">` per cell
72
+ - `name={questionId}` groups radios
73
+
74
+ ## Agent Checklist
75
+
76
+ - [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
77
+ - [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
78
+ - [ ] Only track row is clickable
79
+ - [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
80
+ - [ ] Selected state: pink inner track + thick magenta radio ring
@@ -0,0 +1,57 @@
1
+ # UI Spec: Radio MCQ (`type: 'radio'`)
2
+
3
+ > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
+ > **SDK type:** `RadioQuestion` — `fetchSurvey/types/radio.ts`
5
+ > **Component blueprint:** `01-components/02-question.md`
6
+
7
+ ## Config Inventory
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 |
15
+
16
+ ## Layout Anatomy
17
+
18
+ ```
19
+ ┌─ label (full-width card) ─────────────────────────────────────┐
20
+ │ (●) Option label HTML │ selected
21
+ └───────────────────────────────────────────────────────────────┘
22
+ ┌─ label ───────────────────────────────────────────────────────┐
23
+ │ (○) Option label HTML │ unselected
24
+ └───────────────────────────────────────────────────────────────┘
25
+ ```
26
+
27
+ ## Interactive States
28
+
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
+ ```
44
+
45
+ ## Answer Mutation
46
+
47
+ ```typescript
48
+ onSelect(option.value) // string | number
49
+ ```
50
+
51
+ ## Agent Checklist
52
+
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)
@@ -0,0 +1,51 @@
1
+ # UI Spec: Text Field (`type: 'text'`)
2
+
3
+ > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
+ > **SDK type:** `TextQuestion` — `fetchSurvey/types/text.ts`
5
+
6
+ ## Config Inventory
7
+
8
+ | Field | Implemented | UI impact |
9
+ |-------|-------------|-----------|
10
+ | `placeholder` | ✅ | Textarea placeholder; default `'Type your response here...'` |
11
+ | `maxCharacterCount` | ✅ | Counter uses this; default 1000 |
12
+ | `inputVariant: 'long'` | ✅ | Always textarea (4 rows) in reference |
13
+ | `inputVariant: 'short'` | ❌ | Should render single-line `<input>` when SDK sends `'short'` |
14
+
15
+ ## Layout Anatomy
16
+
17
+ ```
18
+ ┌─ textarea (w-full rounded-[4px] border border-gray-300 p-4) ──┐
19
+ │ placeholder text │
20
+ │ │
21
+ └─────────────────────────────────────────────────────────────────┘
22
+ N Zeichen verbleiben. ← counter
23
+ ```
24
+
25
+ ## Interactive States
26
+
27
+ | State | Style |
28
+ |-------|-------|
29
+ | Default | `border-gray-300` |
30
+ | Focus | `focus:border-[#e20074] focus:ring-1 focus:ring-[#e20074]` |
31
+
32
+ ## Character Counter
33
+
34
+ ```typescript
35
+ remaining = maxCharacterCount ?? 1000 - selectedValue.length
36
+ // Display: "{remaining} Zeichen verbleiben." (German in reference client)
37
+ // Agents: use survey language for i18n
38
+ ```
39
+
40
+ ## Answer Mutation
41
+
42
+ ```typescript
43
+ onSelect(string) // onChange from textarea
44
+ ```
45
+
46
+ ## Agent Checklist
47
+
48
+ - [ ] Textarea with 4 rows for long variant
49
+ - [ ] Character counter bottom-right
50
+ - [ ] Magenta focus ring
51
+ - [ ] Implement `inputVariant: 'short'` as single-line input when present