@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4

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 (113) hide show
  1. package/dist/cli/index.js +140 -33
  2. package/dist/cli/index.mjs +140 -33
  3. package/dist/index.js +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/package.json +8 -1
  6. package/postinstall.js +8 -5
  7. package/templates/AGENT.md +62 -17
  8. package/templates/deploy-wizard-to-s3.sh +52 -0
  9. package/templates/docs/00-integration/agent-execution-flow.md +281 -0
  10. package/templates/docs/00-integration/agent-operating-contract.md +260 -0
  11. package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
  12. package/templates/docs/00-integration/apply-ui-config.md +365 -0
  13. package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
  14. package/templates/docs/00-integration/client-integration-guide.md +10 -8
  15. package/templates/docs/00-integration/client-lib-folder.md +115 -0
  16. package/templates/docs/00-integration/component-checklist.md +90 -54
  17. package/templates/docs/00-integration/constraints.md +13 -5
  18. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
  19. package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
  20. package/templates/docs/00-integration/file-upload-aws.md +116 -0
  21. package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
  22. package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
  23. package/templates/docs/00-integration/progress.md +4 -2
  24. package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
  25. package/templates/docs/00-integration/setup.md +15 -3
  26. package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
  27. package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
  28. package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
  29. package/templates/docs/00-integration/useSurveySDK.md +35 -1
  30. package/templates/docs/00-integration/wizard-api.md +134 -0
  31. package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
  32. package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
  33. package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
  34. package/templates/docs/01-components/01-survey-page.md +18 -8
  35. package/templates/docs/01-components/02-question.md +2 -2
  36. package/templates/docs/01-components/03-rating-scale.md +12 -18
  37. package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
  38. package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
  39. package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
  40. package/templates/docs/01-components/08-file-upload-scale.md +15 -52
  41. package/templates/docs/01-components/09-custom-slider-track.md +15 -25
  42. package/templates/docs/01-components/10-header-footer.md +23 -4
  43. package/templates/docs/01-components/11-progress-bar.md +14 -6
  44. package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
  45. package/templates/docs/01-components/17-heatmap-scale.md +4 -4
  46. package/templates/docs/01-components/18-rank-order-scale.md +24 -31
  47. package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
  48. package/templates/docs/01-components/README.md +2 -2
  49. package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
  50. package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
  51. package/templates/docs/02-reference/question-types/README.md +11 -1
  52. package/templates/docs/02-reference/routing-table.md +1 -0
  53. package/templates/docs/02-reference/value-derivation.md +1 -1
  54. package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
  55. package/templates/docs/03-ui-specs/01-rating.md +28 -2
  56. package/templates/docs/03-ui-specs/02-radio.md +94 -24
  57. package/templates/docs/03-ui-specs/03-text.md +12 -1
  58. package/templates/docs/03-ui-specs/04-csat.md +90 -26
  59. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  60. package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
  61. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
  62. package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
  63. package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
  64. package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
  65. package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
  66. package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
  67. package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
  68. package/templates/docs/03-ui-specs/README.md +11 -7
  69. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
  70. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
  71. package/templates/docs/MANIFEST.json +187 -6
  72. package/templates/docs/index.md +116 -116
  73. package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
  74. package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
  75. package/templates/docs/templates/FileUploadScale.tsx +262 -0
  76. package/templates/docs/templates/HeatmapScale.tsx +114 -0
  77. package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
  78. package/templates/docs/templates/MatrixDropdown.tsx +216 -0
  79. package/templates/docs/templates/Question.tsx +3 -0
  80. package/templates/docs/templates/RankOrderScale.tsx +353 -0
  81. package/templates/docs/templates/RatingScale.tsx +129 -0
  82. package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
  83. package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
  84. package/templates/docs/templates/customFieldValues.ts +36 -0
  85. package/templates/docs/templates/implementation_plan.md +75 -8
  86. package/templates/docs/templates/labelStyles.ts +33 -0
  87. package/templates/docs/templates/selectionStyles.ts +100 -0
  88. package/templates/docs/templates/survey-inventory.schema.json +28 -23
  89. package/templates/docs/templates/surveyUiIcons.tsx +63 -0
  90. package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
  91. package/templates/docs/templates/verify-agent-build.sh +151 -0
  92. package/templates/preview-harness/preview-bridge.inline.js +201 -0
  93. package/templates/preview-harness/previewPages.js +108 -0
  94. package/templates/preview-harness/previewPages.ts +298 -0
  95. package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
  96. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
  97. package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
  98. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
  99. package/templates/preview-harness/vite-app/src/globals.css +16 -0
  100. package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
  101. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
  102. package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
  103. package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
  104. package/templates/previewBridge.ts +153 -0
  105. package/templates/survey-theme.css +295 -0
  106. package/templates/survey-ui-config.schema.json +213 -0
  107. package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
  108. package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
  109. package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
  110. package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
  111. package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
  112. package/templates/wizard-dist/index.html +20 -0
  113. package/templates/wizard.html +13 -1129
@@ -1,7 +1,7 @@
1
1
  # UI Spec: Survey Chrome
2
2
 
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`
3
+ > **Reference:** `SurveyPage.tsx`, `SurveyStickyChrome.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`, `19-survey-sticky-chrome.md`, `12-language-selector.md`, `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`
5
5
 
6
6
  ## SurveyPage — Phase Router
7
7
 
@@ -21,9 +21,8 @@
21
21
  ## Active Layout
22
22
 
23
23
  ```
24
- Header
24
+ SurveyStickyChrome (sticky top-0 — Header + ProgressBar)
25
25
  main (max-w-4xl)
26
- ProgressBar(progressPercentage)
27
26
  LanguageSelector (hidden if ≤1 language)
28
27
  Questions (space-y-12)
29
28
  Submit error (if any)
@@ -31,6 +30,25 @@ main (max-w-4xl)
31
30
  Footer
32
31
  ```
33
32
 
33
+ ### Sticky chrome (required on content phase)
34
+
35
+ Long pages push the last question below the fold. Wrap **Header + ProgressBar** in one sticky container so progress stays visible while answering.
36
+
37
+ ```tsx
38
+ <div className="sticky top-0 z-30 bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
39
+ <Header embedded />
40
+ <div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
41
+ <ProgressBar progressPercentage={state.progressPercentage} />
42
+ </div>
43
+ </div>
44
+ ```
45
+
46
+ **Spacing:** `pt-4` (16px) between logo block and progress track; `pb-5` (20px) below the track before scrollable content. Do not flush the bar against the logo square.
47
+
48
+ See [`01-components/19-survey-sticky-chrome.md`](../01-components/19-survey-sticky-chrome.md). Copy [`templates/SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx).
49
+
50
+ Use standalone `Header` (no sticky wrapper) on intro, end, paused, loading, and terminal phases.
51
+
34
52
  ## Navigation Labels
35
53
 
36
54
  German when `survey.language.startsWith('de')`:
@@ -51,10 +69,53 @@ German when `survey.language.startsWith('de')`:
51
69
 
52
70
  Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-integration/progress.md`).
53
71
 
72
+ Render inside **`SurveyStickyChrome`** (sticky header + progress) on the content phase — not inside scrollable `<main>`. Clamp width to `0–100`.
73
+
74
+ | Token | Value | Role |
75
+ |-------|-------|------|
76
+ | Gap above track | `pt-4` (16px) | Space between logo block and progress bar |
77
+ | Gap below track | `pb-5` (20px) | Breathing room before question content |
78
+ | Track height | `h-3` (12px) | Full-width rounded pill |
79
+
54
80
  ## LanguageSelector
55
81
 
56
82
  - Hidden when `languages.length <= 1`
57
83
  - Focus ring: `#2563eb`
84
+ - Pass `language` in `useMemo` hook options — SDK remaps survey client-side (no refetch)
85
+
86
+ ## Question numbering
87
+
88
+ Render ordinals from SDK on content pages:
89
+
90
+ ```tsx
91
+ {question.questionNumber != null && (
92
+ <span className="mr-1 font-semibold">{question.questionNumber}.</span>
93
+ )}
94
+ ```
95
+
96
+ See [`question-numbering.md`](../00-integration/question-numbering.md).
97
+
98
+ ## Special pages
99
+
100
+ | Phase | Component | Action |
101
+ |-------|-----------|--------|
102
+ | `intro` | `IntroPage` | `page={state.specialPage}` · `onStart={() => onAction({ type: 'START' })}` |
103
+ | `paused` | `PausedPage` | `page={state.specialPage}` · read-only, no nav |
104
+ | `end` | `EndPage` | `page={state.specialPage}` · terminal thank-you |
105
+
106
+ ## Terminal phases
107
+
108
+ Render a simple message (no question nav) for:
109
+
110
+ - `submitted` — already completed
111
+ - `expired` — survey expired
112
+ - `quota_fulfilled` — quota reached
113
+
114
+ Keep Header/Footer chrome consistent across all phases.
115
+
116
+ ## Submit loading
117
+
118
+ Disable primary Next/Submit when `submitSurveyResults.isLoading`; label "Submitting…" / "Wird gesendet…".
58
119
 
59
120
  ## Agent Checklist
60
121
 
@@ -62,5 +123,31 @@ Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-inte
62
123
  - [ ] `onAction({ type: 'START' })` on intro
63
124
  - [ ] `onAction({ type: 'CHANGE' })` wired per question with `allAnswers` / `allQuestions`
64
125
  - [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
65
- - [ ] ProgressBar receives `state.progressPercentage`
126
+ - [ ] ProgressBar receives `state.progressPercentage` inside sticky chrome (`SurveyStickyChrome`)
127
+ - [ ] Sticky `top-0 z-30` wrapper keeps progress visible on long pages
128
+ - [ ] `pt-4` / `pb-5` spacing between logo, progress track, and question content
66
129
  - [ ] Header/Footer on every state branch
130
+ - [ ] `questionNumber` prefix when present on content questions
131
+ - [ ] Intro / Paused / End / terminal phase branches
132
+ - [ ] Submit button disabled while `submitSurveyResults.isLoading`
133
+
134
+ ## Wizard customization (Phase 5 — required)
135
+
136
+ > Full chrome contract: [`wizard-preview-build-guide.md`](../00-integration/wizard-preview-build-guide.md).
137
+
138
+ | Element | `data-cfm-*` / CSS vars |
139
+ |---------|-------------------------|
140
+ | Logo | `data-cfm-logo` or `data-cfm-logo-text` |
141
+ | Company | `data-cfm-company` |
142
+ | Survey title | `data-cfm-survey-title` |
143
+ | Thank you | `data-cfm-thank-you` |
144
+ | Next / Back | `data-cfm-btn-next`, `data-cfm-btn-back` |
145
+ | Footer | `data-cfm-copyright`, `data-cfm-footer-links` |
146
+ | Progress | `--cfm-progress-track`, `--cfm-progress-fill` |
147
+ | Header bg | `--cfm-header-bg`, `--cfm-header-text` |
148
+
149
+ ### Wizard checklist
150
+
151
+ - [ ] All editable chrome strings use `data-cfm-*` attributes
152
+ - [ ] Buttons and progress use `var(--cfm-*)` — not hardcoded Telekom hex
153
+ - [ ] Logo: text/CSS **or** local `public/` image — never external URL (see `constraints.md` §4)
@@ -1,6 +1,6 @@
1
1
  # UI Spec: Heatmap (`type: 'HEATMAP'`)
2
2
 
3
- > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/HeatmapScale.tsx`
3
+ > **Implement from this spec.** Portable template: `docs/templates/HeatmapScale.tsx`
4
4
  > **SDK type:** `HeatmapQuestion` — `fetchSurvey/types/heatmap.ts`
5
5
  > **Component blueprint:** `01-components/17-heatmap-scale.md`
6
6
 
@@ -58,11 +58,18 @@ Use singular "spot" when `maxClicksAllowed === 1`.
58
58
  ## Answer Mutation
59
59
 
60
60
  ```typescript
61
- onSelect(HeatmapAnswer | undefined)
62
- // HeatmapAnswer = Array<{ id: string; x: number; y: number }>
61
+ onSelect(HeatmapClickPoint[] | undefined)
62
+ // HeatmapClickPoint = { id: string; x: number; y: number }
63
63
  // x, y normalized 0–1 relative to rendered image bounds
64
+ onAction({ type: 'CHANGE', payload: { questionId, answerValue: spots } })
64
65
  ```
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
+
66
73
  ## Agent Checklist
67
74
 
68
75
  - [ ] Click handler on `<img>` element (not overlay) for precise coordinates
@@ -71,3 +78,14 @@ onSelect(HeatmapAnswer | undefined)
71
78
  - [ ] Wait for `onLoad` before accepting clicks
72
79
  - [ ] Percentage-positioned spots (survives resize)
73
80
  - [ ] Do NOT render region overlays on respondent UI
81
+
82
+ ## Wizard customization (Phase 5 — required)
83
+
84
+ | Control | CSS vars | Apply in |
85
+ |---------|----------|----------|
86
+ | Spot color | `--cfm-heatmap-spot-color` | Click markers |
87
+ | Spot border | `--cfm-heatmap-spot-border` | Marker outline |
88
+
89
+ ### Wizard checklist
90
+
91
+ - [ ] Spot markers use `--cfm-heatmap-spot-*` vars
@@ -2,58 +2,138 @@
2
2
 
3
3
  > **Reference:** `02-reference/question-types/13-rank-order.md`
4
4
  > **Blueprint:** `01-components/18-rank-order-scale.md`
5
-
6
- ## Interaction Modes
5
+ > **Portable template:** copy [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx)
6
+
7
+ ## Config gate
8
+
9
+ | Field | Values | UI branch |
10
+ |-------|--------|-----------|
11
+ | `interactionMode` | `'dropdown'` | Rank `<select>` per row |
12
+ | `interactionMode` | `'dragAndDrop'` | `@dnd-kit` sortable list |
13
+ | `optionDisplay` | `textOnly` / `imageOnly` / `textAndImage` | Option content layout |
14
+ | `requireRankAll` | boolean | Validation (SDK) |
15
+ | `shuffleOptions` | boolean | **SDK shuffles at fetch** — render `question.options` as-is; do not re-shuffle client-side |
16
+
17
+ ## Dependencies
18
+
19
+ ```bash
20
+ npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
21
+ ```
22
+
23
+ Next.js: template includes `'use client'`. Optional in `Question.tsx`:
24
+
25
+ ```tsx
26
+ const RankOrderScale = dynamic(() => import('./RankOrderScale').then(m => ({ default: m.RankOrderScale })), {
27
+ ssr: false,
28
+ loading: () => <p>Loading rank order…</p>,
29
+ });
30
+ ```
31
+
32
+ ## SDK wiring
33
+
34
+ ```typescript
35
+ import {
36
+ getVisibleRankOrderOptionsForAnswers,
37
+ normalizeRankOrderAnswers,
38
+ assignRankWithoutDuplicates,
39
+ buildRankOrderFromOrderedOptionIds,
40
+ getOrderedOptionIdsFromRanks,
41
+ } from '@explorer02/cfm-survey-sdk';
42
+
43
+ // Question.tsx — filter options + normalize answer
44
+ const rankOrderQuestion = {
45
+ ...question,
46
+ options: getVisibleRankOrderOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues),
47
+ };
48
+ <RankOrderScale
49
+ question={rankOrderQuestion}
50
+ selectedValue={normalizeRankOrderAnswers(selectedValue)}
51
+ onSelect={onSelect}
52
+ />
53
+ ```
54
+
55
+ Answer shape: `RankOrderAnswers` = `Record<optionId, rank>` (1-based).
56
+
57
+ ## Interaction modes
7
58
 
8
59
  ### Dropdown (`interactionMode: 'dropdown'`)
9
60
 
10
61
  | Element | Behavior |
11
62
  |---------|----------|
12
63
  | 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 |
64
+ | Duplicate ranks | `assignRankWithoutDuplicates` clears rank R from other options |
65
+ | Layout | Rank select left, option content right |
15
66
 
16
- ### Drag and Drop (`interactionMode: 'dragAndDrop'`)
67
+ ### Drag and drop (`interactionMode: 'dragAndDrop'`)
17
68
 
18
69
  | Element | Behavior |
19
70
  |---------|----------|
20
- | Drag handle | Six-dot grip on left; pointer + keyboard sensors |
71
+ | Drag handle | Six-dot grip; `PointerSensor` + `KeyboardSensor` |
21
72
  | 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 |
73
+ | Reorder | List order `buildRankOrderFromOrderedOptionIds(orderedIds)` |
74
+ | Up/Down | Keyboard fallback; disabled on first/last |
75
+
76
+ ## Implementation Recipe — dnd-kit
77
+
78
+ ```typescript
79
+ const sensors = useSensors(
80
+ useSensor(PointerSensor),
81
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
82
+ );
83
+
84
+ const handleDragEnd = (event: DragEndEvent) => {
85
+ const { active, over } = event;
86
+ if (!over || active.id === over.id) return;
87
+ const oldIndex = orderedOptionIds.indexOf(String(active.id));
88
+ const newIndex = orderedOptionIds.indexOf(String(over.id));
89
+ if (oldIndex === -1 || newIndex === -1) return;
90
+ onSelect(buildRankOrderFromOrderedOptionIds(arrayMove(orderedOptionIds, oldIndex, newIndex)));
91
+ };
92
+ ```
93
+
94
+ Wrap list in `<DndContext collisionDetection={closestCenter}>` + `<SortableContext strategy={verticalListSortingStrategy}>`.
24
95
 
25
- ## Option Display Variants
96
+ Copy full implementation from [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx).
97
+
98
+ ## Option display
26
99
 
27
100
  | `optionDisplay` | Render |
28
101
  |-----------------|--------|
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).
102
+ | `textOnly` | Label via `dangerouslySetInnerHTML` |
103
+ | `imageOnly` | Square thumbnail; prefer `option.imageUrl` over `previewImageUrl` |
104
+ | `textAndImage` | Thumbnail + label side by side |
34
105
 
35
106
  ## Validation UX
36
107
 
37
- - Required + `requireRankAll`: every visible option must have a unique rank from 1..N
108
+ - Required + `requireRankAll`: every visible option unique rank 1..N
38
109
  - Required + not `requireRankAll`: at least one visible option ranked
39
- - SDK surfaces `validationError` on the question shell — show the standard error banner
110
+ - Show `validationError` from question shell
40
111
 
41
112
  ## Styling
42
113
 
43
- Match MCQ card styling:
114
+ - Border `#e5e5e5`, optional `#e20074` when ranked
115
+ - Rank badge: `#fdf2f8` bg, `#e20074` text
116
+
117
+ ## Logic
118
+
119
+ RANK_ORDER operators (ranked as/above/below) — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
120
+
121
+ ## Agent checklist
44
122
 
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
123
+ - [ ] Copied `templates/RankOrderScale.tsx` (or equivalent dnd-kit + dropdown)
124
+ - [ ] `@dnd-kit/*` in package.json when survey has RANK_ORDER
125
+ - [ ] `getVisibleRankOrderOptionsForAnswers` + `normalizeRankOrderAnswers`
126
+ - [ ] `option.imageUrl` before `previewImageUrl`
127
+ - [ ] No client-side re-shuffle when `shuffleOptions` is true
128
+ - [ ] No stub placeholder text
48
129
 
49
- ## Accessibility
130
+ ## Wizard customization (Phase 5 — required)
50
131
 
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
132
+ | Control | CSS vars | Apply in |
133
+ |---------|----------|----------|
134
+ | Rank badge | `--cfm-rank-badge-bg`, `--cfm-rank-badge-color` | Drag handle / rank number |
135
+ | Option border | `--cfm-rank-option-border` | List item cards |
54
136
 
55
- ## Do Not
137
+ ### Wizard checklist
56
138
 
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
139
+ - [ ] Rank badges and option borders use `--cfm-rank-*` vars
@@ -1,9 +1,11 @@
1
1
  # UI Specifications — Reference Implementation Guide
2
2
 
3
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 typestrust `03-ui-specs/`, `templates/Question.tsx`, and exported types.
4
+ > **Shared lib utilities** (NPS colors, custom fields, heatmap coords) live in [`templates/`](../templates/) see [`client-lib-folder.md`](../00-integration/client-lib-folder.md), not in this ui-specs folder.
5
5
  > Read the spec for **each question type present** in the fetched survey before writing React code.
6
6
 
7
+ > **Wizard customization:** When the client may use the web wizard, hardcoded hex in § Design Tokens below are **defaults only**. Wire `var(--cfm-*)` per [`wizard-question-type-styling.md`](../00-integration/wizard-question-type-styling.md) — especially `--cfm-matrix-selected` (cell annulus) and `--cfm-input-focus-ring` (outer border + inner dot). Portable scale templates in `docs/templates/` are layout/logic baselines; extend with `selectionStyles.ts` + `labelStyles.ts` for live preview.
8
+
7
9
  ## How to Read a UI Spec
8
10
 
9
11
  Each file in this folder follows the same structure:
@@ -38,17 +40,19 @@ Each file in this folder follows the same structure:
38
40
  | Spec file | Reference component | `question.type` |
39
41
  |-----------|---------------------|-----------------|
40
42
  | [00-question-shell.md](00-question-shell.md) | `Question.tsx` | All (wrapper) |
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` |
43
+ | [01-rating.md](01-rating.md) | `RatingScale.tsx` | `NPS_SCALE` |
44
+ | [02-radio.md](02-radio.md) | `Question.tsx` inline | `MCQ` |
45
+ | [03-text.md](03-text.md) | `Question.tsx` inline | `TEXTFIELD` |
46
+ | [04-csat.md](04-csat.md) | `CsatMatrixScale.tsx` | `CSAT_MATRIX` |
45
47
  | [05-rating-scale.md](05-rating-scale.md) | `RatingScale.tsx` | `star_rating` |
46
48
  | [06-slider.md](06-slider.md) | `CustomSliderTrack.tsx` | `slider` |
47
49
  | [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `cfm_matrix` |
48
50
  | [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | `rating_matrix` |
49
51
  | [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
50
52
  | [10-file-upload.md](10-file-upload.md) | `FileUploadScale.tsx` | `file_upload` |
51
- | [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `text_and_media` |
53
+ | [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `TEXT_AND_MEDIA` |
54
+ | [13-heatmap.md](13-heatmap.md) | `HeatmapScale.tsx` | `HEATMAP` |
55
+ | [14-rank-order.md](14-rank-order.md) | `RankOrderScale.tsx` | `RANK_ORDER` |
52
56
  | [12-survey-chrome.md](12-survey-chrome.md) | `SurveyPage.tsx` + chrome | Shell |
53
57
  | [shared/custom-slider-track.md](shared/custom-slider-track.md) | `CustomSliderTrack.tsx` | Primitive |
54
58
  | [shared/matrix-dropdown.md](shared/matrix-dropdown.md) | `MatrixDropdown.tsx` | Primitive |
@@ -73,7 +77,7 @@ Use accent colors from SDK options — never invent colors for NPS:
73
77
 
74
78
  ## When to Read
75
79
 
76
- After Phase 3 in [`index.md`](../index.md), for each question in the fetched survey:
80
+ After Phase 3 in [`index.md`](../index.md) and [`agent-execution-flow.md`](../00-integration/agent-execution-flow.md), for each question in the fetched survey:
77
81
 
78
82
  1. Read [`02-reference/question-types/`](../02-reference/question-types/) for data shape
79
83
  2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `questionTypes.{type}.uiSpec`)
@@ -1,52 +1,45 @@
1
1
  # UI Spec: Custom Slider Track (shared primitive)
2
2
 
3
- > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CustomSliderTrack.tsx`
4
- > **Used by:** `SliderMatrixScale.tsx`, `CsatMatrixScale` (graphics), standalone `slider` (gap)
3
+ > **Portable template:** copy [`templates/CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`templates/surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx)
4
+ > **Used by:** `SliderMatrixScale`, `CsatMatrixScale` (graphics), matrix `displayStyle: graphics`
5
+
6
+ ## Scale column labels (Day 19 — CRITICAL)
7
+
8
+ Matrix rows must pass **`tickLabels` from `scaleColumns[].label`** (filtered to visible columns):
9
+
10
+ ```typescript
11
+ const tickLabels = visibleScaleColumns.map(col => col.label);
12
+ <CustomSliderTrack tickLabels={tickLabels} ticks={tickLabels.length} ... />
13
+ ```
14
+
15
+ Tooltip shows stripped HTML from `tickLabels[tickIndex]`; without labels, tooltips fall back to numeric value only.
5
16
 
6
17
  ## Layer Architecture
7
18
 
8
19
  ```
9
- 1. Gray inactive track (#e5e7eb, 4px height)
10
- 2. Magenta fill bar (width = percentage%, #e20074 or #9ca3af if disabled)
11
- 3. Tick marks (1px × 8px at ticks positions)
12
- 4. Invisible <input type="range"> overlay (opacity 0, full hit area)
13
- 5. Visual thumb: circle OR emoji (sliderType === 'graphics')
14
- 6. Tooltip bubble on hover/drag (#1f2937 bg, rounded value)
20
+ 1. Gray inactive track (#e5e7eb, 4px)
21
+ 2. Magenta fill bar (percentage%, #e20074 or #9ca3af if disabled)
22
+ 3. Tick marks at column positions
23
+ 4. Invisible <input type="range"> overlay
24
+ 5. Thumb: circle OR emoji (sliderType === 'graphics')
25
+ 6. Tooltip on hover/drag (#1f2937)
15
26
  ```
16
27
 
17
28
  ## Props
18
29
 
19
30
  | Prop | Role |
20
31
  |------|------|
21
- | `min`, `max`, `htmlStep` | Range input bounds |
32
+ | `min`, `max`, `htmlStep` | Range bounds |
22
33
  | `value` | Current position |
23
- | `disabled` | Gray theme, opacity 0.5, no interaction |
24
- | `displayValues` | Reserved tooltip always shows on hover/drag in client |
25
- | `hasSelectedValue` | Reserved |
26
- | `sliderType: 'graphics'` | Emoji thumb via client-side react-icons |
27
- | `ticks` | Discrete tick count + emoji index scale |
28
- | `reverseScaleOrder` | Flip emoji index calculation |
29
-
30
- ## Interactive States
31
-
32
- | State | Thumb | Tooltip |
33
- |-------|-------|---------|
34
- | Default | White circle, 2.5px magenta border | Hidden |
35
- | Hover/drag | Pink glow ring `rgba(226,0,116,0.2)` | Dark bubble with `Math.round(value)` |
36
- | Disabled | Gray border, opacity 0.5 | Hidden |
37
- | Graphics | Emoji at percentage position | Same tooltip |
38
-
39
- ## Emoji Index Formula
40
-
41
- ```typescript
42
- const idx = Math.round(((value - min) / (max - min)) * ((ticks || 10) - 1));
43
- const displayIdx = reverseScaleOrder ? (ticks || 10) - 1 - idx : idx;
44
- // Map displayIdx to react-icons emoji — proportional mapping is client-implemented (not an SDK export)
45
- ```
34
+ | `disabled` | Gray theme, opacity 0.5 |
35
+ | `sliderType: 'graphics'` | Emoji thumb via `surveyUiIcons.getEmojiForIndex` |
36
+ | `ticks` | Discrete tick count |
37
+ | **`tickLabels`** | **Column labels for tooltip + graphics index** |
38
+ | `reverseScaleOrder` | Flip emoji index |
46
39
 
47
40
  ## Agent Checklist
48
41
 
49
- - [ ] 4-layer overlay with invisible range input
42
+ - [ ] Copy portable template or match layer stack
43
+ - [ ] Always pass `tickLabels` from matrix `scaleColumns` when available
50
44
  - [ ] Tooltip on hover AND drag
51
- - [ ] Graphics mode emoji thumb
52
- - [ ] Transition disabled while dragging
45
+ - [ ] Graphics emoji thumb with `reverseScaleOrder` support
@@ -1,6 +1,6 @@
1
1
  # UI Spec: Matrix Dropdown (shared primitive)
2
2
 
3
- > **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/MatrixDropdown.tsx`
3
+ > **Implement from this spec.** Portable template: `docs/templates/MatrixDropdown.tsx`
4
4
  > **Used by:** `LikertMatrixScale`, `CsatMatrixScale` vertical list
5
5
 
6
6
  ## Modes