@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.
- package/dist/cli/index.js +18 -18
- package/dist/cli/index.mjs +14 -14
- package/dist/index.d.mts +465 -580
- package/dist/index.d.ts +465 -580
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -4
- package/postinstall.js +96 -69
- package/templates/AGENT.md +18 -8
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
- package/templates/docs/00-integration/aws-deploy.md +281 -0
- package/templates/docs/00-integration/client-integration-guide.md +351 -0
- package/templates/docs/00-integration/component-checklist.md +55 -22
- package/templates/docs/00-integration/constraints.md +49 -4
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
- package/templates/docs/00-integration/progress.md +61 -0
- package/templates/docs/00-integration/question-display-variants.md +65 -0
- package/templates/docs/00-integration/question-numbering.md +51 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
- package/templates/docs/00-integration/setup.md +84 -0
- package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
- package/templates/docs/00-integration/useSurveySDK.md +151 -23
- package/templates/docs/01-components/01-survey-page.md +76 -54
- package/templates/docs/01-components/02-question.md +61 -58
- package/templates/docs/01-components/03-rating-scale.md +11 -1
- package/templates/docs/01-components/04-star-rating-scale.md +45 -0
- package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
- package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +23 -51
- package/templates/docs/01-components/09-custom-slider-track.md +2 -2
- package/templates/docs/01-components/11-progress-bar.md +10 -4
- package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
- package/templates/docs/01-components/14-intro-page.md +34 -0
- package/templates/docs/01-components/15-end-page.md +31 -0
- package/templates/docs/01-components/16-paused-page.md +27 -0
- package/templates/docs/01-components/17-heatmap-scale.md +75 -0
- package/templates/docs/01-components/18-rank-order-scale.md +93 -0
- package/templates/docs/01-components/README.md +42 -36
- package/templates/docs/02-reference/config-field-index.md +141 -139
- package/templates/docs/02-reference/exports.md +149 -0
- package/templates/docs/02-reference/question-types/01-rating.md +31 -32
- package/templates/docs/02-reference/question-types/04-csat.md +51 -42
- package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
- package/templates/docs/02-reference/question-types/06-slider.md +2 -2
- package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
- package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
- package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
- package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
- package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
- package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
- package/templates/docs/02-reference/question-types/README.md +96 -47
- package/templates/docs/02-reference/routing-table.md +33 -18
- package/templates/docs/02-reference/value-derivation.md +63 -0
- package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
- package/templates/docs/03-ui-specs/01-rating.md +18 -52
- package/templates/docs/03-ui-specs/02-radio.md +80 -37
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +68 -33
- package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
- package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
- package/templates/docs/03-ui-specs/README.md +16 -14
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +476 -69
- package/templates/docs/index.md +82 -30
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +221 -154
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/deploy-to-aws.sh +114 -0
- package/templates/docs/templates/heatmapCoords.ts +58 -0
- package/templates/docs/templates/implementation_plan.md +126 -67
- package/templates/docs/templates/survey-inventory.schema.json +77 -0
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +85 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Placeholders and Tokens
|
|
2
|
+
|
|
3
|
+
> Covers `sdkFeatures.placeholderResolution` — how CRM and dynamic answer tokens resolve in question text.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Token formats
|
|
8
|
+
|
|
9
|
+
| Token pattern | Example | Resolved by |
|
|
10
|
+
|---------------|---------|-------------|
|
|
11
|
+
| CRM / static placeholder | `{{FIRST_NAME}}`, `{{COMPANY}}` | SDK at map time via `options.placeholders` |
|
|
12
|
+
| Survey question answer | `{{SURVEY_QUESTION-:-questionId}}` | Client at render time via `resolveSurveyQuestionPlaceholders` |
|
|
13
|
+
| Display variant text | Variant bodies may contain both | SDK resolves static tokens on variants; client resolves answer tokens |
|
|
14
|
+
|
|
15
|
+
Question IDs in answer tokens match the fetched survey's `question.id` (not display order).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Stable `placeholders` ref
|
|
20
|
+
|
|
21
|
+
Define placeholders **outside** the React component (module scope or parent file):
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const SURVEY_PLACEHOLDERS = {
|
|
25
|
+
FIRST_NAME: 'Alex',
|
|
26
|
+
COMPANY: 'Acme Corp',
|
|
27
|
+
} as const;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Wrap hook options in `useMemo` so the SDK does not re-fetch on every render:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const surveyOptions = useMemo(
|
|
34
|
+
() => ({
|
|
35
|
+
instanceId: SURVEY_INSTANCE_ID,
|
|
36
|
+
language: selectedLanguage,
|
|
37
|
+
placeholders: SURVEY_PLACEHOLDERS,
|
|
38
|
+
customFieldValues: SURVEY_CUSTOM_FIELDS,
|
|
39
|
+
}),
|
|
40
|
+
[selectedLanguage]
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Failure mode:** Inline `placeholders={{ FIRST_NAME: '...' }}` inside the component causes infinite fetch loops.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Where SDK resolves vs client responsibility
|
|
49
|
+
|
|
50
|
+
| Stage | What happens |
|
|
51
|
+
|-------|--------------|
|
|
52
|
+
| **Fetch / map time** | SDK applies language translation + static CRM placeholders to question text and display variants |
|
|
53
|
+
| **`currentQuestions`** | Each question's `questionText` is already variant-resolved for the active path |
|
|
54
|
+
| **Render time (client)** | `Question.tsx` calls `resolveSurveyQuestionPlaceholders(text, answers, questionsById)` for `{{SURVEY_QUESTION-:-id}}` tokens |
|
|
55
|
+
|
|
56
|
+
Do **not** reimplement CRM token substitution in components — only answer-reference tokens need client resolution.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## `resolveSurveyQuestionPlaceholders`
|
|
61
|
+
|
|
62
|
+
Import from package root:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { resolveSurveyQuestionPlaceholders } from '@explorer02/cfm-survey-sdk';
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Typical usage in `Question.tsx`:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const resolvedText = resolveSurveyQuestionPlaceholders(
|
|
72
|
+
question.questionText,
|
|
73
|
+
allAnswers,
|
|
74
|
+
questionsById
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Pass **all** answers and a `questionsById` map built from the full survey (not just `currentQuestions`) so cross-page answer tokens resolve.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## `formatAnswerForDisplay`
|
|
83
|
+
|
|
84
|
+
When a token references another question's answer, the SDK formats the stored answer for human-readable display:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { formatAnswerForDisplay } from '@explorer02/cfm-survey-sdk';
|
|
88
|
+
|
|
89
|
+
// Used internally by resolveSurveyQuestionPlaceholders; call directly only for custom UI
|
|
90
|
+
const label = formatAnswerForDisplay(question, answerValue);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Matrix answers format as row/column labels; MCQ as option text; rank order as ordered list.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Wiring checklist
|
|
98
|
+
|
|
99
|
+
- [ ] `SURVEY_PLACEHOLDERS` defined at module scope (stable ref)
|
|
100
|
+
- [ ] `surveyOptions` wrapped in `useMemo`
|
|
101
|
+
- [ ] `Question` receives `allAnswers` + `allQuestions` (or `questionsById`)
|
|
102
|
+
- [ ] Question shell renders `resolveSurveyQuestionPlaceholders` output, not raw `questionText` when answer tokens may appear
|
|
103
|
+
- [ ] Display variants: trust SDK-resolved text on `currentQuestions`; still run answer-token pass at render
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Failure modes
|
|
108
|
+
|
|
109
|
+
| Symptom | Cause | Fix |
|
|
110
|
+
|---------|-------|-----|
|
|
111
|
+
| Token shows literally `{{FIRST_NAME}}` | Missing key in `placeholders` map | Add key matching token name (without braces) |
|
|
112
|
+
| Answer token empty | Referenced question unanswered or hidden | Expected until answered; hidden answers sanitized on skip path change |
|
|
113
|
+
| Infinite API requests | Unstable `placeholders` ref | Move outside component; `useMemo` on options |
|
|
114
|
+
| Expired JWT / fetch error | Instance ID invalid | Refresh token; placeholders unrelated |
|
|
115
|
+
|
|
116
|
+
See also [`question-display-variants.md`](question-display-variants.md) and [`client-integration-guide.md`](client-integration-guide.md).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Progress Bar Calculation
|
|
2
|
+
|
|
3
|
+
> The progress percentage reflects **questions on the active path**, not all questions in the survey.
|
|
4
|
+
|
|
5
|
+
## Formula
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
active path = pages the respondent will visit (given current answers + skip logic)
|
|
9
|
+
total = answerable questions on that path (excludes TEXT_AND_MEDIA)
|
|
10
|
+
answered = earned question credits that still lie on the path
|
|
11
|
+
progress % = round(answered / total × 100)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`state.progressPercentage` from `useSurveySDK` is already computed — bind it to your `ProgressBar`.
|
|
15
|
+
|
|
16
|
+
The same active path also drives **question numbering** on `state.currentQuestions[].questionNumber`. See [`question-numbering.md`](question-numbering.md).
|
|
17
|
+
|
|
18
|
+
Custom field values in `options.customFieldValues` affect the active path when skip or display logic references `CUSTOM_FIELD` entities — the denominator updates automatically. See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
|
|
19
|
+
|
|
20
|
+
## How credits are earned
|
|
21
|
+
|
|
22
|
+
| Question | When credited |
|
|
23
|
+
|----------|---------------|
|
|
24
|
+
| **Optional** | When the user **lands on** the page (`earnPageOnVisit`) |
|
|
25
|
+
| **Required** | When validly answered (on change, or on visit if already filled) |
|
|
26
|
+
|
|
27
|
+
Display-only `TEXT_AND_MEDIA` questions never count toward progress.
|
|
28
|
+
|
|
29
|
+
## Skip logic changes the denominator
|
|
30
|
+
|
|
31
|
+
If skip logic removes pages from the active path, the **total** shrinks automatically.
|
|
32
|
+
|
|
33
|
+
**Example:** Survey has 10 pages; skip removes pages 3–6 → total might drop from 10 questions to 6 on the active path.
|
|
34
|
+
|
|
35
|
+
## Path change vs prune (progress only)
|
|
36
|
+
|
|
37
|
+
| Event | Progress effect |
|
|
38
|
+
|-------|-----------------|
|
|
39
|
+
| **Path changed** (answer edit reshapes route) | Future answers cleared; off-path credits removed; bar recalculates |
|
|
40
|
+
| **Pruned only** (same route, stale credits) | Off-path credits removed; bar recalculates; answers kept |
|
|
41
|
+
|
|
42
|
+
## Example timeline
|
|
43
|
+
|
|
44
|
+
Survey: 4 questions on active path (Q1–Q4 sequential).
|
|
45
|
+
|
|
46
|
+
| Step | Action | Progress |
|
|
47
|
+
|------|--------|----------|
|
|
48
|
+
| 1 | Land page 1 | Optional Q2 on page 2 not yet counted |
|
|
49
|
+
| 2 | Answer required Q1 | 1/4 = 25% |
|
|
50
|
+
| 3 | Next → page 2 | Optional Q2 credited on visit → 2/4 = 50% |
|
|
51
|
+
| 4 | Change Q1 to trigger skip to Q4 | Path shrinks to 2 questions; stale credits pruned; sanitize clears middle answers |
|
|
52
|
+
|
|
53
|
+
## UI wiring
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<ProgressBar progressPercentage={state.progressPercentage} />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
See [`01-components/11-progress-bar.md`](../01-components/11-progress-bar.md) and [`03-ui-specs/12-survey-chrome.md`](../03-ui-specs/12-survey-chrome.md).
|
|
60
|
+
|
|
61
|
+
See also: [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Question Display Variants
|
|
2
|
+
|
|
3
|
+
> Integrator-facing behavior contract. Variant evaluation is handled inside the SDK — the client renders `question.questionText` from `state.currentQuestions` with no variant-aware code.
|
|
4
|
+
|
|
5
|
+
## What variants do
|
|
6
|
+
|
|
7
|
+
A question may define alternate rich-text prompts under `questionDisplayVariants[]` in the API. Each variant has:
|
|
8
|
+
|
|
9
|
+
- `questionText` / `questionRichText` (and optional `translations`)
|
|
10
|
+
- `controlConfig.displayLogic.condition` — the same nested AND/OR filter tree used for skip, display, answer, and custom-field logic
|
|
11
|
+
|
|
12
|
+
At runtime the SDK picks the **first variant whose condition evaluates true** and overrides `questionText` on `state.currentQuestions`. If no variant matches, the question's base shell text (root `questionRichText` / `questionText`) is used.
|
|
13
|
+
|
|
14
|
+
Variants change **wording only** — they do not show/hide the question. Question-level show/hide remains `question.displayLogic` (see [`display-logic-and-navigation.md`](display-logic-and-navigation.md)).
|
|
15
|
+
|
|
16
|
+
## Shared logic engine
|
|
17
|
+
|
|
18
|
+
Variant conditions use the same evaluator as all other logic kinds:
|
|
19
|
+
|
|
20
|
+
| Entity | Source |
|
|
21
|
+
|--------|--------|
|
|
22
|
+
| `QUESTION` | Prior answers in `state.answers` |
|
|
23
|
+
| `CUSTOM_FIELD` | `options.customFieldValues` (`_c_…` ids) |
|
|
24
|
+
|
|
25
|
+
See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) when variant rules reference CRM/transaction fields.
|
|
26
|
+
|
|
27
|
+
## Client responsibilities
|
|
28
|
+
|
|
29
|
+
**None beyond normal rendering.**
|
|
30
|
+
|
|
31
|
+
- Use `state.currentQuestions` — `questionText` is already resolved for the current answers and custom field values.
|
|
32
|
+
- Render with `dangerouslySetInnerHTML` as today.
|
|
33
|
+
- Keep calling `resolveSurveyQuestionPlaceholders(question.questionText, allAnswers, allQuestions)` for `{{SURVEY_QUESTION-:-questionId}}` answer tokens (unchanged).
|
|
34
|
+
- Do **not** read `question.displayVariants` or re-implement condition evaluation in the client.
|
|
35
|
+
|
|
36
|
+
## Resolution order
|
|
37
|
+
|
|
38
|
+
1. **Map time:** language translation + static CRM placeholders (`{{FIRST_NAME}}`) applied to each variant's text
|
|
39
|
+
2. **Runtime (SDK):** first matching variant in API array order
|
|
40
|
+
3. **Render time (client):** dynamic answer placeholders via `resolveSurveyQuestionPlaceholders`
|
|
41
|
+
|
|
42
|
+
## Behavior summary
|
|
43
|
+
|
|
44
|
+
| Scenario | Result |
|
|
45
|
+
|----------|--------|
|
|
46
|
+
| No `displayVariants` | Base `questionText` |
|
|
47
|
+
| Variants present, none match | Base shell `questionText` |
|
|
48
|
+
| Multiple variants match | First in API order wins |
|
|
49
|
+
| Answers / custom fields change | `currentQuestions` updates on same page |
|
|
50
|
+
| Question hidden by display logic | Not in `currentQuestions`; variants not evaluated |
|
|
51
|
+
| Language switch | Re-fetch/remap applies translated variant text |
|
|
52
|
+
|
|
53
|
+
## SDK exports (optional advanced use)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import {
|
|
57
|
+
resolveQuestionDisplayText,
|
|
58
|
+
applyQuestionDisplayVariants,
|
|
59
|
+
type QuestionDisplayVariant,
|
|
60
|
+
} from '@explorer02/cfm-survey-sdk';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Most integrators only need `state.currentQuestions`.
|
|
64
|
+
|
|
65
|
+
See also: [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md), [`useSurveySDK.md`](useSurveySDK.md).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Question Numbering (Active Path)
|
|
2
|
+
|
|
3
|
+
> Integrator-facing behavior contract. Question ordinals are computed inside the SDK — the client renders `question.questionNumber` from `state.currentQuestions` with no numbering logic.
|
|
4
|
+
|
|
5
|
+
## What the SDK does
|
|
6
|
+
|
|
7
|
+
The SDK assigns **1-indexed ordinals** to answerable questions on the respondent’s **active path** (same path used for the progress bar). The resolved value is written to `questionNumber` on each item in `state.currentQuestions`.
|
|
8
|
+
|
|
9
|
+
| Rule | Behavior |
|
|
10
|
+
|------|----------|
|
|
11
|
+
| Path source | `simulateActivePath()` — skip + display + custom-field logic |
|
|
12
|
+
| Which questions | Visible **answerable** questions (`getAnswerableQuestionsOnPage`) |
|
|
13
|
+
| Excluded | `TEXT_AND_MEDIA` (display-only, same as progress) |
|
|
14
|
+
| Order | Active-path page order, then question order within each page |
|
|
15
|
+
| Updates | When answers, custom fields, or path shape change |
|
|
16
|
+
|
|
17
|
+
Example: skip from Q3 to Q8 → path questions are numbered **1, 2, 3, 4…**, not builder indices.
|
|
18
|
+
|
|
19
|
+
## Client responsibilities
|
|
20
|
+
|
|
21
|
+
**Render only — no path simulation.**
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
{question.questionNumber != null && (
|
|
25
|
+
<span>{question.questionNumber}.</span>
|
|
26
|
+
)}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use `state.currentQuestions` (not raw `survey.pages` questions). Optional chrome:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
state.totalQuestionsCount // path denominator (same as progress)
|
|
33
|
+
state.answeredQuestionsCount // earned credits on path
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Relationship to progress
|
|
37
|
+
|
|
38
|
+
Question numbering and `state.progressPercentage` share the same active-path denominator. See [`progress.md`](progress.md).
|
|
39
|
+
|
|
40
|
+
## SDK exports (optional advanced use)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import {
|
|
44
|
+
computeActivePathQuestionNumbers,
|
|
45
|
+
applyActivePathQuestionNumbers,
|
|
46
|
+
} from '@explorer02/cfm-survey-sdk';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Most integrators only need `state.currentQuestions` and `state.totalQuestionsCount`.
|
|
50
|
+
|
|
51
|
+
See also: [`question-display-variants.md`](question-display-variants.md), [`useSurveySDK.md`](useSurveySDK.md).
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Question Type SDK Integration Matrix
|
|
2
|
+
|
|
3
|
+
> **Authoritative master table** for all **11** `QUESTION_TYPE` literals. Read this after [`client-integration-guide.md`](client-integration-guide.md) and before building any question component.
|
|
4
|
+
|
|
5
|
+
Each row uses the same columns so agents can inventory a fetched survey and wire every SDK feature without importing internal evaluators.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Cross-type logic applicability
|
|
10
|
+
|
|
11
|
+
| Feature | MCQ | TEXTFIELD | NPS | CFM | CSAT | RATING | SLIDER | FILE | T&M | HEATMAP | RANK |
|
|
12
|
+
|---------|:---:|:---------:|:---:|:---:|:----:|:------:|:------:|:----:|:---:|:-------:|:----:|
|
|
13
|
+
| Skip logic | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
14
|
+
| Display logic (question) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
15
|
+
| Answer logic (options/rows/cols) | opt | — | col | row+col | row+col | row | row | — | — | — | opt |
|
|
16
|
+
| Display variants | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
17
|
+
| Question numbering | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
18
|
+
| Placeholders | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
19
|
+
| Partial save | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
|
20
|
+
| Custom field logic | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
21
|
+
|
|
22
|
+
Legend: **opt** = option-level; **row+col** = statement rows and scale columns; **col** = scale columns only; **—** = no answer stored or no answer-logic targets.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## MCQ
|
|
27
|
+
|
|
28
|
+
| Column | Value |
|
|
29
|
+
|--------|-------|
|
|
30
|
+
| **SDK type + answer shape** | `McqQuestion`; answer `string \| number` (option `id`) or `string[] \| number[]` when multi-select |
|
|
31
|
+
| **Component file(s)** | Inline in `Question.tsx` or dedicated `McqQuestion.tsx` |
|
|
32
|
+
| **Dispatcher branch** | `QUESTION_TYPE.MCQ` / `'MCQ'` |
|
|
33
|
+
| **Visibility helper** | `getVisibleMcqOptionsForAnswers(question, answers, allQuestions, customFieldValues?)` |
|
|
34
|
+
| **Answer logic scope** | Options |
|
|
35
|
+
| **Skip / display / variants / numbering** | SDK handles skip path, `currentQuestions`, resolved `questionText`, `questionNumber` — client renders visible options only |
|
|
36
|
+
| **Custom field logic** | Pass `customFieldValues` as 4th arg to visibility helper when rules reference CRM fields |
|
|
37
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: optionId \| optionId[] } }` |
|
|
38
|
+
| **Value derivation** | Store `option.id`; SDK maps to API value at submit |
|
|
39
|
+
| **Performance notes** | Filter options once per render via helper; avoid re-fetch on option click |
|
|
40
|
+
| **Docs to read** | [`02-question.md`](../01-components/02-question.md) · [`question-types/README.md#mcq`](../02-reference/question-types/README.md#mcq) · [`02-radio.md`](../03-ui-specs/02-radio.md) |
|
|
41
|
+
| **Default answers** | SDK `applyMcqDefaults` seeds `state.answers` from `defaultOptionIds` on load — client binds `selectedValue` only |
|
|
42
|
+
| **Answer conditions** | SDK validates `minSelections` / `maxSelections` on NEXT; client blocks add at max + shows `validationError` |
|
|
43
|
+
| **Known limitations** | Multi-select stores `string[]`; use `selectionMode` not `isMultiSelect` |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## TEXTFIELD
|
|
48
|
+
|
|
49
|
+
| Column | Value |
|
|
50
|
+
|--------|-------|
|
|
51
|
+
| **SDK type + answer shape** | `TextInputQuestion`; answer `string` |
|
|
52
|
+
| **Component file(s)** | Inline in `Question.tsx` |
|
|
53
|
+
| **Dispatcher branch** | `QUESTION_TYPE.TEXTFIELD` / `'TEXTFIELD'` |
|
|
54
|
+
| **Visibility helper** | None |
|
|
55
|
+
| **Answer logic scope** | None |
|
|
56
|
+
| **Skip / display / variants / numbering** | SDK auto; client renders `inputVariant` (`short` vs `long`) from question config |
|
|
57
|
+
| **Custom field logic** | Only in skip/display rules — no visibility helper |
|
|
58
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: string } }` — debounce long text in UI if needed |
|
|
59
|
+
| **Value derivation** | Raw string stored |
|
|
60
|
+
| **Performance notes** | Debounce `onAction` for long textarea (300–500 ms) to reduce partial-save churn |
|
|
61
|
+
| **Docs to read** | [`02-question.md`](../01-components/02-question.md) · [`03-text.md`](../03-ui-specs/03-text.md) |
|
|
62
|
+
| **Known limitations** | `short` variant uses single-line input; no answer logic on field content |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## NPS_SCALE
|
|
67
|
+
|
|
68
|
+
| Column | Value |
|
|
69
|
+
|--------|-------|
|
|
70
|
+
| **SDK type + answer shape** | `NpsQuestion`; answer `number` (0–10 index or stored scale value) |
|
|
71
|
+
| **Component file(s)** | `RatingScale.tsx` (`variant="nps"`) |
|
|
72
|
+
| **Dispatcher branch** | `QUESTION_TYPE.NPS_SCALE` / `'NPS_SCALE'` |
|
|
73
|
+
| **Visibility helper** | `getVisibleScaleColumnsForAnswers` (when scale points have answer logic) |
|
|
74
|
+
| **Answer logic scope** | Scale columns/points only |
|
|
75
|
+
| **Skip / display / variants / numbering** | SDK auto; client renders NPS 0–10 row |
|
|
76
|
+
| **Custom field logic** | 4th arg on scale visibility helper when needed |
|
|
77
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: number } }` |
|
|
78
|
+
| **Value derivation** | Numeric from selected scale point; use `optionLabel` or index per ui-spec |
|
|
79
|
+
| **Performance notes** | Single row — no matrix overhead |
|
|
80
|
+
| **Docs to read** | [`03-rating-scale.md`](../01-components/03-rating-scale.md) · [`01-rating.md`](../02-reference/question-types/01-rating.md) · [`01-rating.md`](../03-ui-specs/01-rating.md) |
|
|
81
|
+
| **Known limitations** | Legacy `star_rating` maps here or to matrix display styles — not a separate literal |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## CFM_MATRIX
|
|
86
|
+
|
|
87
|
+
| Column | Value |
|
|
88
|
+
|--------|-------|
|
|
89
|
+
| **SDK type + answer shape** | `CfmMatrixQuestion`; answer `MatrixRowAnswers` (`Record<rowId, columnStoredValue>`) |
|
|
90
|
+
| **Component file(s)** | `LikertMatrixScale.tsx`, `MatrixDropdown.tsx` (when `gridLayout: dropdown`) |
|
|
91
|
+
| **Dispatcher branch** | `QUESTION_TYPE.CFM_MATRIX` / `'CFM_MATRIX'` |
|
|
92
|
+
| **Visibility helper** | `getVisibleMatrixItemsForAnswers` — filters rows **and** columns |
|
|
93
|
+
| **Answer logic scope** | Statement rows + scale columns |
|
|
94
|
+
| **Skip / display / variants / numbering** | SDK auto; client picks Likert vs dropdown from `gridLayout` |
|
|
95
|
+
| **Custom field logic** | 4th arg on matrix visibility helper |
|
|
96
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } }` — merge row updates |
|
|
97
|
+
| **Value derivation** | **`matrixColumnStoredValue(column)`** for each cell — never store raw label |
|
|
98
|
+
| **Performance notes** | Render only visible rows/cols; lazy-load large matrices |
|
|
99
|
+
| **Docs to read** | [`06-likert-matrix-scale.md`](../01-components/06-likert-matrix-scale.md) · [`07-matrix-cfm.md`](../02-reference/question-types/07-matrix-cfm.md) · [`07-matrix-cfm.md`](../03-ui-specs/07-matrix-cfm.md) |
|
|
100
|
+
| **Known limitations** | Bipolar layout is visual only — same answer shape |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## CSAT_MATRIX
|
|
105
|
+
|
|
106
|
+
| Column | Value |
|
|
107
|
+
|--------|-------|
|
|
108
|
+
| **SDK type + answer shape** | `CsatQuestion`; answer `MatrixRowAnswers` |
|
|
109
|
+
| **Component file(s)** | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` (graphics) |
|
|
110
|
+
| **Dispatcher branch** | `QUESTION_TYPE.CSAT_MATRIX` / `'CSAT_MATRIX'` |
|
|
111
|
+
| **Visibility helper** | `getVisibleMatrixItemsForAnswers` |
|
|
112
|
+
| **Answer logic scope** | Statement rows + scale columns |
|
|
113
|
+
| **Skip / display / variants / numbering** | SDK auto; emoji/star UI is **client-implemented** (`react-icons`) |
|
|
114
|
+
| **Custom field logic** | 4th arg on matrix visibility helper |
|
|
115
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } }` |
|
|
116
|
+
| **Value derivation** | `matrixColumnStoredValue(column)` |
|
|
117
|
+
| **Performance notes** | Graphics mode uses slider track per row; prefer memoized emoji maps |
|
|
118
|
+
| **Docs to read** | [`05-csat-matrix-scale.md`](../01-components/05-csat-matrix-scale.md) · [`04-csat.md`](../02-reference/question-types/04-csat.md) · [`04-csat.md`](../03-ui-specs/04-csat.md) |
|
|
119
|
+
| **Known limitations** | No SDK emoji exports — implement icons client-side |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## RATING_MATRIX
|
|
124
|
+
|
|
125
|
+
| Column | Value |
|
|
126
|
+
|--------|-------|
|
|
127
|
+
| **SDK type + answer shape** | `RatingMatrixQuestion`; answer `MatrixRowAnswers` |
|
|
128
|
+
| **Component file(s)** | Same as CSAT: `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` |
|
|
129
|
+
| **Dispatcher branch** | `QUESTION_TYPE.RATING_MATRIX` / `'RATING_MATRIX'` |
|
|
130
|
+
| **Visibility helper** | `getVisibleMatrixItemsForAnswers` |
|
|
131
|
+
| **Answer logic scope** | Statement rows; **column answer logic may be limited** — verify fetched survey |
|
|
132
|
+
| **Skip / display / variants / numbering** | SDK auto; reuse CSAT matrix component with rating display style |
|
|
133
|
+
| **Custom field logic** | 4th arg on matrix visibility helper |
|
|
134
|
+
| **`onAction` pattern** | Same as CSAT matrix |
|
|
135
|
+
| **Value derivation** | `matrixColumnStoredValue(column)` |
|
|
136
|
+
| **Performance notes** | Same as CSAT matrix |
|
|
137
|
+
| **Docs to read** | [`05-csat-matrix-scale.md`](../01-components/05-csat-matrix-scale.md) · [`09-matrix-rating.md`](../02-reference/question-types/09-matrix-rating.md) · [`08-matrix-csat-rating.md`](../03-ui-specs/08-matrix-csat-rating.md) |
|
|
138
|
+
| **Known limitations** | Shares CSAT component; column-level answer logic gap documented in reference |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## SLIDER_MATRIX
|
|
143
|
+
|
|
144
|
+
| Column | Value |
|
|
145
|
+
|--------|-------|
|
|
146
|
+
| **SDK type + answer shape** | `SliderMatrixQuestion`; answer `MatrixRowAnswers` |
|
|
147
|
+
| **Component file(s)** | `SliderMatrixScale.tsx`, `CustomSliderTrack.tsx` |
|
|
148
|
+
| **Dispatcher branch** | `QUESTION_TYPE.SLIDER_MATRIX` / `'SLIDER_MATRIX'` |
|
|
149
|
+
| **Visibility helper** | `getVisibleStatementRowsForAnswers` (rows); columns from tick marks config |
|
|
150
|
+
| **Answer logic scope** | Statement rows |
|
|
151
|
+
| **Skip / display / variants / numbering** | SDK auto; tick marks from `tickMarks` array |
|
|
152
|
+
| **Custom field logic** | 4th arg on statement-row helper |
|
|
153
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } }` |
|
|
154
|
+
| **Value derivation** | Stored value from tick mark / slider position per row |
|
|
155
|
+
| **Performance notes** | One slider per visible row; debounce drag if partial save enabled |
|
|
156
|
+
| **Docs to read** | [`07-slider-matrix-scale.md`](../01-components/07-slider-matrix-scale.md) · [`10-slider-matrix.md`](../02-reference/question-types/10-slider-matrix.md) · [`09-slider-matrix.md`](../03-ui-specs/09-slider-matrix.md) |
|
|
157
|
+
| **Known limitations** | Legacy standalone `slider` type maps here — not a separate literal |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## FILE_UPLOAD
|
|
162
|
+
|
|
163
|
+
| Column | Value |
|
|
164
|
+
|--------|-------|
|
|
165
|
+
| **SDK type + answer shape** | `FileUploadQuestion`; answer `UploadedFileAnswer[]` (`{ id, mediaUrl, name?, mimeType?, size? }`) |
|
|
166
|
+
| **Component file(s)** | `FileUploadScale.tsx` |
|
|
167
|
+
| **Dispatcher branch** | `QUESTION_TYPE.FILE_UPLOAD` / `'FILE_UPLOAD'` |
|
|
168
|
+
| **Visibility helper** | None |
|
|
169
|
+
| **Answer logic scope** | None |
|
|
170
|
+
| **Skip / display / variants / numbering** | SDK auto |
|
|
171
|
+
| **Custom field logic** | Skip/display only |
|
|
172
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: UploadedFileAnswer[] } }` **after** presigned upload completes |
|
|
173
|
+
| **Value derivation** | SDK submit maps to `quesIdVsAttachmentDetails` — raw `File[]` is **not submitted** |
|
|
174
|
+
| **Performance notes** | Local validate → POST presigned URL → PUT file → then CHANGE; show progress UI |
|
|
175
|
+
| **Docs to read** | [`file-upload-aws.md`](file-upload-aws.md) · [`08-file-upload-scale.md`](../01-components/08-file-upload-scale.md) · [`11-file-upload.md`](../02-reference/question-types/11-file-upload.md) · [`10-file-upload.md`](../03-ui-specs/10-file-upload.md) |
|
|
176
|
+
| **Known limitations** | Must wire upload API; see [`file-upload-aws.md`](file-upload-aws.md) |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## TEXT_AND_MEDIA
|
|
181
|
+
|
|
182
|
+
| Column | Value |
|
|
183
|
+
|--------|-------|
|
|
184
|
+
| **SDK type + answer shape** | `TextAndMediaQuestion`; **no answer** |
|
|
185
|
+
| **Component file(s)** | Inline in `Question.tsx` |
|
|
186
|
+
| **Dispatcher branch** | `QUESTION_TYPE.TEXT_AND_MEDIA` / `'TEXT_AND_MEDIA'` |
|
|
187
|
+
| **Visibility helper** | None |
|
|
188
|
+
| **Answer logic scope** | None |
|
|
189
|
+
| **Skip / display / variants / numbering** | SDK auto; container media from survey shell |
|
|
190
|
+
| **Custom field logic** | Skip/display only |
|
|
191
|
+
| **`onAction` pattern** | No CHANGE — user advances with NEXT |
|
|
192
|
+
| **Value derivation** | N/A |
|
|
193
|
+
| **Performance notes** | Lazy-load media; respect alignment from ui-spec |
|
|
194
|
+
| **Docs to read** | [`02-question.md`](../01-components/02-question.md) · [`11-text-and-media.md`](../03-ui-specs/11-text-and-media.md) |
|
|
195
|
+
| **Known limitations** | Display-only; excluded from partial-save answer payload |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## HEATMAP
|
|
200
|
+
|
|
201
|
+
| Column | Value |
|
|
202
|
+
|--------|-------|
|
|
203
|
+
| **SDK type + answer shape** | `HeatmapQuestion`; answer `HeatmapClickPoint[]` (array of `{ id, x, y }` normalized 0–1) |
|
|
204
|
+
| **Component file(s)** | `HeatmapScale.tsx` + portable coord helper (see [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts)) |
|
|
205
|
+
| **Dispatcher branch** | `QUESTION_TYPE.HEATMAP` / `'HEATMAP'` |
|
|
206
|
+
| **Visibility helper** | None |
|
|
207
|
+
| **Answer logic scope** | None |
|
|
208
|
+
| **Skip / display / variants / numbering** | SDK auto |
|
|
209
|
+
| **Custom field logic** | Skip/display only |
|
|
210
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: HeatmapClickPoint[] \| undefined } }` on each click add/remove |
|
|
211
|
+
| **Value derivation** | Normalized 0–1 coordinates; respect `maxClicksAllowed` |
|
|
212
|
+
| **Performance notes** | Normalize coords on image load; use `findSpotNearClick` for hit testing |
|
|
213
|
+
| **Docs to read** | [`17-heatmap-scale.md`](../01-components/17-heatmap-scale.md) · [`12-heatmap.md`](../02-reference/question-types/12-heatmap.md) · [`13-heatmap.md`](../03-ui-specs/13-heatmap.md) |
|
|
214
|
+
| **Known limitations** | Coord helper is client code — copy template, not monorepo-only path |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## RANK_ORDER
|
|
219
|
+
|
|
220
|
+
| Column | Value |
|
|
221
|
+
|--------|-------|
|
|
222
|
+
| **SDK type + answer shape** | `RankOrderQuestion`; answer `RankOrderAnswers` (`Record<optionId, rank>`) |
|
|
223
|
+
| **Component file(s)** | `RankOrderScale.tsx` — requires `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` |
|
|
224
|
+
| **Dispatcher branch** | `QUESTION_TYPE.RANK_ORDER` / `'RANK_ORDER'` |
|
|
225
|
+
| **Visibility helper** | `getVisibleRankOrderOptionsForAnswers` |
|
|
226
|
+
| **Answer logic scope** | Options |
|
|
227
|
+
| **Skip / display / variants / numbering** | SDK auto |
|
|
228
|
+
| **Custom field logic** | 4th arg on rank visibility helper |
|
|
229
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: RankOrderAnswers } }` after reorder |
|
|
230
|
+
| **Value derivation** | Use `assignRankWithoutDuplicates`, `buildRankOrderFromOrderedOptionIds`, `getOrderedOptionIdsFromRanks`, `normalizeRankOrderAnswers` |
|
|
231
|
+
| **Performance notes** | Prefer `option.imageUrl` over `previewImageUrl`; dnd-kit sensors for touch |
|
|
232
|
+
| **Docs to read** | [`18-rank-order-scale.md`](../01-components/18-rank-order-scale.md) · [`13-rank-order.md`](../02-reference/question-types/13-rank-order.md) · [`14-rank-order.md`](../03-ui-specs/14-rank-order.md) |
|
|
233
|
+
| **Known limitations** | Image preview URLs may 403 on static deploy — use full `imageUrl` |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Agent workflow
|
|
238
|
+
|
|
239
|
+
1. Inventory types from fetched survey → one row per type above.
|
|
240
|
+
2. Fill [`implementation_plan.md`](../templates/implementation_plan.md) §3d from this matrix.
|
|
241
|
+
3. For each type, read blueprint + reference + uiSpec from MANIFEST `questionTypes.{key}`.
|
|
242
|
+
4. Wire lifecycle features per [`survey-lifecycle-analytics.md`](survey-lifecycle-analytics.md).
|
|
243
|
+
5. Wire placeholders per [`placeholders-and-tokens.md`](placeholders-and-tokens.md).
|
|
244
|
+
6. Enable partial save per [`partial-save-and-recovery.md`](partial-save-and-recovery.md) when required.
|
|
@@ -41,6 +41,41 @@ import { useSurveySDK } from '@explorer02/cfm-survey-sdk';
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
## Next.js static export for AWS deploy {#nextjs-aws-static-export}
|
|
45
|
+
|
|
46
|
+
Required when deploying to **Sprinklr AWS** (S3 + CloudFront via EC2 middle API). See [`aws-deploy.md`](aws-deploy.md) for the full deploy procedure.
|
|
47
|
+
|
|
48
|
+
**`next.config.js`** — add static export settings alongside `transpilePackages`:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
/** @type {import('next').NextConfig} */
|
|
52
|
+
const nextConfig = {
|
|
53
|
+
reactStrictMode: true,
|
|
54
|
+
transpilePackages: ['@explorer02/cfm-survey-sdk'], // or '@repo/sdk'
|
|
55
|
+
output: 'export',
|
|
56
|
+
assetPrefix: './',
|
|
57
|
+
images: { unoptimized: true },
|
|
58
|
+
};
|
|
59
|
+
module.exports = nextConfig;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**`package.json`** — copy RSC payload so `index.html` URLs work on CloudFront:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
"build": "next build && cp out/index.txt out/index.html.txt"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Setting | Why |
|
|
69
|
+
|---------|-----|
|
|
70
|
+
| `output: 'export'` | Produces uploadable `out/` folder |
|
|
71
|
+
| `assetPrefix: './'` | Assets work under nested `/sites/{surveyId}/{deployId}/` path |
|
|
72
|
+
| `images.unoptimized` | No server for Next Image Optimization |
|
|
73
|
+
| `index.html.txt` copy | Fixes 403 on `index.html.txt?_rsc` requests |
|
|
74
|
+
|
|
75
|
+
**Vite:** set `OUT_DIR` to `dist/` at deploy time. Use `base: './'` in `vite.config.ts` if built HTML uses absolute `/` asset paths.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
44
79
|
## Vite + React {#vite}
|
|
45
80
|
|
|
46
81
|
```bash
|
|
@@ -67,6 +102,24 @@ If you hit ESM resolution errors, migrate to Vite or use CRACO to transpile `nod
|
|
|
67
102
|
|
|
68
103
|
---
|
|
69
104
|
|
|
105
|
+
## Monorepo (`@repo/sdk`) {#monorepo}
|
|
106
|
+
|
|
107
|
+
When the client app lives in the same monorepo as the SDK:
|
|
108
|
+
|
|
109
|
+
1. Ensure `package.json` depends on `@repo/sdk` (workspace link).
|
|
110
|
+
2. **Build the SDK before running the client dev server:**
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cd packages/sdk && npm run build
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
3. Use `@repo/sdk` in imports and set `transpilePackages: ['@repo/sdk']` in Next.js config.
|
|
117
|
+
4. Rebuild the SDK after any change to `packages/sdk/src/**` — the client reads from `packages/sdk/dist/`.
|
|
118
|
+
|
|
119
|
+
Missing `dist/index.mjs` causes runtime module-not-found errors. See [`constraints.md`](constraints.md) troubleshooting.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
70
123
|
## Framework Detection (for agents)
|
|
71
124
|
|
|
72
125
|
```
|
|
@@ -78,3 +131,34 @@ else → ask client which React setup to use
|
|
|
78
131
|
```
|
|
79
132
|
|
|
80
133
|
For **existing apps**, read `package.json` and project structure — do not scaffold from scratch unless the client asks.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Survey-type conditional dependencies
|
|
138
|
+
|
|
139
|
+
After fetching the survey, install extra packages only when those types appear:
|
|
140
|
+
|
|
141
|
+
### RANK_ORDER — dnd-kit (required when survey contains RANK_ORDER)
|
|
142
|
+
|
|
143
|
+
Install whenever the survey inventory includes **`RANK_ORDER`** — the portable template supports both dropdown and drag-and-drop modes:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Copy [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx) before building. See [`18-rank-order-scale.md`](../01-components/18-rank-order-scale.md).
|
|
150
|
+
|
|
151
|
+
### HEATMAP — coordinate normalization
|
|
152
|
+
|
|
153
|
+
When the survey contains `HEATMAP`, copy the portable coord helper into your project:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Example destination — adjust to your src layout
|
|
157
|
+
cp node_modules/@explorer02/cfm-survey-sdk/templates/docs/templates/heatmapCoords.ts src/lib/heatmapCoords.ts
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Monorepo agents: copy from `packages/sdk/templates/docs/templates/heatmapCoords.ts`.
|
|
161
|
+
|
|
162
|
+
Import `getNormalizedClickFromImage`, `findSpotNearClick`, and `createNewSpot` in `HeatmapScale.tsx`. Do **not** reference monorepo-only paths like `apps/client/src/lib/...` in shipped agent docs.
|
|
163
|
+
|
|
164
|
+
See [`17-heatmap-scale.md`](../01-components/17-heatmap-scale.md) and [`question-type-sdk-matrix.md#heatmap`](question-type-sdk-matrix.md#heatmap).
|