@explorer02/cfm-survey-sdk 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/templates/docs/01-components/01-survey-page.md +2 -0
  3. package/templates/docs/01-components/02-question.md +2 -0
  4. package/templates/docs/01-components/03-rating-scale.md +2 -0
  5. package/templates/docs/01-components/04-csat-scale.md +3 -1
  6. package/templates/docs/01-components/05-csat-matrix-scale.md +2 -0
  7. package/templates/docs/01-components/06-likert-matrix-scale.md +2 -0
  8. package/templates/docs/01-components/07-slider-matrix-scale.md +2 -0
  9. package/templates/docs/01-components/08-file-upload-scale.md +2 -0
  10. package/templates/docs/01-components/09-custom-slider-track.md +2 -0
  11. package/templates/docs/01-components/10-header-footer.md +3 -1
  12. package/templates/docs/01-components/11-progress-bar.md +2 -0
  13. package/templates/docs/01-components/12-language-selector.md +2 -0
  14. package/templates/docs/01-components/13-matrix-dropdown.md +2 -0
  15. package/templates/docs/01-components/README.md +2 -2
  16. package/templates/docs/02-reference/config-field-index.md +158 -0
  17. package/templates/docs/02-reference/routing-table.md +16 -16
  18. package/templates/docs/03-ui-specs/00-question-shell.md +89 -0
  19. package/templates/docs/03-ui-specs/01-rating.md +80 -0
  20. package/templates/docs/03-ui-specs/02-radio.md +57 -0
  21. package/templates/docs/03-ui-specs/03-text.md +51 -0
  22. package/templates/docs/03-ui-specs/04-csat.md +57 -0
  23. package/templates/docs/03-ui-specs/05-rating-scale.md +49 -0
  24. package/templates/docs/03-ui-specs/06-slider.md +42 -0
  25. package/templates/docs/03-ui-specs/07-matrix-cfm.md +66 -0
  26. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +65 -0
  27. package/templates/docs/03-ui-specs/09-slider-matrix.md +61 -0
  28. package/templates/docs/03-ui-specs/10-file-upload.md +54 -0
  29. package/templates/docs/03-ui-specs/11-text-and-media.md +49 -0
  30. package/templates/docs/03-ui-specs/12-survey-chrome.md +60 -0
  31. package/templates/docs/03-ui-specs/README.md +79 -0
  32. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +52 -0
  33. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +38 -0
  34. package/templates/docs/MANIFEST.json +22 -1
  35. package/templates/docs/index.md +14 -3
  36. package/templates/docs/templates/implementation_plan.md +13 -0
@@ -0,0 +1,57 @@
1
+ # UI Spec: CSAT (`type: 'csat'`)
2
+
3
+ > **Reference:** `apps/client/src/components/CsatScale.tsx`
4
+ > **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
5
+ > **Component blueprint:** `01-components/04-csat-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `options[]` | ✅ | Scale buttons or dropdown options |
12
+ | `buttonType` | ✅ partial | Branches below |
13
+ | `labelPosition` | ✅ | `'top'` → flex-col-reverse; `'hidden'` → no label |
14
+ | `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
15
+ | `hasNotApplicable` | ❌ | N/A option in options array still renders |
16
+ | `reverseScaleOrder` | ❌ | Document: reverse options before map |
17
+
18
+ ## Config Decision Tree
19
+
20
+ ```
21
+ csat
22
+ ├── buttonType === 'dropdown'
23
+ │ └── Native <select>, placeholder "Select an option"
24
+ │ └── Parse value 'null' → null
25
+ └── else (emoji | star | numbered | number | default)
26
+ └── flex flex-wrap gap-3 buttons
27
+ ├── emoji → getEmojiForIndex(index, scaleLength)
28
+ ├── star → CsatStarIcons filled/empty; labels hidden
29
+ ├── numbered/number → mono font value
30
+ └── default → bold value or 'N/A' if null
31
+ ```
32
+
33
+ ## Interactive States
34
+
35
+ | State | Border | Background | Transform |
36
+ |-------|--------|------------|-----------|
37
+ | Default | `border-gray-200` | `white` | scale-100 |
38
+ | Hover | `border-gray-300` | `hover:bg-gray-50` | — |
39
+ | Selected | `border-[#e20074]` | `#fdf2f8` | `scale-110` |
40
+ | Star mode | transparent | transparent | no border/bg on button |
41
+
42
+ ## Tooltips
43
+
44
+ Recommended: `title={option.label}` on each button. Not in reference client.
45
+
46
+ ## Answer Mutation
47
+
48
+ ```typescript
49
+ onSelect(string | number | null)
50
+ ```
51
+
52
+ ## Agent Checklist
53
+
54
+ - [ ] Dropdown branch with magenta focus ring
55
+ - [ ] Emoji/star/numbered branches per decision tree
56
+ - [ ] `labelPosition` controls label above/below/hidden
57
+ - [ ] Implement `reverseScaleOrder` and standalone `hasNotApplicable` (SDK gaps)
@@ -0,0 +1,49 @@
1
+ # UI Spec: Rating Scale (`type: 'rating_scale'`)
2
+
3
+ > **Reference:** ❌ Not routed in `Question.tsx` — reuse `CsatScale.tsx` patterns
4
+ > **SDK type:** `RatingScaleQuestion` — `fetchSurvey/types/ratingScale.ts`
5
+ > **Component blueprint:** `01-components/03-rating-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `options[]` | ❌ gap | Star/emoji buttons (no NPS colors) |
12
+ | `scaleStyle` | ❌ gap | `'star'` or `'emoji'` |
13
+ | `minLabel` / `midLabel` / `maxLabel` | ❌ gap | Same formulas as NPS in Question shell |
14
+ | `midLabelIndex` | ❌ gap | `(midLabelIndex / (options.length - 1)) * 100`% |
15
+ | `reverseScaleOrder` | ❌ gap | Reverse options before render |
16
+
17
+ ## Expected UI (from CsatScale + rating labels)
18
+
19
+ ```
20
+ [minLabel] [midLabel @ %] [maxLabel]
21
+ ┌────┬────┬────┬────┬────┐
22
+ │ ★ │ ★ │ ★ │ ★ │ ★ │ scaleStyle: 'star'
23
+ └────┴────┴────┴────┴────┘
24
+ OR emoji row from getEmojiForIndex when scaleStyle: 'emoji'
25
+ ```
26
+
27
+ Reuse CsatScale selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
28
+
29
+ ## Config Decision Tree
30
+
31
+ ```
32
+ rating_scale
33
+ ├── scaleStyle === 'star' → CsatStarIcons per option
34
+ ├── scaleStyle === 'emoji' → getEmojiForIndex
35
+ └── else → numbered badges (no option.color)
36
+ ```
37
+
38
+ ## Answer Mutation
39
+
40
+ ```typescript
41
+ onSelect(number)
42
+ ```
43
+
44
+ ## Agent Checklist
45
+
46
+ - [ ] Add dispatcher branch in Question for `rating_scale`
47
+ - [ ] No traffic-light NPS colors on options
48
+ - [ ] min/mid/max labels with midLabelIndex formula
49
+ - [ ] Match CsatScale interactive states
@@ -0,0 +1,42 @@
1
+ # UI Spec: Standalone Slider (`type: 'slider'`)
2
+
3
+ > **Reference:** ❌ Not routed in `Question.tsx` — reuse `CustomSliderTrack.tsx`
4
+ > **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
5
+ > **Component blueprint:** `01-components/09-custom-slider-track.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `min` / `max` / `step` | ❌ gap | Track range |
12
+ | `defaultValue` | ❌ gap | Initial thumb position |
13
+ | `minLabel` / `maxLabel` | ❌ gap | End labels above track |
14
+ | `enableInputBox` | ❌ gap | See SliderMatrixScale input pattern |
15
+ | `displayValues` | ❌ gap | Pass to CustomSliderTrack |
16
+
17
+ ## Layout Anatomy
18
+
19
+ ```
20
+ [minLabel] [maxLabel]
21
+ ┌──────────────────────────────────────────────────┐
22
+ │ ───●──────────────── (CustomSliderTrack) │
23
+ └──────────────────────────────────────────────────┘
24
+ [optional number input + steppers if enableInputBox]
25
+ ```
26
+
27
+ ## Reference Patterns
28
+
29
+ Copy from [`09-slider-matrix.md`](09-slider-matrix.md) single-row layout and [`shared/custom-slider-track.md`](shared/custom-slider-track.md).
30
+
31
+ ## Answer Mutation
32
+
33
+ ```typescript
34
+ onSelect(number)
35
+ ```
36
+
37
+ ## Agent Checklist
38
+
39
+ - [ ] Add Question dispatcher for `slider`
40
+ - [ ] CustomSliderTrack with min/max/step
41
+ - [ ] Optional input box with clamp min/max
42
+ - [ ] End labels flex justify-between
@@ -0,0 +1,66 @@
1
+ # UI Spec: CFM Matrix (`type: 'matrix'`, `subType: 'CFM_MATRIX'`)
2
+
3
+ > **Reference:** `apps/client/src/components/LikertMatrixScale.tsx`
4
+ > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
5
+ > **Component blueprint:** `01-components/06-likert-matrix-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
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 |
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 |
20
+
21
+ ## Config Decision Tree
22
+
23
+ ```
24
+ CFM_MATRIX
25
+ ├── matrixFormat === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
26
+ ├── matrixFormat === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
27
+ └── else → LikertMatrixGridLayout
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
32
+ ```
33
+
34
+ ## Interactive Behaviors
35
+
36
+ | Behavior | Condition | Action |
37
+ |----------|-----------|--------|
38
+ | Click selected radio again | single-select grid/carousel | Clear row (`undefined`) |
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 |
42
+
43
+ ## Grid Layout
44
+
45
+ - Row label column: `180px` (likert) or `25%` (bipolar)
46
+ - Zebra rows: even rows `rgba(249,250,251,0.8)`
47
+ - Native inputs: `accentColor: #e20074`, 18×18px
48
+
49
+ ## Carousel
50
+
51
+ - Blue active dot: `#2563eb` (Likert only)
52
+ - Card: white, border, shadow, centered row text
53
+
54
+ ## Answer Mutation
55
+
56
+ ```typescript
57
+ onSelect(MatrixAnswerMap) // Record<rowId, value | value[] | null>
58
+ ```
59
+
60
+ ## Agent Checklist
61
+
62
+ - [ ] All three matrixFormat branches
63
+ - [ ] transposeTable + repeatScale + enableScale
64
+ - [ ] Bipolar 25/50/25 layout with rightText
65
+ - [ ] Single-select deselect on re-click
66
+ - [ ] N/A mutex with opacity 0.4
@@ -0,0 +1,65 @@
1
+ # UI Spec: CSAT / Rating Matrix (`subType: 'CSAT_MATRIX' | 'RATING_MATRIX'`)
2
+
3
+ > **Reference:** `apps/client/src/components/CsatMatrixScale.tsx`
4
+ > **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
5
+ > **Component blueprint:** `01-components/05-csat-matrix-scale.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
10
+ |-------|-------------|-----------|
11
+ | `buttonType` | ✅ | Grid vs vertical list routing |
12
+ | `matrixFormat` | ✅ | carousel vs standard |
13
+ | `hasNotApplicable` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
14
+ | `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
15
+ | `labels[]` | ✅ | Anchor row above columns |
16
+ | `columns[].color` | ✅ | RATING_MATRIX badge colors |
17
+
18
+ ## Config Decision Tree
19
+
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).
51
+
52
+ ## Answer Mutation
53
+
54
+ ```typescript
55
+ onSelect(MatrixAnswerMap)
56
+ // hasNotApplicable: null for N/A; undefined clears
57
+ ```
58
+
59
+ ## Agent Checklist
60
+
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
@@ -0,0 +1,61 @@
1
+ # UI Spec: Slider Matrix (`type: 'slider_matrix'`)
2
+
3
+ > **Reference:** `apps/client/src/components/SliderMatrixScale.tsx`
4
+ > **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
5
+ > **Component blueprint:** `01-components/07-slider-matrix-scale.md`
6
+ > **Shared:** [`shared/custom-slider-track.md`](shared/custom-slider-track.md)
7
+
8
+ ## Config Inventory
9
+
10
+ | Field | Implemented | UI impact |
11
+ |-------|-------------|-----------|
12
+ | `rows[]` (min/max/step) | ✅ | Per-row slider range |
13
+ | `labels[]` | ✅ | Header anchor labels |
14
+ | `tickValues[]` | ✅ | Colored badges above track |
15
+ | `ticks` | ✅ | Discrete marks + htmlStep |
16
+ | `sliderType: 'graphics'` | ✅ | Plain text tick labels; emoji thumb |
17
+ | `enableInputBox` | ✅ | 80px input + stepper buttons |
18
+ | `displayValues` | ✅ | Passed to CustomSliderTrack |
19
+ | `enableNotApplicable` | ✅ | Checkbox; value `'N/A'` string |
20
+
21
+ ## Layout Anatomy
22
+
23
+ ```
24
+ [header: labels[] + tickValues badges OR computed marks]
25
+ ┌─ row band (#f3f4f6, rounded-xl) ─────────────────────────────┐
26
+ │ 25% statement │ CustomSliderTrack │ 25% rightText? │ input? │ N/A? │
27
+ └───────────────────────────────────────────────────────────────┘
28
+ ```
29
+
30
+ ## Header Tick Positioning
31
+
32
+ ```typescript
33
+ percentage = ((tv.value - min) / (max - min)) * 100
34
+ sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100
35
+ // Colored badge: tv.color bg, white text, rounded 4px
36
+ // graphics: plain text label instead of colored badge
37
+ ```
38
+
39
+ ## N/A Behavior
40
+
41
+ - Checked → `rowVal === 'N/A'`; slider disabled, input grayed
42
+ - Unchecked → reset to `defaultValue ?? min`
43
+
44
+ ## Input Box
45
+
46
+ - Clamp to row.min / row.max on change
47
+ - Steppers use `htmlStep` increment/decrement
48
+
49
+ ## Answer Mutation
50
+
51
+ ```typescript
52
+ onSelect(Record<string, number | null | 'N/A'>)
53
+ ```
54
+
55
+ ## Agent Checklist
56
+
57
+ - [ ] Shared header from first row min/max/step
58
+ - [ ] Gray row bands with 25% statement columns
59
+ - [ ] CustomSliderTrack per row with correct htmlStep
60
+ - [ ] tickValues colored badges (non-graphics)
61
+ - [ ] N/A mutex disables slider + input
@@ -0,0 +1,54 @@
1
+ # UI Spec: File Upload (`type: 'file_upload'`)
2
+
3
+ > **Reference:** `apps/client/src/components/FileUploadScale.tsx`
4
+ > **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
5
+ > **Component blueprint:** `01-components/08-file-upload-scale.md`
6
+
7
+ ## Config Inventory
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` |
16
+
17
+ ## Layout Anatomy
18
+
19
+ ```
20
+ [uploadMessage HTML in dashed dropzone]
21
+ File limit: N; File size limit: XMB/file|in total; Supports: ...
22
+ [local error banner — red if validation fails]
23
+ [file list cards: icon, name, size, remove ×]
24
+ ```
25
+
26
+ ## Interactive States
27
+
28
+ | State | Border | Background |
29
+ |-------|--------|------------|
30
+ | Default | `#d1d5db` dashed | `#f9fafb` |
31
+ | Hover | `#e20074` | `#fdf2f8` |
32
+ | Drag active | `#e20074` | `#fdf2f8` |
33
+
34
+ ## Local Validation (before SDK)
35
+
36
+ 1. Max file count exceeded
37
+ 2. Unsupported extension
38
+ 3. PER_FILE size exceeded
39
+ 4. IN_TOTAL cumulative size exceeded
40
+
41
+ Error banner: `#fee2e2` bg, `#b91c1c` text.
42
+
43
+ ## Answer Mutation
44
+
45
+ ```typescript
46
+ onSelect(File[] | undefined) // undefined when all removed
47
+ ```
48
+
49
+ ## Agent Checklist
50
+
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)
@@ -0,0 +1,49 @@
1
+ # UI Spec: Text and Media (`type: 'text_and_media'`)
2
+
3
+ > **Reference:** `apps/client/src/components/Question.tsx` (inline)
4
+ > **SDK type:** `TextAndMediaQuestion` — `fetchSurvey/types/textAndMedia.ts`
5
+ > **Component blueprint:** `01-components/02-question.md`
6
+
7
+ ## Config Inventory
8
+
9
+ | Field | Implemented | UI impact |
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% |
16
+
17
+ No answer input — display-only question.
18
+
19
+ ## Layout Anatomy
20
+
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
+ └────────────────────────────────────────────────────────────┘
30
+ ```
31
+
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.
43
+
44
+ ## Agent Checklist
45
+
46
+ - [ ] Render only when `mediaUrl` is present
47
+ - [ ] Video vs image from mime type
48
+ - [ ] `max-w-full rounded-md shadow-sm` on media element
49
+ - [ ] Optional centered caption for `mediaTitle`
@@ -0,0 +1,60 @@
1
+ # UI Spec: Survey Chrome
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`
5
+
6
+ ## SurveyPage — 5-State Machine
7
+
8
+ | State | Condition | UI |
9
+ |-------|-----------|-----|
10
+ | Loading | `surveyQueryResults.isLoading` | Centered "Loading..." |
11
+ | Error | `surveyQueryResults.error` | Red error message |
12
+ | Empty | `!survey` | "No survey data" |
13
+ | Submitted | `submitSurveyResults.data` | Thank you message |
14
+ | Active | else | Questions + nav |
15
+
16
+ ## Active Layout
17
+
18
+ ```
19
+ Header
20
+ main (max-w-4xl)
21
+ ProgressBar(progressPercentage)
22
+ LanguageSelector (hidden if ≤1 language)
23
+ Questions (space-y-12)
24
+ Submit error (if any)
25
+ Nav: Back (if page > 0) + Next/Submit
26
+ Footer
27
+ ```
28
+
29
+ ## Navigation Labels
30
+
31
+ German when `survey.language.startsWith('de')`:
32
+
33
+ | Action | EN | DE |
34
+ |--------|----|----|
35
+ | Back | Zurück | Back |
36
+ | Next | Weiter | Next |
37
+ | Submit | Absenden | Submit |
38
+ | Submitting | Wird gesendet... | Submitting... |
39
+
40
+ ## Button Tokens
41
+
42
+ - Back: `border-[#e20074] text-[#e20074] hover:bg-[#fdf2f8]`
43
+ - Primary: `bg-[#e20074] text-white hover:opacity-90`
44
+
45
+ ## ProgressBar
46
+
47
+ Pink fill bar, `300ms` width transition on `progressPercentage`.
48
+
49
+ ## LanguageSelector
50
+
51
+ - Hidden when `languages.length <= 1`
52
+ - Focus ring: `#2563eb`
53
+
54
+ ## Agent Checklist
55
+
56
+ - [ ] All 5 states implemented
57
+ - [ ] `onAction({ type: 'CHANGE' })` wired per question
58
+ - [ ] PREVIOUS / NEXT actions on nav buttons
59
+ - [ ] ProgressBar receives `state.progressPercentage`
60
+ - [ ] Header/Footer on every state branch
@@ -0,0 +1,79 @@
1
+ # UI Specifications — Reference Implementation Guide
2
+
3
+ > **Authoritative source:** [`apps/client/src/components/`](../../../../../../apps/client/src/components/) in the monorepo.
4
+ > Agents must reproduce this UI when building survey apps. Read the spec for each question type present in the fetched survey.
5
+
6
+ ## How to Read a UI Spec
7
+
8
+ Each file in this folder follows the same structure:
9
+
10
+ 1. **Config inventory** — every SDK field, whether the reference client implements it
11
+ 2. **Layout anatomy** — structured ASCII diagram
12
+ 3. **Config decision tree** — matches `if/else` branches in reference code
13
+ 4. **Interactive states** — default, hover, selected, disabled, focus
14
+ 5. **Tooltips & labels** — positioning formulas
15
+ 6. **Color tokens** — exact hex values from reference client
16
+ 7. **Answer mutation** — `onSelect` / `onAction CHANGE` shape
17
+ 8. **Agent checklist**
18
+
19
+ ## Design Tokens (Reference Client)
20
+
21
+ | Token | Value | Usage |
22
+ |-------|-------|-------|
23
+ | Primary | `#e20074` | Borders, rings, dots, slider fill, selected text |
24
+ | Selected bg | `#fdf2f8` | Card options, CSAT buttons, matrix cells |
25
+ | NPS track highlight | `#fbe8f3` | Selected radio cell in NPS track row |
26
+ | NPS track bar | `#f2f2f2` | Gray bar behind NPS radios |
27
+ | Validation border | `#333333` | Error banner |
28
+ | Validation bg | `#d9d9d9` | Error banner |
29
+ | Row band | `#f3f4f6` | Slider matrix row background |
30
+ | Zebra row | `rgba(249,250,251,0.8)` | Matrix alternating rows |
31
+ | Tooltip bg | `#1f2937` | Hover tooltips on emoji/slider |
32
+ | Likert carousel dot (active) | `#2563eb` | LikertMatrix carousel only |
33
+ | CSAT carousel dot (active) | `#e20074` | CsatMatrix carousel |
34
+
35
+ ## Reference Component Index
36
+
37
+ | Spec file | Reference component | `question.type` |
38
+ |-----------|---------------------|-----------------|
39
+ | [00-question-shell.md](00-question-shell.md) | `Question.tsx` | All (wrapper) |
40
+ | [01-rating.md](01-rating.md) | `RatingScale.tsx` | `rating` |
41
+ | [02-radio.md](02-radio.md) | `Question.tsx` inline | `radio` |
42
+ | [03-text.md](03-text.md) | `Question.tsx` inline | `text` |
43
+ | [04-csat.md](04-csat.md) | `CsatScale.tsx` | `csat` |
44
+ | [05-rating-scale.md](05-rating-scale.md) | *(gap — reuse CsatScale patterns)* | `rating_scale` |
45
+ | [06-slider.md](06-slider.md) | *(gap — reuse CustomSliderTrack)* | `slider` |
46
+ | [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `matrix` + CFM |
47
+ | [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | CSAT/RATING matrix |
48
+ | [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
49
+ | [10-file-upload.md](10-file-upload.md) | `FileUploadScale.tsx` | `file_upload` |
50
+ | [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `text_and_media` |
51
+ | [12-survey-chrome.md](12-survey-chrome.md) | `SurveyPage.tsx` + chrome | Shell |
52
+ | [shared/custom-slider-track.md](shared/custom-slider-track.md) | `CustomSliderTrack.tsx` | Primitive |
53
+ | [shared/matrix-dropdown.md](shared/matrix-dropdown.md) | `MatrixDropdown.tsx` | Primitive |
54
+
55
+ ## NPS Traffic-Light Colors
56
+
57
+ Use `option.color` from SDK — never invent colors:
58
+
59
+ | Value | Hex |
60
+ |-------|-----|
61
+ | 0 | `#e2001a` |
62
+ | 1 | `#e4251b` |
63
+ | 2 | `#ec610a` |
64
+ | 3 | `#f18b00` |
65
+ | 4 | `#f7b200` |
66
+ | 5 | `#fcd900` |
67
+ | 6 | `#fcd900` |
68
+ | 7 | `#9fca00` |
69
+ | 8 | `#6bb300` |
70
+ | 9 | `#339a00` |
71
+ | 10 | `#008000` |
72
+
73
+ ## When to Read
74
+
75
+ After Phase 3 in [`index.md`](../index.md), for each question in `state.currentQuestions`:
76
+
77
+ 1. Read [`02-reference/question-types/`](../02-reference/question-types/) for data shape
78
+ 2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `uiSpecs`)
79
+ 3. Cross-check [`config-field-index.md`](../02-reference/config-field-index.md)
@@ -0,0 +1,52 @@
1
+ # UI Spec: Custom Slider Track (shared primitive)
2
+
3
+ > **Reference:** `apps/client/src/components/CustomSliderTrack.tsx`
4
+ > **Used by:** `SliderMatrixScale.tsx`, `CsatMatrixScale` (graphics), standalone `slider` (gap)
5
+
6
+ ## Layer Architecture
7
+
8
+ ```
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)
15
+ ```
16
+
17
+ ## Props
18
+
19
+ | Prop | Role |
20
+ |------|------|
21
+ | `min`, `max`, `htmlStep` | Range input bounds |
22
+ | `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 `getEmojiForIndex` |
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
+ getEmojiForIndex(displayIdx, ticks || 10)
45
+ ```
46
+
47
+ ## Agent Checklist
48
+
49
+ - [ ] 4-layer overlay with invisible range input
50
+ - [ ] Tooltip on hover AND drag
51
+ - [ ] Graphics mode emoji thumb
52
+ - [ ] Transition disabled while dragging
@@ -0,0 +1,38 @@
1
+ # UI Spec: Matrix Dropdown (shared primitive)
2
+
3
+ > **Reference:** `apps/client/src/components/MatrixDropdown.tsx`
4
+ > **Used by:** `LikertMatrixScale`, `CsatMatrixScale` vertical list
5
+
6
+ ## Modes
7
+
8
+ | `multiple` | Trigger | Selection |
9
+ |------------|---------|-----------|
10
+ | `false` | Single line + chevron | One value; checkmark on selected option |
11
+ | `true` | Tag chips with × | Toggle array; N/A mutex |
12
+
13
+ ## Interactive States
14
+
15
+ | State | Trigger border | Shadow |
16
+ |-------|----------------|--------|
17
+ | Closed | `#d1d5db` | subtle |
18
+ | Open | `#e20074` | `0 0 0 3px rgba(226,0,116,0.1)` |
19
+ | Option hover | bg `#fdf2f8` | — |
20
+ | Option selected | bg `#fdf2f8`, text `#e20074` | — |
21
+ | N/A mutex (multi) | Other options opacity 0.5, disabled | — |
22
+
23
+ ## N/A Mutex (multi-select)
24
+
25
+ ```typescript
26
+ if (val === null) {
27
+ if (multiValues.includes(null)) onChange([]);
28
+ else onChange([null]);
29
+ }
30
+ // When null selected, non-null options disabled
31
+ ```
32
+
33
+ ## Agent Checklist
34
+
35
+ - [ ] Click-outside closes panel
36
+ - [ ] HTML labels via dangerouslySetInnerHTML
37
+ - [ ] Single: close on select
38
+ - [ ] Multi: tag chips removable with ×