@explorer02/cfm-survey-sdk 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/cli/index.js +18 -18
  2. package/dist/cli/index.mjs +14 -14
  3. package/dist/index.d.mts +465 -580
  4. package/dist/index.d.ts +465 -580
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/package.json +6 -4
  8. package/postinstall.js +96 -69
  9. package/templates/AGENT.md +14 -8
  10. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  11. package/templates/docs/00-integration/aws-deploy.md +281 -0
  12. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  13. package/templates/docs/00-integration/component-checklist.md +45 -22
  14. package/templates/docs/00-integration/constraints.md +49 -4
  15. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  16. package/templates/docs/00-integration/display-logic-and-navigation.md +49 -0
  17. package/templates/docs/00-integration/end-page-logic.md +64 -0
  18. package/templates/docs/00-integration/partial-save-and-recovery.md +74 -0
  19. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  20. package/templates/docs/00-integration/progress.md +61 -0
  21. package/templates/docs/00-integration/question-display-variants.md +65 -0
  22. package/templates/docs/00-integration/question-numbering.md +51 -0
  23. package/templates/docs/00-integration/question-type-sdk-matrix.md +242 -0
  24. package/templates/docs/00-integration/setup.md +84 -0
  25. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  26. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  27. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  28. package/templates/docs/01-components/01-survey-page.md +76 -54
  29. package/templates/docs/01-components/02-question.md +61 -58
  30. package/templates/docs/01-components/03-rating-scale.md +11 -1
  31. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  32. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  33. package/templates/docs/01-components/06-likert-matrix-scale.md +12 -3
  34. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  35. package/templates/docs/01-components/08-file-upload-scale.md +10 -1
  36. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  37. package/templates/docs/01-components/11-progress-bar.md +10 -4
  38. package/templates/docs/01-components/13-matrix-dropdown.md +3 -1
  39. package/templates/docs/01-components/14-intro-page.md +34 -0
  40. package/templates/docs/01-components/15-end-page.md +31 -0
  41. package/templates/docs/01-components/16-paused-page.md +27 -0
  42. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  43. package/templates/docs/01-components/18-rank-order-scale.md +100 -0
  44. package/templates/docs/01-components/README.md +42 -36
  45. package/templates/docs/02-reference/config-field-index.md +141 -139
  46. package/templates/docs/02-reference/exports.md +149 -0
  47. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  48. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  49. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  50. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  51. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  52. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  53. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  54. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  55. package/templates/docs/02-reference/question-types/11-file-upload.md +13 -2
  56. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  57. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  58. package/templates/docs/02-reference/question-types/README.md +86 -47
  59. package/templates/docs/02-reference/routing-table.md +33 -18
  60. package/templates/docs/02-reference/value-derivation.md +63 -0
  61. package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
  62. package/templates/docs/03-ui-specs/01-rating.md +12 -54
  63. package/templates/docs/03-ui-specs/02-radio.md +21 -33
  64. package/templates/docs/03-ui-specs/03-text.md +2 -2
  65. package/templates/docs/03-ui-specs/04-csat.md +38 -33
  66. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  67. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  68. package/templates/docs/03-ui-specs/07-matrix-cfm.md +24 -21
  69. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +17 -45
  70. package/templates/docs/03-ui-specs/09-slider-matrix.md +1 -1
  71. package/templates/docs/03-ui-specs/10-file-upload.md +1 -1
  72. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  73. package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
  74. package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
  75. package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
  76. package/templates/docs/03-ui-specs/README.md +13 -13
  77. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +2 -2
  78. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  79. package/templates/docs/MANIFEST.json +451 -69
  80. package/templates/docs/index.md +80 -29
  81. package/templates/docs/templates/Question.tsx +221 -154
  82. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  83. package/templates/docs/templates/heatmapCoords.ts +58 -0
  84. package/templates/docs/templates/implementation_plan.md +108 -69
  85. package/templates/docs/templates/survey-inventory.schema.json +72 -0
  86. package/templates/docs/templates/verify-agent-build.sh +53 -0
  87. package/templates/docs/01-components/04-csat-scale.md +0 -40
  88. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -1,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
 
@@ -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
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
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,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`
@@ -1,4 +1,4 @@
1
- # UI Spec: File Upload (`type: 'file_upload'`)
1
+ # UI Spec: File Upload (`type: 'FILE_UPLOAD'`)
2
2
 
3
3
  > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/FileUploadScale.tsx`
4
4
  > **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
@@ -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
@@ -0,0 +1,73 @@
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(HeatmapAnswer | undefined)
62
+ // HeatmapAnswer = Array<{ id: string; x: number; y: number }>
63
+ // x, y normalized 0–1 relative to rendered image bounds
64
+ ```
65
+
66
+ ## Agent Checklist
67
+
68
+ - [ ] Click handler on `<img>` element (not overlay) for precise coordinates
69
+ - [ ] `inline-block` wrapper so spots align with image bounds
70
+ - [ ] Toggle-remove within 14px hit radius
71
+ - [ ] Wait for `onLoad` before accepting clicks
72
+ - [ ] Percentage-positioned spots (survives resize)
73
+ - [ ] Do NOT render region overlays on respondent UI
@@ -0,0 +1,59 @@
1
+ # Rank Order UI Spec (`RANK_ORDER`)
2
+
3
+ > **Reference:** `02-reference/question-types/13-rank-order.md`
4
+ > **Blueprint:** `01-components/18-rank-order-scale.md`
5
+
6
+ ## Interaction Modes
7
+
8
+ ### Dropdown (`interactionMode: 'dropdown'`)
9
+
10
+ | Element | Behavior |
11
+ |---------|----------|
12
+ | Rank select | `-` (unassigned) or `1..N` per option row |
13
+ | Duplicate ranks | Selecting rank R on option A clears R from any other option |
14
+ | Layout | Rank select on left, option content on right |
15
+
16
+ ### Drag and Drop (`interactionMode: 'dragAndDrop'`)
17
+
18
+ | Element | Behavior |
19
+ |---------|----------|
20
+ | Drag handle | Six-dot grip on left; pointer + keyboard sensors |
21
+ | Rank badge | Circular badge showing 1-based position |
22
+ | Reorder | List order determines rank; smooth transform during drag |
23
+ | Up/Down buttons | Keyboard fallback on right side of each row |
24
+
25
+ ## Option Display Variants
26
+
27
+ | `optionDisplay` | Render |
28
+ |-----------------|--------|
29
+ | `textOnly` | Full-width text label only |
30
+ | `imageOnly` | Large square thumbnail only (no label) |
31
+ | `textAndImage` | Thumbnail + text label side by side |
32
+
33
+ Use `dangerouslySetInnerHTML` for option labels (HTML from builder).
34
+
35
+ ## Validation UX
36
+
37
+ - Required + `requireRankAll`: every visible option must have a unique rank from 1..N
38
+ - Required + not `requireRankAll`: at least one visible option ranked
39
+ - SDK surfaces `validationError` on the question shell — show the standard error banner
40
+
41
+ ## Styling
42
+
43
+ Match MCQ card styling:
44
+
45
+ - Border: `#e5e5e5` default, `#e20074` when actively ranked (optional highlight)
46
+ - Background hover: `hover:bg-gray-50/50`
47
+ - Rank badge: `#fdf2f8` background, `#e20074` text
48
+
49
+ ## Accessibility
50
+
51
+ - Each rank select: `aria-label="Rank for {option label}"`
52
+ - Drag handle: `aria-label="Drag to reorder {option label}"`
53
+ - Up/Down: disabled on first/last item
54
+
55
+ ## Do Not
56
+
57
+ - Store ranks in local component state without syncing to `onSelect`
58
+ - Submit option labels — submit rank numbers via SDK formatter only
59
+ - Hardcode logic operators in the client — all conditions are SDK-evaluated
@@ -1,7 +1,7 @@
1
1
  # UI Specifications — Reference Implementation Guide
2
2
 
3
- > **For npm installs:** implement from these specs + [`templates/Question.tsx`](../templates/Question.tsx). The monorepo path `apps/client/src/components/` is **not** available in client projects.
4
- > **For monorepo devs:** cross-check against [`apps/client/src/components/`](../../../../../../apps/client/src/components/).
3
+ > **For npm installs:** implement from these specs + [`templates/Question.tsx`](../templates/Question.tsx).
4
+ > **For monorepo devs:** Reference client at `apps/client/src/components/` aligns with SDK types — trust `03-ui-specs/`, `templates/Question.tsx`, and exported types.
5
5
  > Read the spec for **each question type present** in the fetched survey before writing React code.
6
6
 
7
7
  ## How to Read a UI Spec
@@ -38,14 +38,14 @@ Each file in this folder follows the same structure:
38
38
  | Spec file | Reference component | `question.type` |
39
39
  |-----------|---------------------|-----------------|
40
40
  | [00-question-shell.md](00-question-shell.md) | `Question.tsx` | All (wrapper) |
41
- | [01-rating.md](01-rating.md) | `RatingScale.tsx` | `rating` |
42
- | [02-radio.md](02-radio.md) | `Question.tsx` inline | `radio` |
43
- | [03-text.md](03-text.md) | `Question.tsx` inline | `text` |
44
- | [04-csat.md](04-csat.md) | `CsatScale.tsx` | `csat` |
45
- | [05-rating-scale.md](05-rating-scale.md) | *(gap — reuse CsatScale patterns)* | `rating_scale` |
46
- | [06-slider.md](06-slider.md) | *(gap — reuse CustomSliderTrack)* | `slider` |
47
- | [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `matrix` + CFM |
48
- | [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | CSAT/RATING matrix |
41
+ | [01-rating.md](01-rating.md) | `RatingScale.tsx` | `nps` |
42
+ | [02-radio.md](02-radio.md) | `Question.tsx` inline | `mcq` |
43
+ | [03-text.md](03-text.md) | `Question.tsx` inline | `text_input` |
44
+ | [04-csat.md](04-csat.md) | `CsatMatrixScale.tsx` | `csat` |
45
+ | [05-rating-scale.md](05-rating-scale.md) | `RatingScale.tsx` | `star_rating` |
46
+ | [06-slider.md](06-slider.md) | `CustomSliderTrack.tsx` | `slider` |
47
+ | [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `cfm_matrix` |
48
+ | [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | `rating_matrix` |
49
49
  | [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
50
50
  | [10-file-upload.md](10-file-upload.md) | `FileUploadScale.tsx` | `file_upload` |
51
51
  | [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `text_and_media` |
@@ -55,7 +55,7 @@ Each file in this folder follows the same structure:
55
55
 
56
56
  ## NPS Traffic-Light Colors
57
57
 
58
- Use `option.color` from SDK — never invent colors:
58
+ Use accent colors from SDK options — never invent colors for NPS:
59
59
 
60
60
  | Value | Hex |
61
61
  |-------|-----|
@@ -73,8 +73,8 @@ Use `option.color` from SDK — never invent colors:
73
73
 
74
74
  ## When to Read
75
75
 
76
- After Phase 3 in [`index.md`](../index.md), for each question in `state.currentQuestions`:
76
+ After Phase 3 in [`index.md`](../index.md), for each question in the fetched survey:
77
77
 
78
78
  1. Read [`02-reference/question-types/`](../02-reference/question-types/) for data shape
79
- 2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `uiSpecs`)
79
+ 2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `questionTypes.{type}.uiSpec`)
80
80
  3. Cross-check [`config-field-index.md`](../02-reference/config-field-index.md)
@@ -23,7 +23,7 @@
23
23
  | `disabled` | Gray theme, opacity 0.5, no interaction |
24
24
  | `displayValues` | Reserved — tooltip always shows on hover/drag in client |
25
25
  | `hasSelectedValue` | Reserved |
26
- | `sliderType: 'graphics'` | Emoji thumb via `getEmojiForIndex` |
26
+ | `sliderType: 'graphics'` | Emoji thumb via client-side react-icons |
27
27
  | `ticks` | Discrete tick count + emoji index scale |
28
28
  | `reverseScaleOrder` | Flip emoji index calculation |
29
29
 
@@ -41,7 +41,7 @@
41
41
  ```typescript
42
42
  const idx = Math.round(((value - min) / (max - min)) * ((ticks || 10) - 1));
43
43
  const displayIdx = reverseScaleOrder ? (ticks || 10) - 1 - idx : idx;
44
- getEmojiForIndex(displayIdx, ticks || 10)
44
+ // Map displayIdx to react-icons emoji — proportional mapping is client-implemented (not an SDK export)
45
45
  ```
46
46
 
47
47
  ## Agent Checklist
@@ -30,6 +30,11 @@ if (val === null) {
30
30
  // When null selected, non-null options disabled
31
31
  ```
32
32
 
33
+ ## Props
34
+
35
+ - `options: ScaleColumn[]` — matrix scale columns (`id`, `label`)
36
+ - `value` / `onChange` — stored values use `matrixColumnStoredValue(col)` → column **`id`**
37
+
33
38
  ## Agent Checklist
34
39
 
35
40
  - [ ] Click-outside closes panel