@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.
Files changed (95) hide show
  1. package/dist/cli/index.js +18 -18
  2. package/dist/cli/index.mjs +14 -14
  3. package/dist/index.d.mts +465 -580
  4. package/dist/index.d.ts +465 -580
  5. package/dist/index.js +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/package.json +6 -4
  8. package/postinstall.js +96 -69
  9. package/templates/AGENT.md +18 -8
  10. package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
  11. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  12. package/templates/docs/00-integration/aws-deploy.md +281 -0
  13. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  14. package/templates/docs/00-integration/component-checklist.md +55 -22
  15. package/templates/docs/00-integration/constraints.md +49 -4
  16. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  17. package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
  18. package/templates/docs/00-integration/end-page-logic.md +64 -0
  19. package/templates/docs/00-integration/file-upload-aws.md +116 -0
  20. package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
  21. package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
  22. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  23. package/templates/docs/00-integration/progress.md +61 -0
  24. package/templates/docs/00-integration/question-display-variants.md +65 -0
  25. package/templates/docs/00-integration/question-numbering.md +51 -0
  26. package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
  27. package/templates/docs/00-integration/setup.md +84 -0
  28. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  29. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  30. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  31. package/templates/docs/01-components/01-survey-page.md +76 -54
  32. package/templates/docs/01-components/02-question.md +61 -58
  33. package/templates/docs/01-components/03-rating-scale.md +11 -1
  34. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  35. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  36. package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
  37. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  38. package/templates/docs/01-components/08-file-upload-scale.md +23 -51
  39. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  40. package/templates/docs/01-components/11-progress-bar.md +10 -4
  41. package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
  42. package/templates/docs/01-components/14-intro-page.md +34 -0
  43. package/templates/docs/01-components/15-end-page.md +31 -0
  44. package/templates/docs/01-components/16-paused-page.md +27 -0
  45. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  46. package/templates/docs/01-components/18-rank-order-scale.md +93 -0
  47. package/templates/docs/01-components/README.md +42 -36
  48. package/templates/docs/02-reference/config-field-index.md +141 -139
  49. package/templates/docs/02-reference/exports.md +149 -0
  50. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  51. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  52. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  53. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  54. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  55. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  56. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  57. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  58. package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
  59. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  60. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  61. package/templates/docs/02-reference/question-types/README.md +96 -47
  62. package/templates/docs/02-reference/routing-table.md +33 -18
  63. package/templates/docs/02-reference/value-derivation.md +63 -0
  64. package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
  65. package/templates/docs/03-ui-specs/01-rating.md +18 -52
  66. package/templates/docs/03-ui-specs/02-radio.md +80 -37
  67. package/templates/docs/03-ui-specs/03-text.md +2 -2
  68. package/templates/docs/03-ui-specs/04-csat.md +68 -33
  69. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  70. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  71. package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
  72. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
  73. package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
  74. package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
  75. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  76. package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
  77. package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
  78. package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
  79. package/templates/docs/03-ui-specs/README.md +16 -14
  80. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
  81. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  82. package/templates/docs/MANIFEST.json +476 -69
  83. package/templates/docs/index.md +82 -30
  84. package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
  85. package/templates/docs/templates/MatrixDropdown.tsx +216 -0
  86. package/templates/docs/templates/Question.tsx +221 -154
  87. package/templates/docs/templates/RankOrderScale.tsx +353 -0
  88. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  89. package/templates/docs/templates/heatmapCoords.ts +58 -0
  90. package/templates/docs/templates/implementation_plan.md +126 -67
  91. package/templates/docs/templates/survey-inventory.schema.json +77 -0
  92. package/templates/docs/templates/surveyUiIcons.tsx +52 -0
  93. package/templates/docs/templates/verify-agent-build.sh +85 -0
  94. package/templates/docs/01-components/04-csat-scale.md +0 -40
  95. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -0,0 +1,351 @@
1
+ # Client Integration Guide
2
+
3
+ > **Start here** after Phase 2 (instanceId). Single map of everything the client must wire to integrate all SDK features correctly.
4
+
5
+ Import only from the package root (`@explorer02/cfm-survey-sdk` or `@repo/sdk`). Never from `/src/` paths.
6
+
7
+ ## Architecture
8
+
9
+ ```mermaid
10
+ flowchart TB
11
+ subgraph page [SurveyPage orchestrator]
12
+ Hook["useSurveySDK"]
13
+ PhaseRouter["Phase router\nloading / intro / content / end"]
14
+ Lifecycle["SCROLL + RECORD_QUESTION_VIEW effects"]
15
+ end
16
+ subgraph question [Question.tsx dispatcher]
17
+ Dispatch["QUESTION_TYPE switch"]
18
+ Visibility["getVisible*ForAnswers helpers"]
19
+ Placeholders["resolveSurveyQuestionPlaceholders"]
20
+ end
21
+ subgraph scales [Scale components]
22
+ Matrix["LikertMatrixScale / CsatMatrixScale / …"]
23
+ NPS["RatingScale / FileUploadScale / …"]
24
+ end
25
+ Hook --> PhaseRouter
26
+ PhaseRouter --> question
27
+ Dispatch --> scales
28
+ Visibility --> Dispatch
29
+ ```
30
+
31
+ | Layer | File | Responsibility |
32
+ |-------|------|----------------|
33
+ | Orchestrator | `SurveyPage.tsx` | Hook, phase routing, nav buttons, lifecycle effects, pass props to `Question` |
34
+ | Dispatcher | `Question.tsx` | Route on `question.type`; filter options/rows via visibility helpers; placeholder text |
35
+ | Scales | `*Scale.tsx` | Render one question type; store answers via `onSelect` callback |
36
+
37
+ Reference implementation: `apps/client/src/components/SurveyPage.tsx`.
38
+
39
+ ---
40
+
41
+ ## Feature matrix — who wires what
42
+
43
+ | Feature | Client wires | SDK handles |
44
+ |---------|--------------|-------------|
45
+ | Fetch survey | `instanceId`, loading/error UI | `useFetchSurvey`, survey mapping |
46
+ | Submit | `onAction(NEXT/SUBMIT)`, submit loading UI | Validation, API submit, phase → `end`, end-page selection |
47
+ | End page logic | Render `state.specialPage`; pass `customFieldValues` when end pages reference CRM fields | Display-logic evaluation at submit; see [`end-page-logic.md`](end-page-logic.md) |
48
+ | Answers | `onAction(CHANGE)` with `payload.answerValue` | `state.answers`, sanitization on logic change |
49
+ | Skip logic | Nothing (read doc for UX expectations) | Path simulation, skip jumps, answer clearing |
50
+ | Display logic | Use `state.currentQuestions` (pre-filtered) | Question-level visibility, empty-page skip |
51
+ | Question display variants | Render `question.questionText` from `currentQuestions` (no client variant logic) | Conditional variant text via same logic engine |
52
+ | Answer logic UI | `getVisible*ForAnswers` + pass `customFieldValues` | Condition evaluation engine |
53
+ | Custom fields | Stable `customFieldValues` → hook **and** `Question` | Skip/display/validation/progress evaluation |
54
+ | Progress | Bind `state.progressPercentage` | Active-path denominator + earned credits |
55
+ | Question numbering | Render `question.questionNumber` from `currentQuestions` | Active-path ordinals (same denominator as progress); see [`question-numbering.md`](question-numbering.md) |
56
+ | Placeholders | Stable `placeholders` ref + `language` option | Token replacement at map time |
57
+ | Mark as started | `onAction(SCROLL)` once on first scroll | One-time API call (also fires on CHANGE) |
58
+ | Question activity | `onAction(RECORD_QUESTION_VIEW)` via IntersectionObserver | Deduped VIEWED API calls |
59
+ | Partial save | `savePartialResponse: true` option | Debounced partial POST after user interaction — see [`partial-save-and-recovery.md`](partial-save-and-recovery.md) |
60
+ | Placeholders (detail) | Stable ref + `resolveSurveyQuestionPlaceholders` | CRM tokens at map time; answer tokens at render — see [`placeholders-and-tokens.md`](placeholders-and-tokens.md) |
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
+ | Validation scroll | `id={question.id}` on question wrapper | Scroll-to-first-error on failed NEXT/SUBMIT |
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) · [`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
+
66
+ ---
67
+
68
+ ## Stable references (CRITICAL)
69
+
70
+ Inline option objects cause infinite re-fetch loops and React hook dependency churn. Define **outside** the component and wrap hook options in `useMemo`:
71
+
72
+ ```typescript
73
+ const SURVEY_PLACEHOLDERS = { FIRST_NAME: 'Customer' };
74
+
75
+ const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {
76
+ '_c_abc123': 'value',
77
+ };
78
+
79
+ export default function SurveyPage() {
80
+ const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>();
81
+
82
+ const surveyOptions = useMemo(
83
+ () => ({
84
+ instanceId: 'YOUR_JWT_HERE',
85
+ language: selectedLanguage,
86
+ placeholders: SURVEY_PLACEHOLDERS,
87
+ customFieldValues: CUSTOM_FIELD_VALUES,
88
+ savePartialResponse: false as const,
89
+ debug: false,
90
+ }),
91
+ [selectedLanguage]
92
+ );
93
+
94
+ const { surveyQueryResults, submitSurveyResults, state, onAction } = useSurveySDK({
95
+ options: surveyOptions,
96
+ });
97
+ // ...
98
+ }
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Minimal SurveyPage skeleton
104
+
105
+ ```tsx
106
+ 'use client';
107
+
108
+ import { useState, useMemo, useEffect } from 'react';
109
+ import {
110
+ useSurveySDK,
111
+ type CustomFieldValueMap,
112
+ type Survey,
113
+ } from '@explorer02/cfm-survey-sdk';
114
+ import Question from './Question';
115
+
116
+ const SURVEY_PLACEHOLDERS = { FIRST_NAME: 'Customer' };
117
+ const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {}; // fill from CRM when survey uses custom-field logic
118
+
119
+ function SurveyContent({
120
+ survey,
121
+ allQuestions,
122
+ customFieldValues,
123
+ state,
124
+ submitSurveyResults,
125
+ onAction,
126
+ }: {
127
+ survey: Survey;
128
+ allQuestions: Survey['pages'][number]['questions'];
129
+ customFieldValues: CustomFieldValueMap;
130
+ state: ReturnType<typeof useSurveySDK>['state'];
131
+ submitSurveyResults: ReturnType<typeof useSurveySDK>['submitSurveyResults'];
132
+ onAction: ReturnType<typeof useSurveySDK>['onAction'];
133
+ }) {
134
+ // Mark as started on first scroll
135
+ useEffect(() => {
136
+ const onScroll = () => onAction({ type: 'SCROLL' });
137
+ window.addEventListener('scroll', onScroll, { passive: true, once: true });
138
+ return () => window.removeEventListener('scroll', onScroll);
139
+ }, [onAction]);
140
+
141
+ // Question activity (VIEWED)
142
+ useEffect(() => {
143
+ const observer = new IntersectionObserver(
144
+ entries => {
145
+ for (const entry of entries) {
146
+ if (entry.isIntersecting && entry.target.id) {
147
+ onAction({
148
+ type: 'RECORD_QUESTION_VIEW',
149
+ payload: { questionIds: [entry.target.id] },
150
+ });
151
+ }
152
+ }
153
+ },
154
+ { threshold: 0.5 }
155
+ );
156
+ state.currentQuestions.forEach(q => {
157
+ const el = document.getElementById(q.id);
158
+ if (el) observer.observe(el);
159
+ });
160
+ return () => observer.disconnect();
161
+ }, [state.currentQuestions, onAction]);
162
+
163
+ return (
164
+ <main>
165
+ <ProgressBar progressPercentage={state.progressPercentage} />
166
+
167
+ {state.currentQuestions.map(question => (
168
+ <Question
169
+ key={question.id}
170
+ question={question}
171
+ selectedValue={state.answers[question.id]}
172
+ validationError={state.validationErrors[question.id]}
173
+ allAnswers={state.answers}
174
+ allQuestions={allQuestions}
175
+ customFieldValues={customFieldValues}
176
+ onSelect={answerValue =>
177
+ onAction({ type: 'CHANGE', payload: { questionId: question.id, answerValue } })
178
+ }
179
+ />
180
+ ))}
181
+
182
+ {state.canGoBack && (
183
+ <button type="button" onClick={() => onAction({ type: 'PREVIOUS' })}>Back</button>
184
+ )}
185
+ <button
186
+ type="button"
187
+ onClick={() => onAction({ type: 'NEXT' })}
188
+ disabled={submitSurveyResults.isLoading}
189
+ >
190
+ {state.primaryButtonType === 'submit' ? 'Submit' : 'Next'}
191
+ </button>
192
+ </main>
193
+ );
194
+ }
195
+
196
+ export default function SurveyPage() {
197
+ const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>();
198
+
199
+ const surveyOptions = useMemo(
200
+ () => ({
201
+ instanceId: 'YOUR_JWT_HERE',
202
+ language: selectedLanguage,
203
+ placeholders: SURVEY_PLACEHOLDERS,
204
+ customFieldValues: CUSTOM_FIELD_VALUES,
205
+ }),
206
+ [selectedLanguage]
207
+ );
208
+
209
+ const { surveyQueryResults, submitSurveyResults, state, onAction } = useSurveySDK({
210
+ options: surveyOptions,
211
+ });
212
+
213
+ const survey = surveyQueryResults.data;
214
+ const allQuestions = useMemo(
215
+ () => survey?.pages.flatMap(page => page.questions) ?? [],
216
+ [survey]
217
+ );
218
+
219
+ if (surveyQueryResults.isLoading) return <div>Loading…</div>;
220
+ if (surveyQueryResults.error) return <div>Error: {surveyQueryResults.error.message}</div>;
221
+ if (!survey) return <div>No survey data</div>;
222
+
223
+ // Phase router — handle intro / paused / end / terminal before content
224
+ if (state.phase.kind === 'intro') return <IntroPage onStart={() => onAction({ type: 'START' })} />;
225
+ if (state.phase.kind === 'end') return <EndPage page={state.specialPage} />;
226
+ // … paused, submitted, expired, quota_fulfilled
227
+
228
+ if (state.phase.kind === 'content') {
229
+ return (
230
+ <SurveyContent
231
+ survey={survey}
232
+ allQuestions={allQuestions}
233
+ customFieldValues={CUSTOM_FIELD_VALUES}
234
+ state={state}
235
+ submitSurveyResults={submitSurveyResults}
236
+ onAction={onAction}
237
+ />
238
+ );
239
+ }
240
+
241
+ return null;
242
+ }
243
+ ```
244
+
245
+ See [`01-components/01-survey-page.md`](../01-components/01-survey-page.md) for full phase routing.
246
+
247
+ ---
248
+
249
+ ## Question dispatcher essentials
250
+
251
+ Copy [`templates/Question.tsx`](../templates/Question.tsx). Dispatch on flat `question.type` — **11** `QUESTION_TYPE` literals, no `subType`.
252
+
253
+ See [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md) for per-type answer shapes, visibility helpers, and `onAction` patterns.
254
+
255
+ | Type literal | Component | Visibility helper |
256
+ |--------------|-----------|-------------------|
257
+ | `MCQ` | Inline | `getVisibleMcqOptionsForAnswers` |
258
+ | `TEXTFIELD` | Inline | — |
259
+ | `NPS_SCALE` | `RatingScale` | `getVisibleScaleColumnsForAnswers` |
260
+ | `CFM_MATRIX` | `LikertMatrixScale` | `getVisibleMatrixItemsForAnswers` |
261
+ | `CSAT_MATRIX`, `RATING_MATRIX` | `CsatMatrixScale` | `getVisibleMatrixItemsForAnswers` |
262
+ | `SLIDER_MATRIX` | `SliderMatrixScale` | `getVisibleStatementRowsForAnswers` |
263
+ | `FILE_UPLOAD` | `FileUploadScale` | — |
264
+ | `TEXT_AND_MEDIA` | Inline media | — |
265
+ | `HEATMAP` | `HeatmapScale` | — |
266
+ | `RANK_ORDER` | `RankOrderScale` | `getVisibleRankOrderOptionsForAnswers` |
267
+
268
+ **Answer logic:** filter before render:
269
+
270
+ ```typescript
271
+ getVisibleMcqOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues);
272
+ getVisibleMatrixItemsForAnswers(question, allAnswers, allQuestions, customFieldValues);
273
+ ```
274
+
275
+ **Placeholders:** stable `SURVEY_PLACEHOLDERS` ref + `resolveSurveyQuestionPlaceholders(text, allAnswers, questionsById)`. See [`placeholders-and-tokens.md`](placeholders-and-tokens.md).
276
+
277
+ **Wrapper:** every question `<section id={question.id}>` for scroll-to-error.
278
+
279
+ See [`component-checklist.md`](component-checklist.md) and [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md).
280
+
281
+ ---
282
+
283
+ ## Custom fields — two wiring points
284
+
285
+ When survey logic references `conditionEntity.entityType: 'CUSTOM_FIELD'`:
286
+
287
+ 1. **`useSurveySDK` options** — `customFieldValues` map keyed by `_c_…` ids (skip, display, validation, progress).
288
+ 2. **`Question.tsx` visibility helpers** — pass the **same map** as the 4th argument to `getVisible*ForAnswers` (answer-logic UI on options/rows/columns).
289
+
290
+ See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
291
+
292
+ ---
293
+
294
+ ## Monorepo build prerequisite
295
+
296
+ When using `@repo/sdk` from the monorepo, build the SDK before running the client dev server:
297
+
298
+ ```bash
299
+ cd packages/sdk && npm run build
300
+ ```
301
+
302
+ Without a built `dist/`, the client fails with missing module errors. Rebuild after SDK changes.
303
+
304
+ ---
305
+
306
+ ## Debug logging and analytics
307
+
308
+ Enable debug console logs with `useSurveySDK({ options: { debug: true } })`. Logic match logs appear as `[SDK:INFO]` when conditions **newly become true** on answer change:
309
+
310
+ | Log message | When |
311
+ |-------------|------|
312
+ | `Display logic matched` | A gated question becomes visible |
313
+ | `Answer logic matched` | An option / row / column becomes visible via answer logic |
314
+ | `Question variant matched` | A display variant’s condition matches |
315
+ | `End page logic matched` | The winning end page changes (preview before submit) |
316
+ | `Skip logic matched` | Skip logic newly activates on the current question |
317
+
318
+ Analytics events (`[SDK:ANALYTICS]`) fire **always**, even without `debug: true`:
319
+
320
+ | Event | When |
321
+ |-------|------|
322
+ | `display_logic_matched` | Display logic condition newly true |
323
+ | `answer_logic_matched` | Answer-logic sub-entity newly visible |
324
+ | `question_variant_matched` | Variant text switches to a matching variant |
325
+ | `end_page_logic_matched` | Resolved end page changes |
326
+ | `question_viewed` | Question enters viewport (`RECORD_QUESTION_VIEW`) |
327
+ | `skip_logic_triggered` | Skip logic newly activates on answer change |
328
+ | `logic_session_summary` | On successful submit — rollup table + chronological timeline |
329
+
330
+ The submit-time `logic_session_summary` includes counts per logic kind, viewed vs on-path-not-viewed questions, final end page resolution, and a `logicEventTimeline` audit. It prints via `console.table` alongside `survey_session_summary`.
331
+
332
+ ---
333
+
334
+ ## Pre-flight checklist
335
+
336
+ Before `npm run build`:
337
+
338
+ - [ ] Import from package root only — no `@repo/sdk/src/…` paths
339
+ - [ ] `SURVEY_PLACEHOLDERS` and `CUSTOM_FIELD_VALUES` defined outside component
340
+ - [ ] `surveyOptions` wrapped in `useMemo` (depends on `selectedLanguage` etc.)
341
+ - [ ] `Question.tsx` copied from `docs/templates/Question.tsx` — all **11** type branches, no stub text
342
+ - [ ] `SURVEY_PLACEHOLDERS` defined outside component; `surveyOptions` in `useMemo` (see [`placeholders-and-tokens.md`](placeholders-and-tokens.md))
343
+ - [ ] `id={question.id}` on every question wrapper
344
+ - [ ] `customFieldValues` passed to hook **and** `Question` (when survey uses custom-field logic)
345
+ - [ ] Answer logic: `getVisible*ForAnswers` receives `customFieldValues` 4th arg
346
+ - [ ] `SCROLL` + `RECORD_QUESTION_VIEW` lifecycle effects wired (see [`survey-lifecycle-analytics.md`](survey-lifecycle-analytics.md))
347
+ - [ ] `state.canGoBack` / `state.primaryButtonType` used for nav (not raw page index)
348
+ - [ ] Monorepo: SDK built (`packages/sdk/dist/` exists)
349
+ - [ ] `grep -r "not yet implemented" src/ components/ app/` → zero matches
350
+
351
+ Run `bash templates/verify-agent-build.sh` from project root after Phase 5.
@@ -1,41 +1,62 @@
1
1
  # Required Component Files Checklist
2
2
 
3
- > **Blocking gate before Phase 5 (BUILD).** After fetching the survey with `instanceId`, list every `(type, subType)` pair and ensure all required files exist.
3
+ > **Blocking gate before Phase 5 (BUILD).** After fetching the survey with `instanceId`, list every `question.type` and ensure all required files exist.
4
4
 
5
5
  ## Step 1: Inventory Question Types
6
6
 
7
- From `survey.pages`, collect unique combinations:
7
+ From `survey.pages`, collect unique `question.type` values — see `MANIFEST.json` `typeLiterals`:
8
8
 
9
- | questionId | type | subType (if matrix) |
10
- |------------|------|---------------------|
11
- | | | |
9
+ | questionId | type |
10
+ |------------|------|
11
+ | | |
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
- | `rating` | `RatingScale.tsx` | `03-ui-specs/01-rating.md` |
18
- | `csat` | `CsatScale.tsx` | `03-ui-specs/04-csat.md` |
19
- | `rating_scale` | Reuse `CsatScale.tsx` or dedicated component | `03-ui-specs/05-rating-scale.md` |
20
- | `slider` | `CustomSliderTrack.tsx` + wrapper | `03-ui-specs/06-slider.md` |
21
- | `matrix` + `CFM_MATRIX` | `LikertMatrixScale.tsx`, `MatrixDropdown.tsx` | `03-ui-specs/07-matrix-cfm.md` |
22
- | `matrix` + `CSAT_MATRIX` or `RATING_MATRIX` | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/08-matrix-csat-rating.md` |
23
- | `slider_matrix` | `SliderMatrixScale.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/09-slider-matrix.md` |
24
- | `file_upload` | `FileUploadScale.tsx` | `03-ui-specs/10-file-upload.md` |
25
- | `radio`, `text`, `text_and_media` | Inline in `Question.tsx` | `03-ui-specs/02-radio.md`, `03-text.md`, `11-text-and-media.md` |
27
+ | `NPS_SCALE` | `RatingScale.tsx` | `03-ui-specs/01-rating.md` |
28
+ | `CFM_MATRIX` | `LikertMatrixScale.tsx`, `MatrixDropdown.tsx` | `03-ui-specs/07-matrix-cfm.md` |
29
+ | `CSAT_MATRIX` | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/04-csat.md` |
30
+ | `RATING_MATRIX` | `CsatMatrixScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/08-matrix-csat-rating.md` |
31
+ | `SLIDER_MATRIX` | `SliderMatrixScale.tsx`, `CustomSliderTrack.tsx` | `03-ui-specs/09-slider-matrix.md` |
32
+ | `FILE_UPLOAD` | `FileUploadScale.tsx` | `03-ui-specs/10-file-upload.md` |
33
+ | `MCQ`, `TEXTFIELD`, `TEXT_AND_MEDIA` | Inline in `Question.tsx` | `03-ui-specs/02-radio.md`, `03-text.md`, `11-text-and-media.md` |
34
+ | `RANK_ORDER` | `RankOrderScale.tsx` (+ `@dnd-kit/*`) | `03-ui-specs/14-rank-order.md` |
35
+ | `HEATMAP` | `HeatmapScale.tsx`, `lib/heatmapCoords.ts` (copy from `templates/heatmapCoords.ts`) | `03-ui-specs/13-heatmap.md` |
36
+
37
+ **Survey chrome (when applicable):**
38
+
39
+ | Component | When |
40
+ |-----------|------|
41
+ | `IntroPage.tsx` | `survey.introPage` |
42
+ | `EndPage.tsx` | `survey.endPages` |
43
+ | `PausedPage.tsx` | `survey.phaseFlags.paused` |
26
44
 
27
45
  Always create **`Question.tsx`** from [`templates/Question.tsx`](../templates/Question.tsx).
28
46
 
29
- ## Matrix subType Routing (CRITICAL)
47
+ ## Flat Type Routing (CRITICAL)
30
48
 
31
- `question.type === 'matrix'` alone renders **nothing**. You MUST branch on `subType`:
49
+ Use `QUESTION_TYPE` **no `subType`**:
32
50
 
33
51
  ```tsx
34
- {question.type === 'matrix' && question.subType === 'CFM_MATRIX' && (
52
+ import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
53
+
54
+ {question.type === QUESTION_TYPE.CFM_MATRIX && (
35
55
  <LikertMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
36
56
  )}
37
57
 
38
- {question.type === 'matrix' && (question.subType === 'CSAT_MATRIX' || question.subType === 'RATING_MATRIX') && (
58
+ {(question.type === QUESTION_TYPE.CSAT_MATRIX ||
59
+ question.type === QUESTION_TYPE.RATING_MATRIX) && (
39
60
  <CsatMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
40
61
  )}
41
62
  ```
@@ -44,16 +65,28 @@ Always create **`Question.tsx`** from [`templates/Question.tsx`](../templates/Qu
44
65
 
45
66
  | File | Used by |
46
67
  |------|---------|
47
- | `CustomSliderTrack.tsx` | `slider_matrix`, CSAT matrix `graphics`, standalone `slider` |
48
- | `MatrixDropdown.tsx` | `LikertMatrixScale`, `CsatMatrixScale` vertical/dropdown modes |
68
+ | `CustomSliderTrack.tsx` | `SLIDER_MATRIX`, matrix `displayStyle: graphics` |
69
+ | `MatrixDropdown.tsx` | `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX` dropdown/selectbox modes |
70
+
71
+ Store matrix values as column **`id`** — `matrixColumnStoredValue(col)` from SDK.
72
+
73
+ ## Answer logic (when survey has gated options / rows / columns)
74
+
75
+ - [ ] MCQ: filter options with `getVisibleMcqOptionsForAnswers(question, answers, allQuestions, customFieldValues)`
76
+ - [ ] Matrix (CSAT/CFM/RATING/SLIDER): filter rows (and CSAT/CFM columns) with `getVisibleMatrixItemsForAnswers(question, answers, allQuestions, customFieldValues)`
77
+ - [ ] Pass `customFieldValues` from `SurveyPage` → `Question` when survey logic references `CUSTOM_FIELD` entities
78
+ - [ ] Wire `onAction({ type: 'CHANGE' })` so answer logic re-evaluates when prior answers change
49
79
 
50
- Specs: `03-ui-specs/shared/custom-slider-track.md`, `03-ui-specs/shared/matrix-dropdown.md`
80
+ See [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) and [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
51
81
 
52
82
  ## Step 3: Verify Before Build
53
83
 
54
84
  - [ ] Every type in the survey inventory has a matching component file
55
- - [ ] `Question.tsx` copied from `docs/templates/Question.tsx` with all branches wired
85
+ - [ ] `Question.tsx` copied from `docs/templates/Question.tsx` with all **11** type branches wired
86
+ - [ ] `SurveyPage` handles all `state.phase` values
56
87
  - [ ] No `default` fallback rendering stub text (see `constraints.md` §7)
88
+ - [ ] RANK_ORDER: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` in `package.json`
89
+ - [ ] Per-type wiring verified against `question-type-sdk-matrix.md`
57
90
  - [ ] Grep project: `not yet implemented` → **zero matches**
58
91
 
59
92
  ```bash
@@ -36,9 +36,25 @@ Required for scroll-to-error when validation fails.
36
36
 
37
37
  Survey UI files importing the SDK must be client components. Next.js: `'use client'` at top of file.
38
38
 
39
- ## 6. Stable Placeholder References
39
+ ## 6. Stable Placeholder and Custom Field References
40
40
 
41
- Define `SURVEY_PLACEHOLDERS` outside the component — inline objects cause infinite re-fetch.
41
+ Define `SURVEY_PLACEHOLDERS` and `CUSTOM_FIELD_VALUES` **outside** the component — inline objects cause infinite re-fetch and unstable hook dependency arrays.
42
+
43
+ Wrap hook `options` in `useMemo`:
44
+
45
+ ```typescript
46
+ const surveyOptions = useMemo(
47
+ () => ({
48
+ instanceId,
49
+ language: selectedLanguage,
50
+ placeholders: SURVEY_PLACEHOLDERS,
51
+ customFieldValues: CUSTOM_FIELD_VALUES,
52
+ }),
53
+ [selectedLanguage]
54
+ );
55
+ ```
56
+
57
+ See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) when the survey uses custom-field logic.
42
58
 
43
59
  ## 7. No Stub Placeholders (CRITICAL)
44
60
 
@@ -58,14 +74,43 @@ grep -r "not yet implemented" src/ components/ app/ 2>/dev/null || true
58
74
 
59
75
  ---
60
76
 
77
+ ## 8. Deploy Constraints
78
+
79
+ - **Never embed AWS credentials** in client code — JWT goes in `Authorization: Bearer` header at deploy time only
80
+ - **Deploy JWT must match** `SURVEY_INSTANCE_ID` in `SurveyPage.tsx` (surveyId drives S3 prefix)
81
+ - **Next.js AWS deploy requires static export** — no server-only APIs (`getServerSideProps`, etc.)
82
+ - **Max 500 files** per deploy (EC2 API limit)
83
+ - **No absolute `/`-root asset paths** when deploying to nested CloudFront prefix — use `assetPrefix: "./"` and relative public assets
84
+
85
+ See [`aws-deploy.md`](aws-deploy.md) for the full AWS deploy procedure and troubleshooting.
86
+
87
+ ---
88
+
89
+ ## 9. SDK Integration Constraints
90
+
91
+ - **Never import internal evaluators** from `@explorer02/cfm-survey-sdk/src/**` or `@repo/sdk/src/**`
92
+ - **Always use public `getVisible*ForAnswers` helpers** for answer-logic UI filtering — never reimplement condition evaluation
93
+ - **Matrix cell values:** always store column **`id`** via `matrixColumnStoredValue(column)` — never raw label text
94
+ - **Rank order:** use SDK rank helpers (`assignRankWithoutDuplicates`, `normalizeRankOrderAnswers`, etc.) — do not hand-roll rank maps
95
+ - **CSAT emoji/star UI:** implement client-side with `react-icons` — no phantom SDK emoji exports
96
+ - **Read [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md)** before building any scale component
97
+
98
+ ---
99
+
61
100
  ## Troubleshooting
62
101
 
63
102
  | Symptom | Fix |
64
103
  |---------|-----|
65
104
  | `Cannot use import statement outside a module` | Add SDK to `transpilePackages` in `next.config.js` |
66
- | Infinite API requests | Move `placeholders` object outside component |
105
+ | Infinite API requests | Move `placeholders` / `customFieldValues` outside component; wrap options in `useMemo` |
106
+ | `useEffect` dependency array size changed | Unstable inline `options` object — use `useMemo` for hook options |
107
+ | Missing `dist/index.mjs` (monorepo) | Run `npm run build` in `packages/sdk` before client dev |
67
108
  | `window is not defined` | Add `'use client'` to SDK-consuming files |
68
109
  | Validation errors but no scroll | Add `id={question.id}` to question wrapper |
69
110
  | Survey data is null, no error | Invalid/expired `instanceId` — ask client for fresh JWT |
70
111
  | CSAT emojis missing | `npm install react-icons` |
71
- | Grey "not yet implemented" text on questions | Copy `docs/templates/Question.tsx`; create scale components per `component-checklist.md`; route matrix by `subType` |
112
+ | Grey "not yet implemented" text on questions | Copy `docs/templates/Question.tsx`; create scale components per `component-checklist.md`; route each matrix variant by flat `question.type` |
113
+ | 403 on `/_next/static/...` after AWS deploy | Add `assetPrefix: "./"` to `next.config.js`, rebuild — see `aws-deploy.md` |
114
+ | 403 on survey images `*_p.png` | Use `option.imageUrl` before `option.previewImageUrl` in RankOrderScale |
115
+ | 403 on `index.html.txt?_rsc` | Add `cp out/index.txt out/index.html.txt` to build script |
116
+ | AWS deploy PUT failures | Presigned URLs expired — restart from `/deploy/start` within 5–15 min |