@explorer02/cfm-survey-sdk 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/cli/index.js +18 -18
  2. package/dist/cli/index.mjs +14 -14
  3. package/dist/index.d.mts +465 -580
  4. package/dist/index.d.ts +465 -580
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/package.json +6 -4
  8. package/postinstall.js +96 -69
  9. package/templates/AGENT.md +18 -8
  10. package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
  11. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  12. package/templates/docs/00-integration/aws-deploy.md +281 -0
  13. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  14. package/templates/docs/00-integration/component-checklist.md +55 -22
  15. package/templates/docs/00-integration/constraints.md +49 -4
  16. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  17. package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
  18. package/templates/docs/00-integration/end-page-logic.md +64 -0
  19. package/templates/docs/00-integration/file-upload-aws.md +116 -0
  20. package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
  21. package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
  22. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  23. package/templates/docs/00-integration/progress.md +61 -0
  24. package/templates/docs/00-integration/question-display-variants.md +65 -0
  25. package/templates/docs/00-integration/question-numbering.md +51 -0
  26. package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
  27. package/templates/docs/00-integration/setup.md +84 -0
  28. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  29. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  30. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  31. package/templates/docs/01-components/01-survey-page.md +76 -54
  32. package/templates/docs/01-components/02-question.md +61 -58
  33. package/templates/docs/01-components/03-rating-scale.md +11 -1
  34. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  35. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  36. package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
  37. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  38. package/templates/docs/01-components/08-file-upload-scale.md +23 -51
  39. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  40. package/templates/docs/01-components/11-progress-bar.md +10 -4
  41. package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
  42. package/templates/docs/01-components/14-intro-page.md +34 -0
  43. package/templates/docs/01-components/15-end-page.md +31 -0
  44. package/templates/docs/01-components/16-paused-page.md +27 -0
  45. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  46. package/templates/docs/01-components/18-rank-order-scale.md +93 -0
  47. package/templates/docs/01-components/README.md +42 -36
  48. package/templates/docs/02-reference/config-field-index.md +141 -139
  49. package/templates/docs/02-reference/exports.md +149 -0
  50. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  51. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  52. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  53. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  54. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  55. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  56. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  57. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  58. package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
  59. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  60. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  61. package/templates/docs/02-reference/question-types/README.md +96 -47
  62. package/templates/docs/02-reference/routing-table.md +33 -18
  63. package/templates/docs/02-reference/value-derivation.md +63 -0
  64. package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
  65. package/templates/docs/03-ui-specs/01-rating.md +18 -52
  66. package/templates/docs/03-ui-specs/02-radio.md +80 -37
  67. package/templates/docs/03-ui-specs/03-text.md +2 -2
  68. package/templates/docs/03-ui-specs/04-csat.md +68 -33
  69. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  70. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  71. package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
  72. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
  73. package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
  74. package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
  75. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  76. package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
  77. package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
  78. package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
  79. package/templates/docs/03-ui-specs/README.md +16 -14
  80. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
  81. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  82. package/templates/docs/MANIFEST.json +476 -69
  83. package/templates/docs/index.md +82 -30
  84. package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
  85. package/templates/docs/templates/MatrixDropdown.tsx +216 -0
  86. package/templates/docs/templates/Question.tsx +221 -154
  87. package/templates/docs/templates/RankOrderScale.tsx +353 -0
  88. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  89. package/templates/docs/templates/heatmapCoords.ts +58 -0
  90. package/templates/docs/templates/implementation_plan.md +126 -67
  91. package/templates/docs/templates/survey-inventory.schema.json +77 -0
  92. package/templates/docs/templates/surveyUiIcons.tsx +52 -0
  93. package/templates/docs/templates/verify-agent-build.sh +85 -0
  94. package/templates/docs/01-components/04-csat-scale.md +0 -40
  95. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -1,34 +1,34 @@
1
- # UI Spec: CFM Matrix (`type: 'matrix'`, `subType: 'CFM_MATRIX'`)
1
+ # UI Spec: CFM Matrix (`type: 'CFM_MATRIX'`)
2
2
 
3
3
  > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/LikertMatrixScale.tsx`
4
- > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
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
 
@@ -46,21 +46,40 @@ CFM_MATRIX
46
46
  - Zebra rows: even rows `rgba(249,250,251,0.8)`
47
47
  - Native inputs: `accentColor: #e20074`, 18×18px
48
48
 
49
- ## Carousel
49
+ ## Carousel — Implementation Recipe
50
50
 
51
- - Blue active dot: `#2563eb` (Likert only)
52
- - Card: white, border, shadow, centered row text
51
+ | Rule | Behavior |
52
+ |------|----------|
53
+ | Prev button | Disabled when `currentRowIndex === 0` |
54
+ | Next button | Disabled when on last visible row |
55
+ | Dot indicators | Click dot → jump to that row index |
56
+ | On answer | **Do not** auto-advance to next row |
57
+ | Keyboard | Optional Left/Right for prev/next |
58
+ | Active dot (Likert) | `#2563eb`, expanded pill `20×8px` vs `8×8px` inactive |
53
59
 
54
- ## Answer Mutation
60
+ One statement row visible at a time; column headers below cells when `showColumnHeaders`.
61
+
62
+ ## N/A mutex (all layouts)
63
+
64
+ | Action | Result |
65
+ |--------|--------|
66
+ | Select N/A | Clear other column values for row; other inputs opacity 0.4 + disabled |
67
+ | Select column after N/A | Clear N/A first |
68
+ | Multi-select + N/A | N/A sets `[null]` exclusively |
69
+
70
+ ## Implementation Recipe
71
+
72
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
55
73
 
56
74
  ```typescript
57
- onSelect(MatrixAnswerMap) // Record<rowId, value | value[] | null>
75
+ onSelect(MatrixRowAnswers) // Record<statementRows[].id, value | value[] | null>
76
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
58
77
  ```
59
78
 
60
79
  ## Agent Checklist
61
80
 
62
- - [ ] All three matrixFormat branches
63
- - [ ] transposeTable + repeatScale + enableScale
64
- - [ ] Bipolar 25/50/25 layout with rightText
81
+ - [ ] All three gridLayout branches
82
+ - [ ] transposeTable + repeatColumnHeaders + showColumnHeaders
83
+ - [ ] Bipolar 25/50/25 layout with oppositeStatementText
65
84
  - [ ] Single-select deselect on re-click
66
85
  - [ ] N/A mutex with opacity 0.4
@@ -1,65 +1,41 @@
1
- # UI Spec: CSAT / Rating Matrix (`subType: 'CSAT_MATRIX' | 'RATING_MATRIX'`)
1
+ # UI Spec: Rating Matrix Grid (`type: 'RATING_MATRIX'`)
2
2
 
3
3
  > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CsatMatrixScale.tsx`
4
- > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
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
22
+ Same as `CSAT_MATRIX` — reuse `CsatMatrixScale`. See [04-csat.md](04-csat.md) for emoji/star/carousel/N/A/graphics recipes.
40
23
 
41
- ## Carousel
24
+ ## Carousel / N/A / scale labels
42
25
 
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).
26
+ Follow CSAT Implementation Recipes in [04-csat.md](04-csat.md): pink carousel dots, N/A mutex, `tickLabels` from `scaleColumns[].label` on graphics sliders.
51
27
 
52
28
  ## Answer Mutation
53
29
 
30
+ Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
31
+
54
32
  ```typescript
55
- onSelect(MatrixAnswerMap)
56
- // hasNotApplicable: null for N/A; undefined clears
33
+ onSelect(MatrixRowAnswers)
34
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
57
35
  ```
58
36
 
59
37
  ## Agent Checklist
60
38
 
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
39
+ - [ ] Reuse CsatMatrixScale with `RATING_MATRIX` type
40
+ - [ ] Apply accentColor on numbered badges when present
41
+ - [ ] minLabel / maxLabel above grid when set
@@ -1,4 +1,4 @@
1
- # UI Spec: Slider Matrix (`type: 'slider_matrix'`)
1
+ # UI Spec: Slider Matrix (`type: 'SLIDER_MATRIX'`)
2
2
 
3
3
  > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/SliderMatrixScale.tsx`
4
4
  > **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
@@ -36,6 +36,23 @@ sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100
36
36
  // graphics: plain text label instead of colored badge
37
37
  ```
38
38
 
39
+ ## CustomSliderTrack per row — scale labels
40
+
41
+ ```typescript
42
+ <CustomSliderTrack
43
+ min={row.min}
44
+ max={row.max}
45
+ htmlStep={row.htmlStep}
46
+ value={rowVal as number}
47
+ tickLabels={row.tickLabels ?? scaleColumns.map(c => c.label)}
48
+ ticks={row.ticks}
49
+ sliderType={question.sliderStyle === 'graphics' ? 'graphics' : undefined}
50
+ onChange={v => updateRow(row.id, v)}
51
+ />
52
+ ```
53
+
54
+ Pass **`scaleColumns[].label`** (or row tick labels) as `tickLabels` — required for correct tooltip text (Day 19 fix).
55
+
39
56
  ## N/A Behavior
40
57
 
41
58
  - Checked → `rowVal === 'N/A'`; slider disabled, input grayed
@@ -1,18 +1,18 @@
1
- # UI Spec: File Upload (`type: 'file_upload'`)
1
+ # UI Spec: File Upload (`type: 'FILE_UPLOAD'`)
2
2
 
3
- > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/FileUploadScale.tsx`
4
3
  > **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
5
4
  > **Component blueprint:** `01-components/08-file-upload-scale.md`
5
+ > **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
6
6
 
7
7
  ## Config Inventory
8
8
 
9
- | Field | Implemented | UI impact |
10
- |-------|-------------|-----------|
11
- | `uploadMessage` | ✅ | HTML in dropzone (bold 16px) |
12
- | `supportedFileFormats` | ✅ | Validation + accept attribute |
13
- | `maxFileCount` | ✅ | Total file limit |
14
- | `fileSizeLimit` | ✅ | MB limit |
15
- | `fileSizeLimitType` | ✅ | `PER_FILE` or `IN_TOTAL` |
9
+ | Field | UI impact |
10
+ |-------|-----------|
11
+ | `uploadMessage` | HTML in dropzone (bold 16px) |
12
+ | `supportedFileFormats` | Validation + accept attribute |
13
+ | `maxFileCount` | Total file limit |
14
+ | `fileSizeLimit` | MB limit |
15
+ | `fileSizeLimitType` | `PER_FILE` or `IN_TOTAL` |
16
16
 
17
17
  ## Layout Anatomy
18
18
 
@@ -20,7 +20,7 @@
20
20
  [uploadMessage HTML in dashed dropzone]
21
21
  File limit: N; File size limit: XMB/file|in total; Supports: ...
22
22
  [local error banner — red if validation fails]
23
- [file list cards: icon, name, size, remove ×]
23
+ [file list cards: icon, name, size, uploading…, remove ×]
24
24
  ```
25
25
 
26
26
  ## Interactive States
@@ -31,24 +31,45 @@
31
31
  | Hover | `#e20074` | `#fdf2f8` |
32
32
  | Drag active | `#e20074` | `#fdf2f8` |
33
33
 
34
- ## Local Validation (before SDK)
34
+ ## Implementation Recipe
35
35
 
36
- 1. Max file count exceeded
37
- 2. Unsupported extension
38
- 3. PER_FILE size exceeded
39
- 4. IN_TOTAL cumulative size exceeded
36
+ ### Phase 1 Local pick + validate
40
37
 
41
- Error banner: `#fee2e2` bg, `#b91c1c` text.
38
+ 1. Hidden `<input type="file" multiple={maxFileCount > 1}>`
39
+ 2. Drag enter/over/leave/drop on dropzone
40
+ 3. Reject: wrong extension, count exceeded, size exceeded
41
+
42
+ ### Phase 2 — Upload (required for submit)
43
+
44
+ ```
45
+ User picks File
46
+ → POST {uploadApi}/get-upload-url { fileName, contentType, questionId }
47
+ ← { uploadUrl, fileId, mediaUrl }
48
+ → PUT uploadUrl (binary body, file Content-Type)
49
+ → onSelect([...existing, { id: fileId, mediaUrl, name, mimeType, size }])
50
+ → onAction(CHANGE)
51
+ ```
52
+
53
+ See [`file-upload-aws.md`](../00-integration/file-upload-aws.md).
54
+
55
+ ### Phase 3 — Remove
56
+
57
+ Remove from list → `onSelect(undefined)` when empty, else updated `UploadedFileAnswer[]`.
42
58
 
43
59
  ## Answer Mutation
44
60
 
45
61
  ```typescript
46
- onSelect(File[] | undefined) // undefined when all removed
62
+ // After upload completes NOT raw File[]
63
+ onSelect: (value: UploadedFileAnswer[] | undefined) => void
64
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: UploadedFileAnswer[] } })
47
65
  ```
48
66
 
67
+ **Non-submittable:** storing `File[]` only — SDK submit drops attachments.
68
+
49
69
  ## Agent Checklist
50
70
 
51
- - [ ] Hidden `<input type="file">` triggered by dropzone click
52
- - [ ] Drag enter/over/leave/drop handlers
53
- - [ ] Dynamic constraints line in dropzone
54
- - [ ] File list with remove button (hover red)
71
+ - [ ] Local validation before upload
72
+ - [ ] Presigned upload wired; progress/spinner per file
73
+ - [ ] Final answer shape: `UploadedFileAnswer[]` with `id` + `mediaUrl`
74
+ - [ ] Remove clears answer or passes updated array
75
+ - [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
@@ -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,17 +49,56 @@ 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
 
51
56
  - Hidden when `languages.length <= 1`
52
57
  - Focus ring: `#2563eb`
58
+ - Pass `language` in `useMemo` hook options — SDK remaps survey client-side (no refetch)
59
+
60
+ ## Question numbering
61
+
62
+ Render ordinals from SDK on content pages:
63
+
64
+ ```tsx
65
+ {question.questionNumber != null && (
66
+ <span className="mr-1 font-semibold">{question.questionNumber}.</span>
67
+ )}
68
+ ```
69
+
70
+ See [`question-numbering.md`](../00-integration/question-numbering.md).
71
+
72
+ ## Special pages
73
+
74
+ | Phase | Component | Action |
75
+ |-------|-----------|--------|
76
+ | `intro` | `IntroPage` | `page={state.specialPage}` · `onStart={() => onAction({ type: 'START' })}` |
77
+ | `paused` | `PausedPage` | `page={state.specialPage}` · read-only, no nav |
78
+ | `end` | `EndPage` | `page={state.specialPage}` · terminal thank-you |
79
+
80
+ ## Terminal phases
81
+
82
+ Render a simple message (no question nav) for:
83
+
84
+ - `submitted` — already completed
85
+ - `expired` — survey expired
86
+ - `quota_fulfilled` — quota reached
87
+
88
+ Keep Header/Footer chrome consistent across all phases.
89
+
90
+ ## Submit loading
91
+
92
+ Disable primary Next/Submit when `submitSurveyResults.isLoading`; label "Submitting…" / "Wird gesendet…".
53
93
 
54
94
  ## Agent Checklist
55
95
 
56
- - [ ] All 5 states implemented
57
- - [ ] `onAction({ type: 'CHANGE' })` wired per question
58
- - [ ] PREVIOUS / NEXT actions on nav buttons
96
+ - [ ] All phase branches implemented
97
+ - [ ] `onAction({ type: 'START' })` on intro
98
+ - [ ] `onAction({ type: 'CHANGE' })` wired per question with `allAnswers` / `allQuestions`
99
+ - [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
59
100
  - [ ] ProgressBar receives `state.progressPercentage`
60
101
  - [ ] Header/Footer on every state branch
102
+ - [ ] `questionNumber` prefix when present on content questions
103
+ - [ ] Intro / Paused / End / terminal phase branches
104
+ - [ ] Submit button disabled while `submitSurveyResults.isLoading`
@@ -0,0 +1,80 @@
1
+ # UI Spec: Heatmap (`type: 'HEATMAP'`)
2
+
3
+ > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/HeatmapScale.tsx`
4
+ > **SDK type:** `HeatmapQuestion` — `fetchSurvey/types/heatmap.ts`
5
+ > **Component blueprint:** `01-components/17-heatmap-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `questionText` | ✅ (shell) | Bold title + required asterisk |
12
+ | `questionDescription` | ✅ (shell) | Instruction text below title |
13
+ | `required` | ✅ (shell + SDK) | Asterisk + validation on NEXT |
14
+ | `imageUrl` | ✅ | Center-aligned interactive image |
15
+ | `maxClicksAllowed` | ✅ | Footer hint count; blocks new clicks at limit |
16
+ | `regions` | ❌ (respondent UI) | Logic only — not rendered on survey |
17
+
18
+ ## Layout Anatomy
19
+
20
+ ```
21
+ [Question shell: title *, description]
22
+ [Heatmap container — relative, inline-block, max-width 100%]
23
+ [<img> — object-contain, display block, crosshair cursor]
24
+ [Spot overlay — absolute inset-0, pointer-events-none]
25
+ [Orange circle markers at normalized x/y positions]
26
+ [Footer hint — info icon + max spots text]
27
+ [Validation error — from Question shell]
28
+ ```
29
+
30
+ ## Spot Marker Visual Spec
31
+
32
+ | Property | Value |
33
+ |----------|-------|
34
+ | Shape | Circle |
35
+ | Diameter | 16px |
36
+ | Fill | `#F97316` |
37
+ | Border | 2px solid `#FFFFFF` |
38
+ | Shadow | `0 1px 3px rgba(0,0,0,0.25)` |
39
+ | Position | `left: x * 100%`, `top: y * 100%`, `transform: translate(-50%, -50%)` |
40
+
41
+ ## Interactive States
42
+
43
+ | State | Behavior |
44
+ |-------|----------|
45
+ | Image loading | Spinner overlay; clicks disabled |
46
+ | Ready | `cursor: crosshair` on image |
47
+ | At max clicks | Block new adds; allow remove |
48
+ | Removing last spot | `onSelect(undefined)` |
49
+
50
+ ## Footer Hint Text
51
+
52
+ ```
53
+ ⓘ You can select up to {maxClicksAllowed} spot(s). To change your selection, simply click on the same spot again to remove it.
54
+ ```
55
+
56
+ Use singular "spot" when `maxClicksAllowed === 1`.
57
+
58
+ ## Answer Mutation
59
+
60
+ ```typescript
61
+ onSelect(HeatmapClickPoint[] | undefined)
62
+ // HeatmapClickPoint = { id: string; x: number; y: number }
63
+ // x, y normalized 0–1 relative to rendered image bounds
64
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: spots } })
65
+ ```
66
+
67
+ Copy coord helpers from [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts): `getNormalizedClickFromImage`, `findSpotNearClick` (14px remove radius), `createNewSpot`.
68
+
69
+ ## Logic fields
70
+
71
+ `HEATMAP` and `HEATMAP_REGION` — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
72
+
73
+ ## Agent Checklist
74
+
75
+ - [ ] Click handler on `<img>` element (not overlay) for precise coordinates
76
+ - [ ] `inline-block` wrapper so spots align with image bounds
77
+ - [ ] Toggle-remove within 14px hit radius
78
+ - [ ] Wait for `onLoad` before accepting clicks
79
+ - [ ] Percentage-positioned spots (survives resize)
80
+ - [ ] Do NOT render region overlays on respondent UI