@explorer02/cfm-survey-sdk 0.2.1 → 0.2.2

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 (88) 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 +14 -8
  10. package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
  11. package/templates/docs/00-integration/aws-deploy.md +281 -0
  12. package/templates/docs/00-integration/client-integration-guide.md +351 -0
  13. package/templates/docs/00-integration/component-checklist.md +45 -22
  14. package/templates/docs/00-integration/constraints.md +49 -4
  15. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
  16. package/templates/docs/00-integration/display-logic-and-navigation.md +49 -0
  17. package/templates/docs/00-integration/end-page-logic.md +64 -0
  18. package/templates/docs/00-integration/partial-save-and-recovery.md +74 -0
  19. package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
  20. package/templates/docs/00-integration/progress.md +61 -0
  21. package/templates/docs/00-integration/question-display-variants.md +65 -0
  22. package/templates/docs/00-integration/question-numbering.md +51 -0
  23. package/templates/docs/00-integration/question-type-sdk-matrix.md +242 -0
  24. package/templates/docs/00-integration/setup.md +84 -0
  25. package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
  26. package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
  27. package/templates/docs/00-integration/useSurveySDK.md +151 -23
  28. package/templates/docs/01-components/01-survey-page.md +76 -54
  29. package/templates/docs/01-components/02-question.md +61 -58
  30. package/templates/docs/01-components/03-rating-scale.md +11 -1
  31. package/templates/docs/01-components/04-star-rating-scale.md +45 -0
  32. package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
  33. package/templates/docs/01-components/06-likert-matrix-scale.md +12 -3
  34. package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
  35. package/templates/docs/01-components/08-file-upload-scale.md +10 -1
  36. package/templates/docs/01-components/09-custom-slider-track.md +2 -2
  37. package/templates/docs/01-components/11-progress-bar.md +10 -4
  38. package/templates/docs/01-components/13-matrix-dropdown.md +3 -1
  39. package/templates/docs/01-components/14-intro-page.md +34 -0
  40. package/templates/docs/01-components/15-end-page.md +31 -0
  41. package/templates/docs/01-components/16-paused-page.md +27 -0
  42. package/templates/docs/01-components/17-heatmap-scale.md +75 -0
  43. package/templates/docs/01-components/18-rank-order-scale.md +100 -0
  44. package/templates/docs/01-components/README.md +42 -36
  45. package/templates/docs/02-reference/config-field-index.md +141 -139
  46. package/templates/docs/02-reference/exports.md +149 -0
  47. package/templates/docs/02-reference/question-types/01-rating.md +31 -32
  48. package/templates/docs/02-reference/question-types/04-csat.md +51 -42
  49. package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
  50. package/templates/docs/02-reference/question-types/06-slider.md +2 -2
  51. package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
  52. package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
  53. package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
  54. package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
  55. package/templates/docs/02-reference/question-types/11-file-upload.md +13 -2
  56. package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
  57. package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
  58. package/templates/docs/02-reference/question-types/README.md +86 -47
  59. package/templates/docs/02-reference/routing-table.md +33 -18
  60. package/templates/docs/02-reference/value-derivation.md +63 -0
  61. package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
  62. package/templates/docs/03-ui-specs/01-rating.md +12 -54
  63. package/templates/docs/03-ui-specs/02-radio.md +21 -33
  64. package/templates/docs/03-ui-specs/03-text.md +2 -2
  65. package/templates/docs/03-ui-specs/04-csat.md +38 -33
  66. package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
  67. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  68. package/templates/docs/03-ui-specs/07-matrix-cfm.md +24 -21
  69. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +17 -45
  70. package/templates/docs/03-ui-specs/09-slider-matrix.md +1 -1
  71. package/templates/docs/03-ui-specs/10-file-upload.md +1 -1
  72. package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
  73. package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
  74. package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
  75. package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
  76. package/templates/docs/03-ui-specs/README.md +13 -13
  77. package/templates/docs/03-ui-specs/shared/custom-slider-track.md +2 -2
  78. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
  79. package/templates/docs/MANIFEST.json +451 -69
  80. package/templates/docs/index.md +80 -29
  81. package/templates/docs/templates/Question.tsx +221 -154
  82. package/templates/docs/templates/deploy-to-aws.sh +114 -0
  83. package/templates/docs/templates/heatmapCoords.ts +58 -0
  84. package/templates/docs/templates/implementation_plan.md +108 -69
  85. package/templates/docs/templates/survey-inventory.schema.json +72 -0
  86. package/templates/docs/templates/verify-agent-build.sh +53 -0
  87. package/templates/docs/01-components/04-csat-scale.md +0 -40
  88. package/templates/docs/02-question-types/01-rating.md +0 -44
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # Deploy static survey UI to AWS via EC2 middle API (S3 + CloudFront).
3
+ # Prerequisites: CFM_INSTANCE_ID, CFM_DEPLOY_API_BASE, OUT_DIR set in environment.
4
+ # Run from project root after a successful static build.
5
+ set -euo pipefail
6
+
7
+ : "${CFM_INSTANCE_ID:?Set CFM_INSTANCE_ID to the same JWT as SurveyPage SURVEY_INSTANCE_ID}"
8
+ : "${CFM_DEPLOY_API_BASE:?Set CFM_DEPLOY_API_BASE (e.g. http://43.204.26.213:3000)}"
9
+ : "${OUT_DIR:?Set OUT_DIR to absolute path of out/ (Next.js) or dist/ (Vite)}"
10
+
11
+ if [ ! -f "$OUT_DIR/index.html" ]; then
12
+ echo "FAIL: $OUT_DIR/index.html not found — run build first"
13
+ exit 1
14
+ fi
15
+
16
+ FILE_COUNT=$(find "$OUT_DIR" -type f | wc -l | tr -d ' ')
17
+ if [ "$FILE_COUNT" -gt 500 ]; then
18
+ echo "FAIL: $FILE_COUNT files exceeds API limit of 500"
19
+ exit 1
20
+ fi
21
+
22
+ echo "=== AWS Deploy ==="
23
+ echo "API: $CFM_DEPLOY_API_BASE"
24
+ echo "OUT: $OUT_DIR ($FILE_COUNT files)"
25
+ echo
26
+
27
+ HEALTH=$(curl -s "$CFM_DEPLOY_API_BASE/health")
28
+ echo "Health: $HEALTH"
29
+ echo
30
+
31
+ python3 <<'PY' > /tmp/deploy-manifest.json
32
+ import json, os, mimetypes
33
+ from pathlib import Path
34
+ out = Path(os.environ["OUT_DIR"])
35
+ files = []
36
+ for path in sorted(out.rglob("*")):
37
+ if not path.is_file():
38
+ continue
39
+ rel = path.relative_to(out).as_posix()
40
+ mime, _ = mimetypes.guess_type(rel)
41
+ files.append({
42
+ "path": rel,
43
+ "contentType": mime or "application/octet-stream",
44
+ "sizeBytes": path.stat().st_size,
45
+ })
46
+ print(json.dumps({"files": files}))
47
+ print(f"files: {len(files)}", file=__import__('sys').stderr)
48
+ PY
49
+
50
+ echo "Channel 1: deploy/start"
51
+ curl -s -X POST "$CFM_DEPLOY_API_BASE/deploy/start" \
52
+ -H "Authorization: Bearer $CFM_INSTANCE_ID" \
53
+ -H "Content-Type: application/json" \
54
+ -d @/tmp/deploy-manifest.json \
55
+ | tee /tmp/deploy-start.json
56
+ echo
57
+
58
+ if ! python3 -c "import json; json.load(open('/tmp/deploy-start.json'))['deployId']" 2>/dev/null; then
59
+ echo "FAIL: deploy/start did not return deployId"
60
+ exit 1
61
+ fi
62
+
63
+ echo "Channel 2: S3 uploads"
64
+ python3 <<'PY'
65
+ import json, os, subprocess
66
+ from pathlib import Path
67
+
68
+ out = Path(os.environ["OUT_DIR"])
69
+ start = json.load(open("/tmp/deploy-start.json"))
70
+ manifest = {f["path"]: f["contentType"] for f in json.load(open("/tmp/deploy-manifest.json"))["files"]}
71
+ failed = []
72
+
73
+ for item in start["uploads"]:
74
+ path = item["path"]
75
+ url = item["uploadUrl"]
76
+ mime = manifest.get(path, "application/octet-stream")
77
+ proc = subprocess.run(
78
+ ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
79
+ "-X", "PUT", url, "-H", f"Content-Type: {mime}",
80
+ "--data-binary", f"@{out / path}"],
81
+ capture_output=True, text=True,
82
+ )
83
+ code = proc.stdout.strip()
84
+ mark = "OK" if code == "200" else "FAIL"
85
+ print(f"{mark} {code} {path}")
86
+ if code != "200":
87
+ failed.append(path)
88
+
89
+ if failed:
90
+ print(f"\n{len(failed)} failed — rerun deploy (presigned URLs expire)")
91
+ raise SystemExit(1)
92
+ print("\nAll uploads succeeded")
93
+ PY
94
+
95
+ echo
96
+ echo "Channel 3: deploy/complete"
97
+ DEPLOY_ID=$(python3 -c "import json; print(json.load(open('/tmp/deploy-start.json'))['deployId'])")
98
+ curl -s -X POST "$CFM_DEPLOY_API_BASE/deploy/complete" \
99
+ -H "Authorization: Bearer $CFM_INSTANCE_ID" \
100
+ -H "Content-Type: application/json" \
101
+ -d "{\"deployId\":\"$DEPLOY_ID\"}" \
102
+ | tee /tmp/deploy-complete.json
103
+ echo
104
+
105
+ LIVE_URL=$(python3 -c "import json; print(json.load(open('/tmp/deploy-complete.json'))['url'])")
106
+ echo "Live URL: $LIVE_URL"
107
+ HTTP=$(curl -s -o /dev/null -w "%{http_code}" "$LIVE_URL")
108
+ echo "Verify HTTP: $HTTP"
109
+ if [ "$HTTP" != "200" ]; then
110
+ echo "WARN: index.html returned $HTTP — check CloudFront path or invalidation"
111
+ exit 1
112
+ fi
113
+
114
+ echo "=== Deploy complete ==="
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Portable heatmap coordinate helpers for agent projects.
3
+ *
4
+ * Copy to e.g. src/lib/heatmapCoords.ts — do not import from monorepo-only paths.
5
+ * See 01-components/17-heatmap-scale.md and question-type-sdk-matrix.md#heatmap.
6
+ */
7
+ import type { MouseEvent } from 'react';
8
+ import type { HeatmapClickPoint } from '@explorer02/cfm-survey-sdk';
9
+
10
+ export const SPOT_HIT_RADIUS_PX = 14;
11
+
12
+ export function roundCoordinate(value: number): number {
13
+ return Math.round(value * 100000) / 100000;
14
+ }
15
+
16
+ export function getNormalizedClickFromImage(
17
+ event: MouseEvent,
18
+ imageElement: HTMLImageElement
19
+ ): { x: number; y: number } {
20
+ const rect = imageElement.getBoundingClientRect();
21
+ if (rect.width === 0 || rect.height === 0) {
22
+ return { x: 0, y: 0 };
23
+ }
24
+
25
+ const x = (event.clientX - rect.left) / rect.width;
26
+ const y = (event.clientY - rect.top) / rect.height;
27
+
28
+ return {
29
+ x: roundCoordinate(Math.min(1, Math.max(0, x))),
30
+ y: roundCoordinate(Math.min(1, Math.max(0, y))),
31
+ };
32
+ }
33
+
34
+ export function findSpotNearClick(
35
+ clickX: number,
36
+ clickY: number,
37
+ spots: HeatmapClickPoint[],
38
+ imageRect: DOMRect
39
+ ): HeatmapClickPoint | undefined {
40
+ const clickPixelX = clickX * imageRect.width;
41
+ const clickPixelY = clickY * imageRect.height;
42
+
43
+ for (const spot of spots) {
44
+ const spotPixelX = spot.x * imageRect.width;
45
+ const spotPixelY = spot.y * imageRect.height;
46
+ const distance = Math.hypot(spotPixelX - clickPixelX, spotPixelY - clickPixelY);
47
+
48
+ if (distance <= SPOT_HIT_RADIUS_PX) {
49
+ return spot;
50
+ }
51
+ }
52
+
53
+ return undefined;
54
+ }
55
+
56
+ export function createNewSpot(x: number, y: number): HeatmapClickPoint {
57
+ return { id: crypto.randomUUID(), x, y };
58
+ }
@@ -12,107 +12,146 @@
12
12
  ## 1. Data Fetching & State
13
13
 
14
14
  - [ ] `useSurveySDK` options: instanceId, language, placeholders, debug
15
- - [ ] `SurveyPage` 5-state machine: loading / error / empty / submitted / active
15
+ - [ ] `surveyOptions` wrapped in `useMemo` (stable refs for placeholders / customFieldValues)
16
+ - [ ] `customFieldValues` map when survey logic references `CUSTOM_FIELD` entities (see `custom-field-logic-and-navigation.md`)
17
+ - [ ] `savePartialResponse` enabled if required (`true` or `{ debounceMs }`)
18
+ - [ ] Monorepo: `packages/sdk` built (`npm run build`) before client dev
19
+ - [ ] `SurveyPage` phase router: loading / error / empty / intro / paused / end / terminal / content
16
20
  - [ ] `surveyQueryResults.isLoading`, `.error`, `.data` handling
17
21
  - [ ] `submitSurveyResults.isLoading`, `.error`, `.data` handling
22
+ - [ ] Answers read from `state.answers` (`SurveyAnswers`); changes via `payload.answerValue`
18
23
 
19
24
  ## 2. Pagination, Actions & Flow
20
25
 
21
- - [ ] `onAction` NEXT / PREVIOUS / SUBMIT wiring
22
- - [ ] `ProgressBar` bound to `state.progressPercentage`
23
- - [ ] Back/Next button visibility per layout config
24
- - [ ] Last-page NEXT auto SUBMIT behavior
26
+ - [ ] `onAction` START / NEXT / PREVIOUS / SUBMIT wiring
27
+ - [ ] `onAction(SCROLL)` once on first scroll (mark as started)
28
+ - [ ] `onAction(RECORD_QUESTION_VIEW)` via IntersectionObserver on question wrappers
29
+ - [ ] `ProgressBar` bound to `state.progressPercentage` (path-scoped — see `00-integration/progress.md`)
30
+ - [ ] Back via `state.canGoBack`; primary label via `state.primaryButtonType`
31
+ - [ ] Read `00-integration/skip-logic-and-navigation.md` for skip/Back behavior
32
+ - [ ] Read `00-integration/client-integration-guide.md` for full wiring map
33
+
34
+ ## 1b. Lifecycle & Analytics
35
+
36
+ - [ ] `onAction(SCROLL)` once on first scroll (see `survey-lifecycle-analytics.md`)
37
+ - [ ] First `CHANGE` auto mark-as-started (SDK — no extra client call)
38
+ - [ ] `onAction(RECORD_QUESTION_VIEW)` via IntersectionObserver on `#question-{id}` wrappers
39
+ - [ ] Effect re-runs when `currentQuestions` changes (multi-page / display logic)
40
+ - [ ] Partial save: `savePartialResponse` + monitor `partialSaveResults` if enabled (see `partial-save-and-recovery.md`)
41
+ - [ ] Placeholders: stable `SURVEY_PLACEHOLDERS` ref + `useMemo` on hook options (see `placeholders-and-tokens.md`)
25
42
 
26
43
  ## 3. Question Dispatching (`Question.tsx`)
27
44
 
28
- Exhaustive switch — check each:
29
-
30
- - [ ] `rating` → `RatingScale`
31
- - [ ] `csat` → `CsatScale`
32
- - [ ] `radio` → inline MCQ
33
- - [ ] `text` → inline textarea/input
34
- - [ ] `rating_scale` → `RatingScale` (star/emoji)
35
- - [ ] `slider` → `CustomSliderTrack`
36
- - [ ] `matrix` + `CFM_MATRIX` → `LikertMatrixScale`
37
- - [ ] `matrix` + `CSAT_MATRIX` / `RATING_MATRIX` → `CsatMatrixScale`
38
- - [ ] `rating_matrix` → `CsatMatrixScale`
39
- - [ ] `slider_matrix` → `SliderMatrixScale`
40
- - [ ] `file_upload` `FileUploadScale`
41
- - [ ] `text_and_media` inline media (no scale)
42
- - [ ] `dangerouslySetInnerHTML` for `question.text` / `description`
45
+ Exhaustive switch — check each (**11** `QUESTION_TYPE` literals):
46
+
47
+ - [ ] `NPS_SCALE` → `RatingScale`
48
+ - [ ] `MCQ` → inline MCQ
49
+ - [ ] `TEXTFIELD` → inline textarea/input
50
+ - [ ] `CFM_MATRIX` → `LikertMatrixScale`
51
+ - [ ] `CSAT_MATRIX`, `RATING_MATRIX` → `CsatMatrixScale`
52
+ - [ ] `SLIDER_MATRIX` → `SliderMatrixScale`
53
+ - [ ] `FILE_UPLOAD` → `FileUploadScale`
54
+ - [ ] `TEXT_AND_MEDIA` inline media (no scale)
55
+ - [ ] `HEATMAP` → `HeatmapScale`
56
+ - [ ] `RANK_ORDER` → `RankOrderScale`
57
+ - [ ] `allAnswers` + `allQuestions` passed for placeholder resolution
58
+ - [ ] `customFieldValues` passed from SurveyPage when survey uses custom-field or answer logic on CRM data
59
+ - [ ] Answer logic: `getVisibleMcqOptionsForAnswers` / `getVisibleMatrixItemsForAnswers` with `customFieldValues` 4th arg
60
+ - [ ] `dangerouslySetInnerHTML` for `question.questionText` / `questionDescription`
43
61
  - [ ] `validationError` banner per question
44
62
  - [ ] `id={question.id}` on every question wrapper
45
63
  - [ ] Copied from `docs/templates/Question.tsx` — no stub/default fallback branches
46
64
 
65
+ ## 3a. Value Derivation (client-side)
66
+
67
+ Read `02-reference/value-derivation.md`:
68
+
69
+ - [ ] Import `matrixColumnStoredValue` from SDK for matrix cells
70
+ - [ ] Matrix keys = `statementRows[].id`; values = column **`id`** or `null`
71
+ - [ ] NPS: derive numeric value from `optionLabel` or index
72
+ - [ ] MCQ: store `option.id`
73
+
47
74
  ## 3b. Required Component Files (from fetched survey)
48
75
 
49
- Fill from `00-integration/component-checklist.md` — every file below that applies to your survey **must exist** before Phase 5:
76
+ Fill from `00-integration/component-checklist.md`:
50
77
 
51
78
  | Component file | Required if survey contains | Created? |
52
79
  |----------------|----------------------------|----------|
53
- | `LikertMatrixScale.tsx` | `matrix` + `CFM_MATRIX` | |
54
- | `CsatMatrixScale.tsx` | `matrix` + `CSAT_MATRIX` or `RATING_MATRIX` | |
55
- | `SliderMatrixScale.tsx` | `slider_matrix` | **blocking** |
56
- | `FileUploadScale.tsx` | `file_upload` | **blocking** |
57
- | `MatrixDropdown.tsx` | matrix with dropdown/selectbox modes | |
58
- | `CustomSliderTrack.tsx` | `slider_matrix` or CSAT matrix `graphics` | |
80
+ | `IntroPage.tsx` | intro page | |
81
+ | `EndPage.tsx` | end pages | |
82
+ | `PausedPage.tsx` | paused flag | |
83
+ | `LikertMatrixScale.tsx` | `CFM_MATRIX` | |
84
+ | `CsatMatrixScale.tsx` | `CSAT_MATRIX` or `RATING_MATRIX` | |
85
+ | `SliderMatrixScale.tsx` | `SLIDER_MATRIX` | **blocking** |
86
+ | `FileUploadScale.tsx` | `FILE_UPLOAD` | **blocking** |
87
+ | `MatrixDropdown.tsx` | matrix dropdown/selectbox | |
88
+ | `CustomSliderTrack.tsx` | `SLIDER_MATRIX` or matrix graphics | |
89
+ | `RatingScale.tsx` | `NPS_SCALE` | |
90
+ | `HeatmapScale.tsx` | `HEATMAP` | |
91
+ | `RankOrderScale.tsx` | `RANK_ORDER` | |
92
+ | `lib/heatmapCoords.ts` | `HEATMAP` | copy from `templates/heatmapCoords.ts` |
59
93
 
60
- ## 4. Component Architecture Checklist
94
+ ## 3c. Logic inventory (from fetched survey)
61
95
 
62
- | Component | In survey? | Blueprint doc | Key constraints verified |
63
- |-----------|------------|---------------|--------------------------|
64
- | RatingScale | | `01-components/03-rating-scale.md` | |
65
- | CsatScale | | `01-components/04-csat-scale.md` | |
66
- | CsatMatrixScale | | `01-components/05-csat-matrix-scale.md` | |
67
- | LikertMatrixScale | | `01-components/06-likert-matrix-scale.md` | |
68
- | SliderMatrixScale | | `01-components/07-slider-matrix-scale.md` | |
69
- | FileUploadScale | | `01-components/08-file-upload-scale.md` | |
70
- | CustomSliderTrack | | `01-components/09-custom-slider-track.md` | |
71
- | Header / Footer | | `01-components/10-header-footer.md` | |
72
- | ProgressBar | | `01-components/11-progress-bar.md` | |
73
- | LanguageSelector | | `01-components/12-language-selector.md` | |
74
- | MatrixDropdown | | `01-components/13-matrix-dropdown.md` | |
96
+ Scan `survey.pages` for logic flags. Fill after fetch:
75
97
 
76
- ## 4b. Per-Question Config & UI Coverage
98
+ | questionId | skipLogics | displayLogic | answerLogic (options/rows/cols) | customField refs (`_c_…`) |
99
+ |------------|------------|--------------|----------------------------------|---------------------------|
100
+ | | | | | |
101
+
102
+ Custom field ids found → populate `CUSTOM_FIELD_VALUES` map. See `00-integration/custom-field-logic-and-navigation.md`.
103
+
104
+ ## 3d. Per-Type SDK Integration (from fetched survey)
77
105
 
78
- Fill one row per question in the fetched survey:
106
+ Fill from `00-integration/question-type-sdk-matrix.md`:
79
107
 
80
- | questionId | type | subType | configs detected | uiSpec read | ref component | branches implemented |
81
- |------------|------|---------|------------------|-------------|---------------|---------------------|
82
- | | | | | | | |
108
+ | questionId | type | answer shape | visibility helper | customField refs | component files |
109
+ |------------|------|--------------|-------------------|------------------|-----------------|
110
+ | | | | | | |
83
111
 
84
- - **configs detected:** non-default fields from `02-reference/config-field-index.md` (e.g. `matrixFormat: carousel`, `buttonType: emoji`)
85
- - **uiSpec read:** path from `MANIFEST.json` `uiSpecs` (matrix: use `matrix_{subType}`)
86
- - **ref component:** monorepo: `apps/client/src/components/{Component}.tsx`; npm: implement from ui-spec only
87
- - **branches implemented:** list decision-tree branches from the ui-spec (e.g. dropdown vs grid vs carousel)
88
- - **blocking types:** `matrix`, `slider_matrix`, `file_upload` must show interactive UI, not placeholder text
112
+ ## 4. Component Architecture Checklist
113
+
114
+ | Component | In survey? | Blueprint doc |
115
+ |-----------|------------|---------------|
116
+ | IntroPage / EndPage / PausedPage | | `14` / `15` / `16` |
117
+ | RatingScale | | `03-rating-scale.md` |
118
+ | CsatMatrixScale | | `05-csat-matrix-scale.md` |
119
+ | LikertMatrixScale | | `06-likert-matrix-scale.md` |
120
+ | SliderMatrixScale | | `07-slider-matrix-scale.md` |
121
+ | FileUploadScale | | `08-file-upload-scale.md` |
122
+ | Header / Footer / ProgressBar / LanguageSelector | | `10`–`12` |
123
+
124
+ ## 4b. Per-Question Config & UI Coverage
125
+
126
+ | questionId | type | configs detected | uiSpec read | component files | branches implemented |
127
+ |------------|------|------------------|-------------|-----------------|---------------------|
128
+ | | | | | | |
89
129
 
90
130
  ## 5. Theme, Brand & Logo
91
131
 
92
132
  - [ ] CSS-only logo (no `<img>`, no `public/` uploads)
93
133
  - [ ] Primary / hover colors from client prompt
94
- - [ ] Header / Footer / ProgressBar / LanguageSelector per layout
95
134
 
96
135
  ## 6. Submit & Thank You
97
136
 
98
- - [ ] `onAction({ type: 'SUBMIT' })` on final page
99
- - [ ] Thank-you screen when `submitSurveyResults.data` is set
100
- - [ ] Custom thank-you message from client prompt if provided
137
+ - [ ] `onAction({ type: 'NEXT' })` when `primaryButtonType === 'submit'`
138
+ - [ ] End UI when `state.phase.kind === 'end'` with `EndPage`
101
139
 
102
140
  ## 7. Pre-Build Verification
103
141
 
104
142
  - [ ] No imports from `.../src/` internal paths
105
- - [ ] All mutations via `onAction` — no direct state mutation
106
- - [ ] `npm run build` passes
107
-
108
- ## 7b. Stub-Free Verification
109
-
110
- Per question type in the fetched survey, confirm interactive UI renders (not grey placeholder text):
111
-
112
- | type | subType | Renders real component? | No "not yet implemented" text? |
113
- |------|---------|---------------------------|--------------------------------|
114
- | | | | |
115
-
116
- - [ ] `grep -r "not yet implemented" src/ components/ app/` returns **zero matches**
117
- - [ ] Matrix questions route by `subType` in `Question.tsx`
118
- - [ ] `slider_matrix` shows sliders; `file_upload` shows dropzone
143
+ - [ ] All mutations via `onAction`
144
+ - [ ] Stable `customFieldValues` ref; passed to hook and Question visibility helpers
145
+ - [ ] `bash templates/verify-agent-build.sh` passes
146
+ - [ ] `grep -r "not yet implemented"` → zero matches
147
+
148
+ ## 8. AWS Deploy Readiness (fill if client prompt mentions AWS / CloudFront / S3 / EC2 / Sprinklr)
149
+
150
+ - [ ] Next.js `output: "export"` + `assetPrefix: "./"` configured (see `setup.md#nextjs-aws-static-export`)
151
+ - [ ] `images.unoptimized: true` in `next.config.js`
152
+ - [ ] Build produces `out/index.html` (+ `index.html.txt` copy step in build script)
153
+ - [ ] `SURVEY_INSTANCE_ID` matches deploy JWT (`CFM_INSTANCE_ID`)
154
+ - [ ] Logo/media paths are relative (no leading `/`)
155
+ - [ ] Rank-order uses `option.imageUrl` before `option.previewImageUrl`
156
+ - [ ] `CFM_DEPLOY_API_BASE=http://43.204.26.213:3000` noted in plan
157
+ - [ ] Static file count ≤ 500 (`find out -type f | wc -l`)
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cfm-survey-sdk/templates/survey-inventory.schema.json",
4
+ "title": "Survey Question Inventory",
5
+ "description": "Post-fetch inventory agents fill before Phase 5 BUILD",
6
+ "type": "object",
7
+ "required": ["questions", "uniqueTypes"],
8
+ "properties": {
9
+ "instanceId": { "type": "string", "description": "JWT used to fetch survey" },
10
+ "uniqueTypes": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "string",
14
+ "enum": [
15
+ "nps",
16
+ "mcq",
17
+ "text_input",
18
+ "csat",
19
+ "star_rating",
20
+ "slider",
21
+ "cfm_matrix",
22
+ "rating_matrix",
23
+ "slider_matrix",
24
+ "file_upload",
25
+ "text_and_media"
26
+ ]
27
+ },
28
+ "uniqueItems": true
29
+ },
30
+ "questions": {
31
+ "type": "array",
32
+ "items": {
33
+ "type": "object",
34
+ "required": ["id", "type", "uiSpecRead", "implemented"],
35
+ "properties": {
36
+ "id": { "type": "string" },
37
+ "type": {
38
+ "type": "string",
39
+ "enum": [
40
+ "nps",
41
+ "mcq",
42
+ "text_input",
43
+ "csat",
44
+ "star_rating",
45
+ "slider",
46
+ "cfm_matrix",
47
+ "rating_matrix",
48
+ "slider_matrix",
49
+ "file_upload",
50
+ "text_and_media"
51
+ ]
52
+ },
53
+ "configFlags": {
54
+ "type": "array",
55
+ "items": { "type": "string" },
56
+ "description": "Non-default config, e.g. gridLayout:carousel"
57
+ },
58
+ "uiSpecRead": { "type": "string" },
59
+ "componentFiles": {
60
+ "type": "array",
61
+ "items": { "type": "string" }
62
+ },
63
+ "branchesImplemented": {
64
+ "type": "array",
65
+ "items": { "type": "string" }
66
+ },
67
+ "implemented": { "type": "boolean" }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ # Agent build verification — run from project root after Phase 5 BUILD
3
+ set -euo pipefail
4
+
5
+ echo "=== CFM Survey SDK — Agent Build Verification ==="
6
+
7
+ STUB_MATCHES=$(grep -r "not yet implemented" src/ components/ app/ 2>/dev/null || true)
8
+ if [ -n "$STUB_MATCHES" ]; then
9
+ echo "FAIL: Stub placeholder text found:"
10
+ echo "$STUB_MATCHES"
11
+ exit 1
12
+ fi
13
+ echo "OK: No stub placeholder text"
14
+
15
+ INTERNAL_IMPORTS=$(grep -rE "@explorer02/cfm-survey-sdk/src|@repo/sdk/src" src/ components/ app/ 2>/dev/null || true)
16
+ if [ -n "$INTERNAL_IMPORTS" ]; then
17
+ echo "FAIL: Internal SDK imports found:"
18
+ echo "$INTERNAL_IMPORTS"
19
+ exit 1
20
+ fi
21
+ echo "OK: No internal SDK imports"
22
+
23
+ REQUIRED_TYPES=(MCQ TEXTFIELD NPS_SCALE CFM_MATRIX CSAT_MATRIX RATING_MATRIX SLIDER_MATRIX FILE_UPLOAD TEXT_AND_MEDIA HEATMAP RANK_ORDER)
24
+ QUESTION_FILE=""
25
+ for candidate in src/components/Question.tsx components/Question.tsx app/components/Question.tsx; do
26
+ if [ -f "$candidate" ]; then
27
+ QUESTION_FILE="$candidate"
28
+ break
29
+ fi
30
+ done
31
+
32
+ if [ -z "$QUESTION_FILE" ]; then
33
+ echo "WARN: Question.tsx not found in expected paths — skip type coverage check"
34
+ else
35
+ echo "Checking dispatcher types in $QUESTION_FILE"
36
+ for t in "${REQUIRED_TYPES[@]}"; do
37
+ if ! grep -q "QUESTION_TYPE.${t}" "$QUESTION_FILE" && \
38
+ ! grep -q "type === '${t}'" "$QUESTION_FILE" && \
39
+ ! grep -q "type === \"${t}\"" "$QUESTION_FILE"; then
40
+ echo "FAIL: Missing dispatcher branch for type: ${t}"
41
+ exit 1
42
+ fi
43
+ done
44
+ if grep -q "subType" "$QUESTION_FILE"; then
45
+ echo "FAIL: Legacy subType routing found in Question.tsx — use flat question.type literals"
46
+ exit 1
47
+ fi
48
+ echo "OK: All 11 question.type branches present"
49
+ fi
50
+
51
+ echo "Running npm run build..."
52
+ npm run build
53
+ echo "=== All checks passed ==="
@@ -1,40 +0,0 @@
1
- # CSAT & Rating Architectural Blueprints
2
-
3
- > **UI spec (authoritative):** `03-ui-specs/04-csat.md`
4
- > **Reference implementation:** `apps/client/src/components/CsatScale.tsx`
5
- > **Target Components**: `CsatScale`
6
- > **Handles**: `type: 'csat'`, `'rating'`, `'rating_scale'`, CSAT matrices.
7
-
8
- ## Core Responsibility
9
- Render sentiment arrays (Faces, Stars, Numbers) ensuring that active selections are unmistakably highlighted using bounding boxes or strong border accents, NEVER just by changing the SVG fill color.
10
-
11
- ## State Management & Mapping
12
- - **Emojis**: `buttonType === 'emoji'`
13
- - Map values using `getEmojiForIndex(index, totalLength)` from your icons utility.
14
- - **Stars**: `buttonType === 'star'`
15
- - Active stars (index <= selectedValue) get `CsatStarIcons.filled`.
16
- - **Numbers**: Default for rating scales.
17
- - Apply `option.color` to the background if provided by the SDK (NPS tracking).
18
-
19
- ## UX Bounding Box Rules (CRITICAL)
20
-
21
- The UI must exactly match the "Active Box" standard.
22
-
23
- ### For Emojis & Stars:
24
- - **Unselected**: `inline-flex p-2 border-2 border-transparent opacity-40 hover:opacity-100 hover:scale-110 transition-all cursor-pointer`.
25
- - **Selected**: The specific selected icon MUST be wrapped in a prominent square box with rounded corners.
26
- - **CSS**: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110 opacity-100`.
27
-
28
- ### For Numbered Badges (Rating):
29
- - **Shape**: MUST be square with rounded corners (`rounded-lg w-12 h-12`). Do not use full circles.
30
- - **Selected**: `border-2 border-[#e20074] opacity-100 font-bold scale-105 shadow-md`.
31
- - **Unselected**: `opacity-30 border-transparent`.
32
-
33
- ## Bounding Boxes inside Matrices
34
- When emojis or stars are rendered inside `CsatMatrixScale`, the exact same bounding box logic applies. The table cell `<td>` should center the icon, and the icon wrapper `<div>` or `<button>` should toggle between `border-transparent` and `border-[#e20074]` based on the active state.
35
-
36
- ## Tooltips & Label Positions
37
- - **Tooltips**: Every single emoji, star, and badge MUST have `title={label}` applied so the user sees the semantic text string on hover.
38
- - **Labels (`minLabel`, `midLabel`, `maxLabel`)**:
39
- - Must be rendered absolutely below the track.
40
- - `midLabel` must use inline styles to calculate its exact centered position: `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
@@ -1,44 +0,0 @@
1
- # Rating Question (NPS 0-10)
2
-
3
- > **Type**: `'rating'` | **API Source**: `SCALE` with `SCALE_TYPE=TEN_POINT` or `NPS_SCALE`
4
- > **Mapper**: `ratingMapper.ts` | **Component**: `RatingScale`
5
-
6
- ## TypeScript Type
7
-
8
- ```typescript
9
- type RatingQuestion = QuestionBase & {
10
- type: 'rating';
11
- options: SurveyOption[]; // 0-10 with NPS traffic-light colors
12
- minLabel?: string; // Left label (e.g. "Not Likely")
13
- midLabel?: string; // Middle label (e.g. "Neutral")
14
- maxLabel?: string; // Right label (e.g. "Extremely Likely")
15
- midLabelIndex?: number; // Fractional position for mid-label CSS
16
- reverseScaleOrder?: boolean; // Render high→low
17
- buttonVariant?: 'radio' | 'numbered' | 'emoji';
18
- };
19
- ```
20
-
21
- ## Answer Shape: `number`
22
- Example: `7` (the selected badge value)
23
-
24
- ## NPS Traffic-Light Colors
25
- ```
26
- 0: #e2001a (red) 3: #f18b00 (orange) 6: #fcd900 (yellow)
27
- 1: #e4251b 4: #f7b200 7: #9fca00 (lime)
28
- 2: #ec610a 5: #fcd900 8: #6bb300 (green)
29
- 9: #339a00
30
- 10: #008000
31
- ```
32
-
33
- ## Mid-Label Positioning
34
- ```typescript
35
- // CSS left percentage for mid-label
36
- const left = (midLabelIndex / (options.length - 1)) * 100;
37
- // For 11-point scale: midLabelIndex defaults to 7.5
38
- ```
39
-
40
- ## Rendering Guidance
41
- - Display as horizontal row of numbered badge buttons
42
- - Each badge uses `option.color` as background
43
- - Selected badge: emphasized border/scale, all others dimmed
44
- - Labels positioned: minLabel (left), midLabel (calculated %), maxLabel (right)