@explorer02/cfm-survey-sdk 0.2.2 → 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/package.json +1 -1
- package/templates/AGENT.md +8 -4
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/client-integration-guide.md +1 -1
- package/templates/docs/00-integration/component-checklist.md +10 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- 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 +24 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +3 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/01-components/06-likert-matrix-scale.md +2 -1
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/03-ui-specs/01-rating.md +10 -2
- package/templates/docs/03-ui-specs/02-radio.md +80 -25
- package/templates/docs/03-ui-specs/04-csat.md +35 -5
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +20 -4
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +5 -1
- package/templates/docs/03-ui-specs/09-slider-matrix.md +17 -0
- package/templates/docs/03-ui-specs/10-file-upload.md +41 -20
- package/templates/docs/03-ui-specs/12-survey-chrome.md +38 -0
- package/templates/docs/03-ui-specs/13-heatmap.md +9 -2
- package/templates/docs/03-ui-specs/14-rank-order.md +98 -29
- package/templates/docs/03-ui-specs/README.md +7 -5
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/MANIFEST.json +28 -3
- package/templates/docs/index.md +5 -4
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/implementation_plan.md +20 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +32 -0
package/package.json
CHANGED
package/templates/AGENT.md
CHANGED
|
@@ -20,20 +20,24 @@ Import from whatever appears in `package.json`:
|
|
|
20
20
|
| 0 | Detect framework; extract branding from client prompt |
|
|
21
21
|
| 1 | Scaffold React app — `00-integration/setup.md` |
|
|
22
22
|
| 2 | Ask client for instanceId JWT |
|
|
23
|
-
| 3 | Read `client-integration-guide.md` + `question-type-sdk-matrix.md` + hook/logic/lifecycle docs — full SDK contract |
|
|
24
|
-
| 4 | Fetch survey; inventory types; read MANIFEST-driven docs per type
|
|
25
|
-
| 5 | Build UI —
|
|
23
|
+
| 3 | Read `client-integration-guide.md` + `question-type-sdk-matrix.md` + hook/logic/lifecycle/analytics docs — full SDK contract |
|
|
24
|
+
| 4 | Fetch survey; inventory types; read MANIFEST-driven docs per type; copy `templates/Question.tsx` + scale templates + `implementation_plan.md` |
|
|
25
|
+
| 5 | Build UI — copy templates from `docs/templates/`; read ui-spec Implementation Recipes; dispatch on **11** flat `QUESTION_TYPE` literals |
|
|
26
26
|
| 6 | Run `templates/verify-agent-build.sh` |
|
|
27
|
-
| 7 | On confirmation: **AWS prompt** → `
|
|
27
|
+
| 7 | On confirmation: **AWS prompt** → `aws-deploy.md`; **Vercel prompt** → `npx cfm-sdk deploy` |
|
|
28
28
|
|
|
29
29
|
**Do NOT** run `cfm-sdk init`, read `survey-config.json`, or use any setup wizard.
|
|
30
30
|
|
|
31
31
|
**Do NOT** render `"not yet implemented"` or default fallback stubs in `Question.tsx`. Copy `templates/docs/templates/Question.tsx` and create all required scale components.
|
|
32
32
|
|
|
33
|
+
**Copy scale templates from `docs/templates/`** before implementing from ui-spec recipes: `RankOrderScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx`, `surveyUiIcons.tsx`, `heatmapCoords.ts`.
|
|
34
|
+
|
|
33
35
|
**Do NOT** route matrix questions by `subType` — use flat types: `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX`, `SLIDER_MATRIX`.
|
|
34
36
|
|
|
35
37
|
**Read `question-type-sdk-matrix.md` before building any scale component** — it defines answer shapes, visibility helpers, and performance expectations for all 11 types.
|
|
36
38
|
|
|
39
|
+
**FILE_UPLOAD:** wire presigned upload per `00-integration/file-upload-aws.md` — raw `File[]` does not submit.
|
|
40
|
+
|
|
37
41
|
**Monorepo:** run `npm run build` in `packages/sdk` before starting the client dev server.
|
|
38
42
|
|
|
39
43
|
Follow the master index strictly. Do not guess SDK internals. Do not import from `/src/` paths.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Analytics Events Catalog
|
|
2
|
+
|
|
3
|
+
> Consolidated SDK telemetry and client-wired lifecycle events. Most events log to console as `[SDK:ANALYTICS]` — **no client code required** unless noted.
|
|
4
|
+
|
|
5
|
+
See also: [`survey-lifecycle-analytics.md`](survey-lifecycle-analytics.md) · [`client-integration-guide.md`](client-integration-guide.md) § Debug logging
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Client must wire
|
|
10
|
+
|
|
11
|
+
| Event / action | How | When |
|
|
12
|
+
|----------------|-----|------|
|
|
13
|
+
| Mark as started | `onAction({ type: 'SCROLL' })` once on first window scroll | First content engagement |
|
|
14
|
+
| Question viewed | `onAction({ type: 'RECORD_QUESTION_VIEW', payload: { questionId } })` via IntersectionObserver on `#question.id` | Question 50% visible |
|
|
15
|
+
| Partial save feedback | Monitor `partialSaveResults.isLoading` / `.error` | When `savePartialResponse: true` |
|
|
16
|
+
|
|
17
|
+
First `CHANGE` also triggers mark-as-started (SDK) — no extra client call.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## SDK-emitted (always — no `debug` flag required)
|
|
22
|
+
|
|
23
|
+
| Event | When | Metadata highlights |
|
|
24
|
+
|-------|------|---------------------|
|
|
25
|
+
| `fetch_survey_latency` | Survey GET completes | durationMs, status, payloadSize (UTF-8 KB) |
|
|
26
|
+
| `fetch_survey_error` | Survey GET fails | status, url, message |
|
|
27
|
+
| `submit_survey_latency` | Submit POST completes | durationMs, status |
|
|
28
|
+
| `submit_survey_error` | Submit fails | status, message |
|
|
29
|
+
| `page_time_spent` | Leaving a content page | pageIndex, durationMs |
|
|
30
|
+
| `optional_question_skipped` | Optional question left blank on forward nav | questionId, pageIndex |
|
|
31
|
+
| `backtrack_revisited` | User returns to earlier page and changes answers | fromPage, toPage, changedAnswers |
|
|
32
|
+
| `question_viewed` | After RECORD_QUESTION_VIEW deduped | questionId |
|
|
33
|
+
| `display_logic_matched` | Display condition newly true | questionId, logic details |
|
|
34
|
+
| `answer_logic_matched` | Answer-logic sub-entity newly visible | entity scope |
|
|
35
|
+
| `question_variant_matched` | Variant text switches | variant id |
|
|
36
|
+
| `end_page_logic_matched` | Resolved end page changes | endPageId |
|
|
37
|
+
| `skip_logic_triggered` | Skip logic newly activates | skip target |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Post-submit rollups
|
|
42
|
+
|
|
43
|
+
| Event | When | Contents |
|
|
44
|
+
|-------|------|----------|
|
|
45
|
+
| `survey_session_summary` | Successful submit | `completed`, `maxVisitedPageIndex`, `totalDurationMs`, question counts (total/answered/skipped), `validationErrorsCount`, `backtrackCount`, `validationHistory` |
|
|
46
|
+
| `logic_session_summary` | Successful submit | Per-logic-kind counts, viewed vs on-path-not-viewed, final end page, `logicEventTimeline` audit (console.table) |
|
|
47
|
+
|
|
48
|
+
Enable `debug: true` in hook options for additional `[SDK:INFO]` transition logs (display/answer/variant/end/skip matches on answer change).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Validation error tracking
|
|
53
|
+
|
|
54
|
+
SDK increments validation error counts and records per-page error history in session summary. Client displays `state.validationErrors[questionId]` banners and uses `id={question.id}` for scroll-to-error.
|
|
@@ -61,7 +61,7 @@ Reference implementation: `apps/client/src/components/SurveyPage.tsx`.
|
|
|
61
61
|
| Per-type integration | Read [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md) before building scales | Answer shapes, visibility helpers, performance notes per type |
|
|
62
62
|
| Validation scroll | `id={question.id}` on question wrapper | Scroll-to-first-error on failed NEXT/SUBMIT |
|
|
63
63
|
|
|
64
|
-
Deep dives: [`useSurveySDK.md`](useSurveySDK.md) · [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md) · [`placeholders-and-tokens.md`](placeholders-and-tokens.md) · [`partial-save-and-recovery.md`](partial-save-and-recovery.md) · [`survey-lifecycle-analytics.md`](survey-lifecycle-analytics.md) · [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) · [`display-logic-and-navigation.md`](display-logic-and-navigation.md) · [`question-display-variants.md`](question-display-variants.md) · [`question-numbering.md`](question-numbering.md) · [`end-page-logic.md`](end-page-logic.md) · [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) · [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) · [`progress.md`](progress.md) · [`constraints.md`](constraints.md)
|
|
64
|
+
Deep dives: [`useSurveySDK.md`](useSurveySDK.md) · [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md) · [`placeholders-and-tokens.md`](placeholders-and-tokens.md) · [`partial-save-and-recovery.md`](partial-save-and-recovery.md) · [`survey-lifecycle-analytics.md`](survey-lifecycle-analytics.md) · [`analytics-events-catalog.md`](analytics-events-catalog.md) · [`logic-fields-catalog.md`](logic-fields-catalog.md) · [`file-upload-aws.md`](file-upload-aws.md) · [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) · [`display-logic-and-navigation.md`](display-logic-and-navigation.md) · [`question-display-variants.md`](question-display-variants.md) · [`question-numbering.md`](question-numbering.md) · [`end-page-logic.md`](end-page-logic.md) · [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) · [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) · [`progress.md`](progress.md) · [`constraints.md`](constraints.md)
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
@@ -12,6 +12,16 @@ From `survey.pages`, collect unique `question.type` values — see `MANIFEST.jso
|
|
|
12
12
|
|
|
13
13
|
## Step 2: Create Required Files
|
|
14
14
|
|
|
15
|
+
**Copy portable templates from [`templates/`](../templates/) when available** — then customize branding. Otherwise implement from ui-spec Implementation Recipe.
|
|
16
|
+
|
|
17
|
+
| Portable template | Copy to |
|
|
18
|
+
|-------------------|---------|
|
|
19
|
+
| `templates/Question.tsx` | `src/components/Question.tsx` |
|
|
20
|
+
| `templates/RankOrderScale.tsx` | `src/components/RankOrderScale.tsx` |
|
|
21
|
+
| `templates/MatrixDropdown.tsx` | `src/components/MatrixDropdown.tsx` |
|
|
22
|
+
| `templates/CustomSliderTrack.tsx` + `surveyUiIcons.tsx` | same folder |
|
|
23
|
+
| `templates/heatmapCoords.ts` | `src/lib/heatmapCoords.ts` |
|
|
24
|
+
|
|
15
25
|
| If survey contains | Agent MUST create before `npm run build` | UI spec |
|
|
16
26
|
|--------------------|------------------------------------------|---------|
|
|
17
27
|
| `NPS_SCALE` | `RatingScale.tsx` | `03-ui-specs/01-rating.md` |
|
|
@@ -46,4 +46,19 @@ Display logic may gate questions on **custom field** values (`conditionEntity.en
|
|
|
46
46
|
|
|
47
47
|
Alternate question wording (`questionDisplayVariants`) uses the same condition engine but **does not hide** the question — the SDK resolves `questionText` on `state.currentQuestions`. See [`question-display-variants.md`](question-display-variants.md).
|
|
48
48
|
|
|
49
|
-
See also: [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md), [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`progress.md`](progress.md).
|
|
49
|
+
See also: [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md), [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`progress.md`](progress.md), [`logic-fields-catalog.md`](logic-fields-catalog.md).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Worked example — dynamic display on same page
|
|
54
|
+
|
|
55
|
+
**Setup:** Page 1 has Q1 (MCQ trigger) and Q2 (display-gated, `isDynamicAppearanceEnabled: true`). Q2 hidden until Q1 selects option A.
|
|
56
|
+
|
|
57
|
+
1. User loads page → `currentQuestions` = [Q1] only; progress denominator excludes Q2.
|
|
58
|
+
2. User selects A → SDK evaluates display logic → `currentQuestions` = [Q1, Q2]; Q2 animates in; progress total increases.
|
|
59
|
+
3. User answers Q2 → normal CHANGE flow.
|
|
60
|
+
4. User changes Q1 away from A → SDK sanitizes Q2 answer; Q2 hidden again; progress credits adjust.
|
|
61
|
+
|
|
62
|
+
**Client:** render `state.currentQuestions` only — no manual show/hide. Re-run IntersectionObserver effect when `currentQuestions` changes.
|
|
63
|
+
|
|
64
|
+
**Back navigation:** if display logic hides all questions on a page, SDK skips empty pages — user never sees a blank screen (see skip/display integration tests).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# File Upload — Presigned S3 Flow
|
|
2
|
+
|
|
3
|
+
> Required for **submittable** file-upload answers. Raw `File[]` in `state.answers` is **dropped** at submit — SDK expects `UploadedFileAnswer[]` with `id` and `mediaUrl`.
|
|
4
|
+
|
|
5
|
+
Related: [`03-ui-specs/10-file-upload.md`](../03-ui-specs/10-file-upload.md) · [`02-reference/question-types/11-file-upload.md`](../02-reference/question-types/11-file-upload.md)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Answer contract
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
type UploadedFileAnswer = {
|
|
13
|
+
id: string;
|
|
14
|
+
mediaUrl: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
onAction({
|
|
21
|
+
type: 'CHANGE',
|
|
22
|
+
payload: { questionId, answerValue: UploadedFileAnswer[] },
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Submit formatter maps to `quesIdVsAttachmentDetails[questionId]`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Upload API (EC2 reference — Day 22)
|
|
31
|
+
|
|
32
|
+
**Base URL:** configure via env (example: `http://43.204.26.213:3000` or project-specific upload service).
|
|
33
|
+
|
|
34
|
+
### Step 1 — Request presigned URL
|
|
35
|
+
|
|
36
|
+
```http
|
|
37
|
+
POST /get-upload-url
|
|
38
|
+
Content-Type: application/json
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
"fileName": "report.pdf",
|
|
42
|
+
"contentType": "application/pdf",
|
|
43
|
+
"questionId": "<question.id>"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Response:**
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"uploadUrl": "https://s3.amazonaws.com/...",
|
|
52
|
+
"fileId": "generated-id",
|
|
53
|
+
"mediaUrl": "https://cdn.../path/to/file"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 2 — PUT file to S3
|
|
58
|
+
|
|
59
|
+
```http
|
|
60
|
+
PUT {uploadUrl}
|
|
61
|
+
Content-Type: application/pdf
|
|
62
|
+
Body: <file binary>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 3 — Update SDK state
|
|
66
|
+
|
|
67
|
+
Append to answer array and call `onAction(CHANGE)` with `{ id: fileId, mediaUrl, name, mimeType, size }`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Client implementation sketch
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
async function uploadFile(file: File, questionId: string): Promise<UploadedFileAnswer> {
|
|
75
|
+
const res = await fetch(`${UPLOAD_API_BASE}/get-upload-url`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
fileName: file.name,
|
|
80
|
+
contentType: file.type,
|
|
81
|
+
questionId,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
const { uploadUrl, fileId, mediaUrl } = await res.json();
|
|
85
|
+
await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } });
|
|
86
|
+
return { id: fileId, mediaUrl, name: file.name, mimeType: file.type, size: file.size };
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Wire `UPLOAD_API_BASE` from env — **never** embed AWS credentials in client code.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## UI states
|
|
95
|
+
|
|
96
|
+
| State | UX |
|
|
97
|
+
|-------|-----|
|
|
98
|
+
| Uploading | Spinner on file row; disable dropzone |
|
|
99
|
+
| Success | Show file in list; call `onAction(CHANGE)` |
|
|
100
|
+
| Failure | Red banner; keep local file uncommitted |
|
|
101
|
+
| Remove | Update array; `undefined` when empty |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Partial save
|
|
106
|
+
|
|
107
|
+
Only call `onAction(CHANGE)` after upload completes — partial save should not POST raw `File` blobs.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Agent checklist
|
|
112
|
+
|
|
113
|
+
- [ ] Local validation before upload (format, count, size)
|
|
114
|
+
- [ ] Presigned PUT wired per file
|
|
115
|
+
- [ ] Final answer is `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
116
|
+
- [ ] Submit body includes attachments (verify in network tab)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Logic Fields Catalog
|
|
2
|
+
|
|
3
|
+
> Central reference for skip, display, answer, variant, and end-page logic fields by question type. Agents do **not** implement evaluators — wire UI + hook; SDK evaluates conditions.
|
|
4
|
+
|
|
5
|
+
Cross-links: [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) · [`display-logic-and-navigation.md`](display-logic-and-navigation.md) · [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) · [`end-page-logic.md`](end-page-logic.md)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## MCQ
|
|
10
|
+
|
|
11
|
+
| Field | Used in | Operators | Notes |
|
|
12
|
+
|-------|---------|-----------|-------|
|
|
13
|
+
| `SELECTED_OPTIONS` / option id | skip, display, end | contains, does not contain, equals | Single or multi option ids |
|
|
14
|
+
| `NUM_OF_SELECTED_OPTIONS` | skip, display, end | `EQUALS`, `GT`, `LT`, etc. | Count of selected option ids; zero = missing for EXISTS |
|
|
15
|
+
| Option-level answer logic | answer (UI) | same engine | `getVisibleMcqOptionsForAnswers` |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## TEXTFIELD / TEXT_AND_MEDIA
|
|
20
|
+
|
|
21
|
+
| Field | Used in | Operators |
|
|
22
|
+
|-------|---------|-----------|
|
|
23
|
+
| `TEXT` / `CASE_SENSITIVE_TEXT` | skip, display, end | empty, contains, equals, etc. |
|
|
24
|
+
| TEXT_AND_MEDIA | — | no answer stored |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## NPS_SCALE
|
|
29
|
+
|
|
30
|
+
| Field | Used in | Operators |
|
|
31
|
+
|-------|---------|-----------|
|
|
32
|
+
| `TEN_POINT_SCALE` / scale value | skip, display, end | numeric compare `<`, `>`, `==`, `!=` |
|
|
33
|
+
| Scale point answer logic | answer (UI) | `getVisibleScaleColumnsForAnswers` |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## CFM_MATRIX / CSAT_MATRIX / RATING_MATRIX
|
|
38
|
+
|
|
39
|
+
| Field | Used in | Operators |
|
|
40
|
+
|-------|---------|-----------|
|
|
41
|
+
| Per-row composite keys | skip, display | per statement row + column id |
|
|
42
|
+
| Matrix cell values | skip, display | numeric or column id match |
|
|
43
|
+
| Row/column answer logic | answer (UI) | `getVisibleMatrixItemsForAnswers` |
|
|
44
|
+
|
|
45
|
+
Store answers as column **`id`** via `matrixColumnStoredValue`.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## SLIDER_MATRIX
|
|
50
|
+
|
|
51
|
+
| Field | Used in | Operators |
|
|
52
|
+
|-------|---------|-----------|
|
|
53
|
+
| Row statement values | skip, display | numeric compare |
|
|
54
|
+
| Row answer logic | answer (UI) | `getVisibleStatementRowsForAnswers` |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## FILE_UPLOAD
|
|
59
|
+
|
|
60
|
+
| Field | Used in | Operators |
|
|
61
|
+
|-------|---------|-----------|
|
|
62
|
+
| Presence only | skip, display | `EXISTS`, `MISSING` |
|
|
63
|
+
| Evaluation | on page entry | no discrete value-change events |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## HEATMAP
|
|
68
|
+
|
|
69
|
+
| Field | Used in | Operators |
|
|
70
|
+
|-------|---------|-----------|
|
|
71
|
+
| `HEATMAP` | skip, display | spot count, presence |
|
|
72
|
+
| `HEATMAP_REGION` | skip, display | region label match |
|
|
73
|
+
|
|
74
|
+
Regions are **logic-only** — not rendered on respondent UI.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## RANK_ORDER
|
|
79
|
+
|
|
80
|
+
| Field | Used in | Operators |
|
|
81
|
+
|-------|---------|-----------|
|
|
82
|
+
| `RANK_ORDER` (per option) | skip, display, answer | ranked as (`EQUALS`), ranked more/less than (`GT`/`LT`), ranked above/below (`BEFORE`/`AFTER`) |
|
|
83
|
+
| Whole question | skip, display | `EXISTS`, `MISSING` |
|
|
84
|
+
|
|
85
|
+
See [`02-reference/question-types/13-rank-order.md`](../02-reference/question-types/13-rank-order.md).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## CUSTOM_FIELD (all types)
|
|
90
|
+
|
|
91
|
+
| Field | Used in | Value shapes |
|
|
92
|
+
|-------|---------|--------------|
|
|
93
|
+
| `_c_*` entity ids | skip, display, end, answer | TEXT, NUMBER, PICKLIST, DATETIME, multiselect |
|
|
94
|
+
|
|
95
|
+
Pass stable `customFieldValues` to hook **and** visibility helpers. See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Client wiring summary
|
|
100
|
+
|
|
101
|
+
| Logic kind | Client action |
|
|
102
|
+
|------------|---------------|
|
|
103
|
+
| Skip / display / end / variants | Render `state.currentQuestions`; no client evaluators |
|
|
104
|
+
| Answer logic | `getVisible*ForAnswers(..., customFieldValues?)` before render |
|
|
105
|
+
| Custom fields | Two wiring points: hook options + Question visibility helpers |
|
|
@@ -63,6 +63,30 @@ Do not block navigation (NEXT/PREVIOUS) on partial save failure unless product e
|
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
|
+
## Per-type debounce guidance
|
|
67
|
+
|
|
68
|
+
| Question type | Client UI note |
|
|
69
|
+
|---------------|----------------|
|
|
70
|
+
| TEXTFIELD (long) | Debounce `onAction(CHANGE)` 300–500 ms to reduce partial-save churn |
|
|
71
|
+
| SLIDER_MATRIX / graphics | Debounce slider `onChange` or rely on SDK debounce |
|
|
72
|
+
| FILE_UPLOAD | Call `CHANGE` only **after** presigned upload completes — see [`file-upload-aws.md`](file-upload-aws.md) |
|
|
73
|
+
| MCQ / matrix | Immediate `CHANGE` on selection is fine |
|
|
74
|
+
| TEXT_AND_MEDIA | No CHANGE — excluded from partial save payload |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Recovery (honest scope)
|
|
79
|
+
|
|
80
|
+
Reload behavior depends on **backend** + instance configuration — the SDK debounces POST to `responseV2` with `surveySubmitted: false` but does not implement client-side resume UI. If product requires resume:
|
|
81
|
+
|
|
82
|
+
- Enable `savePartialResponse`
|
|
83
|
+
- Optionally show `partialSaveResults` status
|
|
84
|
+
- Confirm with backend team whether refetch restores `state.answers`
|
|
85
|
+
|
|
86
|
+
Path-change sanitization (skip/display) clears off-path answers before partial save — see [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
66
90
|
## Verify checklist
|
|
67
91
|
|
|
68
92
|
- [ ] `savePartialResponse` only set when required
|
|
@@ -38,7 +38,9 @@ Legend: **opt** = option-level; **row+col** = statement rows and scale columns;
|
|
|
38
38
|
| **Value derivation** | Store `option.id`; SDK maps to API value at submit |
|
|
39
39
|
| **Performance notes** | Filter options once per render via helper; avoid re-fetch on option click |
|
|
40
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
|
-
| **
|
|
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` |
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
@@ -160,18 +162,18 @@ Legend: **opt** = option-level; **row+col** = statement rows and scale columns;
|
|
|
160
162
|
|
|
161
163
|
| Column | Value |
|
|
162
164
|
|--------|-------|
|
|
163
|
-
| **SDK type + answer shape** | `FileUploadQuestion`; answer `
|
|
165
|
+
| **SDK type + answer shape** | `FileUploadQuestion`; answer `UploadedFileAnswer[]` (`{ id, mediaUrl, name?, mimeType?, size? }`) |
|
|
164
166
|
| **Component file(s)** | `FileUploadScale.tsx` |
|
|
165
167
|
| **Dispatcher branch** | `QUESTION_TYPE.FILE_UPLOAD` / `'FILE_UPLOAD'` |
|
|
166
168
|
| **Visibility helper** | None |
|
|
167
169
|
| **Answer logic scope** | None |
|
|
168
170
|
| **Skip / display / variants / numbering** | SDK auto |
|
|
169
171
|
| **Custom field logic** | Skip/display only |
|
|
170
|
-
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue:
|
|
171
|
-
| **Value derivation** | SDK submit maps
|
|
172
|
-
| **Performance notes** |
|
|
173
|
-
| **Docs to read** | [`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) |
|
|
174
|
-
| **Known limitations** |
|
|
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) |
|
|
175
177
|
|
|
176
178
|
---
|
|
177
179
|
|
|
@@ -198,14 +200,14 @@ Legend: **opt** = option-level; **row+col** = statement rows and scale columns;
|
|
|
198
200
|
|
|
199
201
|
| Column | Value |
|
|
200
202
|
|--------|-------|
|
|
201
|
-
| **SDK type + answer shape** | `HeatmapQuestion`; answer `
|
|
203
|
+
| **SDK type + answer shape** | `HeatmapQuestion`; answer `HeatmapClickPoint[]` (array of `{ id, x, y }` normalized 0–1) |
|
|
202
204
|
| **Component file(s)** | `HeatmapScale.tsx` + portable coord helper (see [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts)) |
|
|
203
205
|
| **Dispatcher branch** | `QUESTION_TYPE.HEATMAP` / `'HEATMAP'` |
|
|
204
206
|
| **Visibility helper** | None |
|
|
205
207
|
| **Answer logic scope** | None |
|
|
206
208
|
| **Skip / display / variants / numbering** | SDK auto |
|
|
207
209
|
| **Custom field logic** | Skip/display only |
|
|
208
|
-
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue:
|
|
210
|
+
| **`onAction` pattern** | `{ type: 'CHANGE', payload: { questionId, answerValue: HeatmapClickPoint[] \| undefined } }` on each click add/remove |
|
|
209
211
|
| **Value derivation** | Normalized 0–1 coordinates; respect `maxClicksAllowed` |
|
|
210
212
|
| **Performance notes** | Normalize coords on image load; use `findSpotNearClick` for hit testing |
|
|
211
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) |
|
|
@@ -138,15 +138,15 @@ For **existing apps**, read `package.json` and project structure — do not scaf
|
|
|
138
138
|
|
|
139
139
|
After fetching the survey, install extra packages only when those types appear:
|
|
140
140
|
|
|
141
|
-
### RANK_ORDER —
|
|
141
|
+
### RANK_ORDER — dnd-kit (required when survey contains RANK_ORDER)
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
Install whenever the survey inventory includes **`RANK_ORDER`** — the portable template supports both dropdown and drag-and-drop modes:
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
146
|
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
See [`18-rank-order-scale.md`](../01-components/18-rank-order-scale.md)
|
|
149
|
+
Copy [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx) before building. See [`18-rank-order-scale.md`](../01-components/18-rank-order-scale.md).
|
|
150
150
|
|
|
151
151
|
### HEATMAP — coordinate normalization
|
|
152
152
|
|
|
@@ -77,4 +77,4 @@ Event: `optional_question_skipped` with `questionId` and `pageIndex`.
|
|
|
77
77
|
- Use `state.canGoBack` for the Back button (not `currentPageIndex === 0`).
|
|
78
78
|
- Use `state.primaryButtonType` for Next vs Submit label; SDK maps `NEXT` → `SUBMIT` on the last page when appropriate.
|
|
79
79
|
|
|
80
|
-
See also: [`progress.md`](progress.md), [`useSurveySDK.md`](useSurveySDK.md), [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md).
|
|
80
|
+
See also: [`progress.md`](progress.md), [`useSurveySDK.md`](useSurveySDK.md), [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md), [`logic-fields-catalog.md`](logic-fields-catalog.md).
|
|
@@ -79,7 +79,7 @@ useEffect(() => {
|
|
|
79
79
|
|
|
80
80
|
## Debug: `logic_session_summary`
|
|
81
81
|
|
|
82
|
-
When `debug: true` is set in hook options, the SDK emits structured console events including lifecycle transitions.
|
|
82
|
+
When `debug: true` is set in hook options, the SDK emits structured console events including lifecycle transitions. Full event catalog: [`analytics-events-catalog.md`](analytics-events-catalog.md).
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -23,9 +23,10 @@ If `true`, the standard Row-by-Column matrix is inverted:
|
|
|
23
23
|
- **Architectural Requirement**: Your `<tbody>` map must iterate over `question.columns` first, and render `<td>` cells mapped from `question.rows`.
|
|
24
24
|
- Ensure `onSelect` still correctly pairs the original `row.id` with the original `col.value`.
|
|
25
25
|
|
|
26
|
-
### 2. `
|
|
26
|
+
### 2. `repeatColumnHeaders`
|
|
27
27
|
If `true`, the `<thead>` containing column labels must be re-injected periodically to prevent users from losing track of columns on long matrices.
|
|
28
28
|
- **Implementation Strategy**: In your row `map()`, if `index > 0 && index % 5 === 0`, return a React Fragment containing a cloned `<tr>` header row followed by the actual data `<tr>`.
|
|
29
|
+
- **Note:** API/config field is `repeatColumnHeaders` (not `repeatScale`).
|
|
29
30
|
|
|
30
31
|
### 3. `matrixType === 'bipolar'`
|
|
31
32
|
Standard matrices (`'likert'`) only show statement text on the left. Bipolar scales show text on BOTH sides of the radio buttons.
|
|
@@ -1,67 +1,30 @@
|
|
|
1
1
|
# File Upload Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
|
|
4
|
-
> **
|
|
5
|
-
> **
|
|
6
|
-
> **
|
|
4
|
+
> **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
5
|
+
> **Portable template:** implement per ui-spec (upload wiring is project-specific)
|
|
6
|
+
> **Target Component:** `FileUploadScale`
|
|
7
|
+
> **Handles:** `type: 'FILE_UPLOAD'`
|
|
7
8
|
|
|
8
9
|
## Core Responsibility
|
|
9
|
-
Render a robust drag-and-drop zone and enforce complex file size, type limits, and maximum file count constraints BEFORE updating the state.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- `selectedValue`: Expects an array of native `File` objects (or serialized equivalents).
|
|
13
|
-
- **Mutation Pattern**:
|
|
14
|
-
1. The user selects files.
|
|
15
|
-
2. The component intercepts the files.
|
|
16
|
-
3. The component validates them against ALL limits.
|
|
17
|
-
4. If valid, append the new files to `selectedValue` (handling it as an array).
|
|
18
|
-
5. Call `onSelect(updatedFilesArray)`.
|
|
19
|
-
|
|
20
|
-
## Configuration Matrix (CRITICAL FOR PLANNING)
|
|
21
|
-
|
|
22
|
-
Before calling `onSelect`, your component MUST validate against these properties:
|
|
23
|
-
|
|
24
|
-
### 1. `supportedFileFormats`
|
|
25
|
-
- Extracted from `question.supportedFileFormats` (e.g. `['PDF', 'PNG', 'JPG']`).
|
|
26
|
-
- Use this to build the `accept` attribute string for the hidden input: `question.supportedFileFormats?.map(f => \`.\${f.toLowerCase()}\`).join(',')`.
|
|
27
|
-
- You MUST reject files with extensions that are not in this list.
|
|
11
|
+
Render drag-and-drop zone, validate files locally, **upload via presigned URL**, then bubble `UploadedFileAnswer[]` via `onSelect`.
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
- Prevent uploads if `currentFiles.length + newFiles.length > maxFileCount`.
|
|
31
|
-
- Provide a clear local error message indicating the max limit.
|
|
32
|
-
|
|
33
|
-
### 3. `fileSizeLimit` & `fileSizeLimitType`
|
|
34
|
-
- `fileSizeLimit` is a number representing megabytes (MB).
|
|
35
|
-
- If `fileSizeLimitType === 'PER_FILE'`, validate that EVERY incoming file's `size` (in bytes) is `<= fileSizeLimit * 1024 * 1024`.
|
|
36
|
-
- If `fileSizeLimitType === 'IN_TOTAL'`, validate that the combined sum of all existing `selectedValue` sizes plus the incoming files' sizes is `<= fileSizeLimit * 1024 * 1024`.
|
|
37
|
-
|
|
38
|
-
## UI/UX Specifications
|
|
39
|
-
|
|
40
|
-
1. **Dropzone**:
|
|
41
|
-
- Wrap the main area with `onDragEnter`, `onDragOver`, `onDragLeave`, `onDrop`.
|
|
42
|
-
- **Inactive State**: `border-2 border-dashed border-gray-300 rounded-xl bg-gray-50 p-8 text-center cursor-pointer transition-all`.
|
|
43
|
-
- **Drag/Hover State**: `border-[#e20074] bg-[#fdf2f8]` (Using the brand primary color).
|
|
44
|
-
- Display a Cloud SVG icon (use gray, switch to magenta on hover).
|
|
45
|
-
- Render `question.uploadMessage` (which can contain HTML, so use `dangerouslySetInnerHTML`).
|
|
46
|
-
- Dynamically render the limits text (e.g., "Max 3 files, 10MB per file").
|
|
13
|
+
## State Management
|
|
47
14
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
15
|
+
- `selectedValue`: `UploadedFileAnswer[] | undefined` — **not** raw `File[]` after upload completes
|
|
16
|
+
- **Mutation pattern:**
|
|
17
|
+
1. User selects files → local validation
|
|
18
|
+
2. For each file: request presigned URL → PUT to S3 → append `{ id, mediaUrl, name, mimeType, size }`
|
|
19
|
+
3. `onSelect(uploadedAnswers)` → `onAction(CHANGE)`
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
- Local validation errors (e.g. "File too large") must be displayed in a highly visible red banner (`bg-red-50 text-red-700 p-3 rounded-md`) right below the dropzone.
|
|
21
|
+
## Local validation (before upload)
|
|
56
22
|
|
|
57
|
-
|
|
58
|
-
- Include a hidden `<input type="file" multiple={question.maxFileCount > 1} accept={...} />` that is triggered via a `useRef` when the dropzone is clicked.
|
|
23
|
+
Same as ui-spec: `supportedFileFormats`, `maxFileCount`, `fileSizeLimit` + `fileSizeLimitType`.
|
|
59
24
|
|
|
60
25
|
## SDK Integration Checklist (agent)
|
|
61
26
|
|
|
62
27
|
- [ ] Type literal: `QUESTION_TYPE.FILE_UPLOAD`
|
|
63
|
-
- [ ]
|
|
64
|
-
- [ ]
|
|
65
|
-
- [ ] Visibility helper: N/A
|
|
66
|
-
- [ ] Enforce `maxFileCount` and `fileSizeLimit` client-side
|
|
28
|
+
- [ ] Final answer: `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
29
|
+
- [ ] Wire upload API per [`file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
67
30
|
- [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
|