@explorer02/cfm-survey-sdk 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/cli/index.js +18 -18
  2. package/dist/cli/index.mjs +14 -14
  3. package/dist/index.d.mts +465 -580
  4. package/dist/index.d.ts +465 -580
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/package.json +6 -4
  8. package/postinstall.js +96 -64
  9. package/templates/AGENT.md +15 -7
  10. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  11. package/templates/docs/00-integration/aws-deploy.md +281 -0
  12. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  13. package/templates/docs/00-integration/component-checklist.md +84 -0
  14. package/templates/docs/00-integration/constraints.md +65 -3
  15. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  16. package/templates/docs/00-integration/display-logic-and-navigation.md +49 -0
  17. package/templates/docs/00-integration/end-page-logic.md +64 -0
  18. package/templates/docs/00-integration/partial-save-and-recovery.md +74 -0
  19. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  20. package/templates/docs/00-integration/progress.md +61 -0
  21. package/templates/docs/00-integration/question-display-variants.md +65 -0
  22. package/templates/docs/00-integration/question-numbering.md +51 -0
  23. package/templates/docs/00-integration/question-type-sdk-matrix.md +242 -0
  24. package/templates/docs/00-integration/setup.md +84 -0
  25. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  26. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  27. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  28. package/templates/docs/01-components/01-survey-page.md +76 -54
  29. package/templates/docs/01-components/02-question.md +77 -89
  30. package/templates/docs/01-components/03-rating-scale.md +11 -1
  31. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  32. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  33. package/templates/docs/01-components/06-likert-matrix-scale.md +12 -3
  34. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  35. package/templates/docs/01-components/08-file-upload-scale.md +10 -1
  36. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  37. package/templates/docs/01-components/11-progress-bar.md +10 -4
  38. package/templates/docs/01-components/13-matrix-dropdown.md +3 -1
  39. package/templates/docs/01-components/14-intro-page.md +34 -0
  40. package/templates/docs/01-components/15-end-page.md +31 -0
  41. package/templates/docs/01-components/16-paused-page.md +27 -0
  42. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  43. package/templates/docs/01-components/18-rank-order-scale.md +100 -0
  44. package/templates/docs/01-components/README.md +44 -36
  45. package/templates/docs/02-reference/config-field-index.md +141 -139
  46. package/templates/docs/02-reference/exports.md +149 -0
  47. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  48. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  49. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  50. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  51. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  52. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  53. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  54. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  55. package/templates/docs/02-reference/question-types/11-file-upload.md +13 -2
  56. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  57. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  58. package/templates/docs/02-reference/question-types/README.md +86 -47
  59. package/templates/docs/02-reference/routing-table.md +33 -18
  60. package/templates/docs/02-reference/value-derivation.md +63 -0
  61. package/templates/docs/03-ui-specs/00-question-shell.md +24 -80
  62. package/templates/docs/03-ui-specs/01-rating.md +12 -54
  63. package/templates/docs/03-ui-specs/02-radio.md +21 -33
  64. package/templates/docs/03-ui-specs/03-text.md +2 -2
  65. package/templates/docs/03-ui-specs/04-csat.md +38 -33
  66. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  67. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  68. package/templates/docs/03-ui-specs/07-matrix-cfm.md +25 -22
  69. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +18 -46
  70. package/templates/docs/03-ui-specs/09-slider-matrix.md +2 -2
  71. package/templates/docs/03-ui-specs/10-file-upload.md +2 -2
  72. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  73. package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
  74. package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
  75. package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
  76. package/templates/docs/03-ui-specs/README.md +14 -13
  77. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +3 -3
  78. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +6 -1
  79. package/templates/docs/MANIFEST.json +454 -67
  80. package/templates/docs/index.md +84 -29
  81. package/templates/docs/templates/Question.tsx +310 -0
  82. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  83. package/templates/docs/templates/heatmapCoords.ts +58 -0
  84. package/templates/docs/templates/implementation_plan.md +115 -49
  85. package/templates/docs/templates/survey-inventory.schema.json +72 -0
  86. package/templates/docs/templates/verify-agent-build.sh +53 -0
  87. package/templates/docs/01-components/04-csat-scale.md +0 -40
  88. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -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
 
@@ -1,34 +1,34 @@
1
- # UI Spec: CFM Matrix (`type: 'matrix'`, `subType: 'CFM_MATRIX'`)
1
+ # UI Spec: CFM Matrix (`type: 'CFM_MATRIX'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/LikertMatrixScale.tsx`
4
- > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/LikertMatrixScale.tsx`
4
+ > **SDK type:** `CfmMatrixQuestion` — `fetchSurvey/types/cfmMatrix.ts`
5
5
  > **Component blueprint:** `01-components/06-likert-matrix-scale.md`
6
6
 
7
7
  ## Config Inventory
8
8
 
9
9
  | Field | Implemented | UI impact |
10
10
  |-------|-------------|-----------|
11
- | `rows[]`, `columns[]` | ✅ | Grid / carousel / dropdown data |
12
- | `matrixFormat` | ✅ | Orchestrator routing |
13
- | `answerType` | ✅ | `'multiple'` → checkboxes |
14
- | `matrixType: 'bipolar'` | ✅ | 25% left + 25% right columns |
15
- | `labels[]` | ✅ | Anchor labels above scale |
11
+ | `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
12
+ | `gridLayout` | ✅ | Orchestrator routing |
13
+ | `selectionMode` | ✅ | `'multiple'` → checkboxes |
14
+ | `statementLayout: 'bipolar'` | ✅ | 25% left + 25% right columns |
15
+ | `scaleAnchorLabels[]` | ✅ | Anchor labels above scale |
16
16
  | `transposeTable` | ✅ | Swap row/col iteration |
17
- | `repeatScale` | ✅ | Re-render header per row |
18
- | `enableScale` | ✅ | Show/hide column header row |
19
- | `hasNotApplicable` | ✅ | N/A column with mutex |
17
+ | `repeatColumnHeaders` | ✅ | Re-render header per row |
18
+ | `showColumnHeaders` | ✅ | Show/hide column header row |
19
+ | `hasNotApplicableOption` | ✅ | N/A column with mutex |
20
20
 
21
21
  ## Config Decision Tree
22
22
 
23
23
  ```
24
24
  CFM_MATRIX
25
- ├── matrixFormat === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
26
- ├── matrixFormat === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
25
+ ├── gridLayout === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
26
+ ├── gridLayout === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
27
27
  └── else → LikertMatrixGridLayout
28
28
  ├── transposeTable ? columns×rows : rows×columns
29
- ├── repeatScale ? header per row : single header
30
- ├── enableScale ? column headers : hidden
31
- └── matrixType === 'bipolar' ? 25% | scale | 25% layout
29
+ ├── repeatColumnHeaders ? header per row : single header
30
+ ├── showColumnHeaders ? column headers : hidden
31
+ └── statementLayout === 'bipolar' ? 25% | scale | 25% layout
32
32
  ```
33
33
 
34
34
  ## Interactive Behaviors
@@ -37,8 +37,8 @@ CFM_MATRIX
37
37
  |----------|-----------|--------|
38
38
  | Click selected radio again | single-select grid/carousel | Clear row (`undefined`) |
39
39
  | N/A selected | any | Other cols opacity 0.4, disabled |
40
- | Multi N/A | answerType multiple | Sets `[null]` exclusively |
41
- | Multi toggle | answerType multiple | Add/remove from array |
40
+ | Multi N/A | selectionMode multiple | Sets `[null]` exclusively |
41
+ | Multi toggle | selectionMode multiple | Add/remove from array |
42
42
 
43
43
  ## Grid Layout
44
44
 
@@ -53,14 +53,17 @@ CFM_MATRIX
53
53
 
54
54
  ## Answer Mutation
55
55
 
56
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
57
+
56
58
  ```typescript
57
- onSelect(MatrixAnswerMap) // Record<rowId, value | value[] | null>
59
+ onSelect(MatrixRowAnswers) // Record<statementRows[].id, value | value[] | null>
60
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
58
61
  ```
59
62
 
60
63
  ## Agent Checklist
61
64
 
62
- - [ ] All three matrixFormat branches
63
- - [ ] transposeTable + repeatScale + enableScale
64
- - [ ] Bipolar 25/50/25 layout with rightText
65
+ - [ ] All three gridLayout branches
66
+ - [ ] transposeTable + repeatColumnHeaders + showColumnHeaders
67
+ - [ ] Bipolar 25/50/25 layout with oppositeStatementText
65
68
  - [ ] Single-select deselect on re-click
66
69
  - [ ] N/A mutex with opacity 0.4
@@ -1,65 +1,37 @@
1
- # UI Spec: CSAT / Rating Matrix (`subType: 'CSAT_MATRIX' | 'RATING_MATRIX'`)
1
+ # UI Spec: Rating Matrix Grid (`type: 'RATING_MATRIX'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/CsatMatrixScale.tsx`
4
- > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CsatMatrixScale.tsx`
4
+ > **SDK type:** `RatingMatrixQuestion` — `fetchSurvey/types/ratingMatrix.ts`
5
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
- | `buttonType` | ✅ | Grid vs vertical list routing |
12
- | `matrixFormat` | ✅ | carousel vs standard |
13
- | `hasNotApplicable` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
11
+ | `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
12
+ | `displayStyle` | ✅ | Grid vs vertical list routing |
13
+ | `gridLayout` | ✅ | carousel vs standard vs dropdown |
14
+ | `hasNotApplicableOption` | ✅ | N/A column or checkbox |
14
15
  | `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
15
- | `labels[]` | ✅ | Anchor row above columns |
16
- | `columns[].color` | ✅ | RATING_MATRIX badge colors |
16
+ | `scaleAnchorLabels[]` | ✅ | Anchor row above columns |
17
+ | `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
18
+ | `scaleColumns[].accentColor` | ✅ | NPS-style badge colors when numbered |
17
19
 
18
20
  ## Config Decision Tree
19
21
 
20
- ```
21
- CSAT_MATRIX | RATING_MATRIX
22
- ├── matrixFormat === 'carousel' → CsatMatrixCarousel
23
- ├── buttonType in (dropdown, selectbox, radio) → CsatMatrixVerticalList
24
- │ ├── dropdown → MatrixDropdown placeholder "Select..."
25
- │ ├── selectbox → full-width cards #fdf2f8 selected
26
- │ └── else → card radio list
27
- └── else → CsatMatrixGrid
28
- ├── emoji → getEmojiForIndex + hover scale + ring when selected
29
- ├── star → opacity 0.5 unselected; filled/empty icons
30
- ├── numbered → 40×40 badge; selected magenta fill
31
- └── graphics → CustomSliderTrack per row (not in grid — carousel only)
32
- ```
33
-
34
- ## Grid Structure
35
-
36
- - Row label: `180px` fixed column
37
- - N/A column: `56px` when `hasNotApplicable`
38
- - Alternating row backgrounds
39
- - Column headers OR `labels[]` anchor row with percentage positioning
40
-
41
- ## Carousel
42
-
43
- - Pink active dot expands: `20px × 8px` vs `8px` inactive
44
- - Column labels below cells
45
- - Emoji tooltip via hover state (`hoveredCell`)
46
- - Graphics mode: slider maps column index
47
-
48
- ## Tooltips
49
-
50
- `title={col.label}` recommended on grid cells (emoji hover uses scale transform).
22
+ Same as `CSAT_MATRIX` grid — reuse `CsatMatrixScale` component. See [04-csat.md](04-csat.md) decision tree with `type: 'RATING_MATRIX'`.
51
23
 
52
24
  ## Answer Mutation
53
25
 
26
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
27
+
54
28
  ```typescript
55
- onSelect(MatrixAnswerMap)
56
- // hasNotApplicable: null for N/A; undefined clears
29
+ onSelect(MatrixRowAnswers)
30
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
57
31
  ```
58
32
 
59
33
  ## Agent Checklist
60
34
 
61
- - [ ] scaleColumns = columns.filter(c => c.value !== null)
62
- - [ ] All buttonType × matrixFormat branches
63
- - [ ] reverseScaleOrder on emoji/star/numbered
64
- - [ ] Carousel pink dot indicators
65
- - [ ] Vertical selectbox cards match standalone radio styling
35
+ - [ ] Reuse CsatMatrixScale with `RATING_MATRIX` type
36
+ - [ ] Apply accentColor on numbered badges when present
37
+ - [ ] minLabel / maxLabel above grid when set
@@ -1,6 +1,6 @@
1
- # UI Spec: Slider Matrix (`type: 'slider_matrix'`)
1
+ # UI Spec: Slider Matrix (`type: 'SLIDER_MATRIX'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/SliderMatrixScale.tsx`
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/SliderMatrixScale.tsx`
4
4
  > **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
5
5
  > **Component blueprint:** `01-components/07-slider-matrix-scale.md`
6
6
  > **Shared:** [`shared/custom-slider-track.md`](shared/custom-slider-track.md)
@@ -1,6 +1,6 @@
1
- # UI Spec: File Upload (`type: 'file_upload'`)
1
+ # UI Spec: File Upload (`type: 'FILE_UPLOAD'`)
2
2
 
3
- > **Reference:** `apps/client/src/components/FileUploadScale.tsx`
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/FileUploadScale.tsx`
4
4
  > **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
5
5
  > **Component blueprint:** `01-components/08-file-upload-scale.md`
6
6
 
@@ -1,4 +1,4 @@
1
- # UI Spec: Text and Media (`type: 'text_and_media'`)
1
+ # UI Spec: Text and Media (`type: 'TEXT_AND_MEDIA'`)
2
2
 
3
3
  > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
4
  > **SDK type:** `TextAndMediaQuestion` — `fetchSurvey/types/textAndMedia.ts`
@@ -8,42 +8,42 @@
8
8
 
9
9
  | Field | Implemented | UI impact |
10
10
  |-------|-------------|-----------|
11
- | `mediaUrl` | ✅ | Required to render media block |
12
- | `mediaMimeType` | ✅ | `video/` or `VIDEO` → `<video>`; else `<img>` |
13
- | `mediaTitle` | ✅ | Caption below media, centered gray text |
14
- | `mediaAlignment` | ✅ | Flex justify: CENTER / RIGHT / default left |
15
- | `mediaSize` | ✅ | Width `%` on media container; default 100% |
11
+ | `media?.url` | ✅ | Required to render media block |
12
+ | `media?.mimeType` | ✅ | `video/` or `VIDEO` → `<video>`; else `<img>` |
13
+ | `media?.title` | ✅ | Caption below media, centered gray text |
14
+ | `media?.alignment` | ✅ | Flex justify: CENTER / RIGHT / default left |
15
+ | `media?.size` | ✅ | Width `%` on media container; default 100% |
16
16
 
17
17
  No answer input — display-only question.
18
18
 
19
- ## Layout Anatomy
19
+ ## Media alignment anatomy
20
20
 
21
21
  ```
22
- [question title + description — standard shell]
23
-
24
- ┌─ flex container (justify per mediaAlignment) ──────────────┐
25
- │ ┌─ media block (width: mediaSize%) ──────────────────┐ │
26
- │ <video controls> or <img>
27
- │ [optional mediaTitle caption] │
28
- └─────────────────────────────────────────────────────┘
29
- └────────────────────────────────────────────────────────────┘
22
+ ┌─────────────────────────────────────────┐
23
+ │ questionText (HTML) │
24
+ │ questionDescription (optional)
25
+ ├─────────────────────────────────────────┤
26
+ [ media block — flex container ]
27
+ alignment LEFT: justify-start
28
+ alignment CENTER: justify-center
29
+ │ alignment RIGHT: justify-end │
30
+ │ width: media.size % (default 100) │
31
+ │ <img> or <video> max-w-full │
32
+ │ caption: media.title (centered gray) │
33
+ └─────────────────────────────────────────┘
30
34
  ```
31
35
 
32
- ## Alignment Mapping
33
-
34
- | `mediaAlignment` | CSS |
35
- |------------------|-----|
36
- | `TOP_CENTER`, `BOTTOM_CENTER` | `justify-center` |
37
- | `TOP_RIGHT`, `BOTTOM_RIGHT` | `justify-end` |
38
- | default | `justify-start` |
39
-
40
- ## Answer Mutation
41
-
42
- None — no `onSelect` payload for this type.
36
+ Container-level media (survey chrome) is separate — see `03-ui-specs/12-survey-chrome.md`.
43
37
 
44
38
  ## Agent Checklist
45
39
 
46
- - [ ] Render only when `mediaUrl` is present
47
- - [ ] Video vs image from mime type
40
+ - [ ] Render only when `question.media?.url` is present
41
+ - [ ] Video vs image from `media.mimeType`
48
42
  - [ ] `max-w-full rounded-md shadow-sm` on media element
49
- - [ ] Optional centered caption for `mediaTitle`
43
+ - [ ] Optional centered caption for `media.title`
44
+ - [ ] Respect `media.alignment` (LEFT / CENTER / RIGHT) and `media.size` width %
45
+
46
+ ## SDK Integration Checklist (agent)
47
+
48
+ - [ ] No CHANGE action — display-only
49
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#text_and_media`](../00-integration/question-type-sdk-matrix.md#text_and_media)
@@ -1,17 +1,22 @@
1
1
  # UI Spec: Survey Chrome
2
2
 
3
- > **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`
4
- > **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`
3
+ > **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`, `IntroPage.tsx`, `EndPage.tsx`, `PausedPage.tsx`
4
+ > **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`, `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`
5
5
 
6
- ## SurveyPage — 5-State Machine
6
+ ## SurveyPage — Phase Router
7
7
 
8
8
  | State | Condition | UI |
9
9
  |-------|-----------|-----|
10
10
  | Loading | `surveyQueryResults.isLoading` | Centered "Loading..." |
11
11
  | Error | `surveyQueryResults.error` | Red error message |
12
12
  | Empty | `!survey` | "No survey data" |
13
- | Submitted | `submitSurveyResults.data` | Thank you message |
14
- | Active | else | Questions + nav |
13
+ | Intro | `state.phase.kind === 'intro'` | `IntroPage` + Start CTA |
14
+ | Paused | `state.phase.kind === 'paused'` | `PausedPage` |
15
+ | End | `state.phase.kind === 'end'` | `EndPage` |
16
+ | Submitted | `state.phase.kind === 'submitted'` | Terminal message |
17
+ | Expired | `state.phase.kind === 'expired'` | Terminal message |
18
+ | Quota | `state.phase.kind === 'quota_fulfilled'` | Terminal message |
19
+ | Active | `state.phase.kind === 'content'` | Questions + nav |
15
20
 
16
21
  ## Active Layout
17
22
 
@@ -22,7 +27,7 @@ main (max-w-4xl)
22
27
  LanguageSelector (hidden if ≤1 language)
23
28
  Questions (space-y-12)
24
29
  Submit error (if any)
25
- Nav: Back (if page > 0) + Next/Submit
30
+ Nav: Back (if canGoBack) + Next/Submit (primaryButtonType)
26
31
  Footer
27
32
  ```
28
33
 
@@ -32,10 +37,10 @@ German when `survey.language.startsWith('de')`:
32
37
 
33
38
  | Action | EN | DE |
34
39
  |--------|----|----|
35
- | Back | Zurück | Back |
36
- | Next | Weiter | Next |
37
- | Submit | Absenden | Submit |
38
- | Submitting | Wird gesendet... | Submitting... |
40
+ | Back | Back | Zurück |
41
+ | Next | Next | Weiter |
42
+ | Submit | Submit | Absenden |
43
+ | Submitting | Submitting... | Wird gesendet... |
39
44
 
40
45
  ## Button Tokens
41
46
 
@@ -44,7 +49,7 @@ German when `survey.language.startsWith('de')`:
44
49
 
45
50
  ## ProgressBar
46
51
 
47
- Pink fill bar, `300ms` width transition on `progressPercentage`.
52
+ Pink fill bar. Bound to `state.progressPercentage` (path-scoped see `00-integration/progress.md`).
48
53
 
49
54
  ## LanguageSelector
50
55
 
@@ -53,8 +58,9 @@ Pink fill bar, `300ms` width transition on `progressPercentage`.
53
58
 
54
59
  ## Agent Checklist
55
60
 
56
- - [ ] All 5 states implemented
57
- - [ ] `onAction({ type: 'CHANGE' })` wired per question
58
- - [ ] PREVIOUS / NEXT actions on nav buttons
61
+ - [ ] All phase branches implemented
62
+ - [ ] `onAction({ type: 'START' })` on intro
63
+ - [ ] `onAction({ type: 'CHANGE' })` wired per question with `allAnswers` / `allQuestions`
64
+ - [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
59
65
  - [ ] ProgressBar receives `state.progressPercentage`
60
66
  - [ ] Header/Footer on every state branch