@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
@@ -3,9 +3,9 @@ You are an AI coding agent tasked with building a survey UI using the CFM Survey
3
3
  ## Start Here
4
4
 
5
5
  **Monorepo:** read `packages/sdk/templates/docs/index.md`
6
- **Installed package:** read `node_modules/@explorer02/cfm-survey-sdk/docs/index.md`
6
+ **Installed package:** read `node_modules/@explorer02/cfm-survey-sdk/templates/docs/index.md`
7
7
 
8
- Also read `MANIFEST.json` in the same `docs/` folder for machine-readable phase and question-type routing.
8
+ Also read `MANIFEST.json` in the same `templates/docs/` folder for machine-readable phase and question-type routing.
9
9
 
10
10
  ## Package Name
11
11
 
@@ -20,14 +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 `useSurveySDK.md` hook contract |
24
- | 4 | Read all `01-components/`; inventory survey types; read `03-ui-specs/` per type; copy `templates/Question.tsx` + `implementation_plan.md` |
25
- | 5 | Build UI — create all scale files from `component-checklist.md`; no stub placeholders |
26
- | 6 | Verify build checklist |
27
- | 7 | Deploy on client confirmation |
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
+ | 6 | Run `templates/verify-agent-build.sh` |
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
- **Do NOT** render `"not yet implemented"` or default fallback stubs in `Question.tsx`. Copy `docs/templates/Question.tsx` and create all required scale components.
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
+
33
+ **Copy scale templates from `docs/templates/`** before implementing from ui-spec recipes: `RankOrderScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx`, `surveyUiIcons.tsx`, `heatmapCoords.ts`.
34
+
35
+ **Do NOT** route matrix questions by `subType` — use flat types: `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX`, `SLIDER_MATRIX`.
36
+
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.
38
+
39
+ **FILE_UPLOAD:** wire presigned upload per `00-integration/file-upload-aws.md` — raw `File[]` does not submit.
40
+
41
+ **Monorepo:** run `npm run build` in `packages/sdk` before starting the client dev server.
32
42
 
33
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.
@@ -0,0 +1,50 @@
1
+ # Answer Logic and Navigation
2
+
3
+ > Integrator-facing behavior contract. Answer logic evaluation is handled inside the SDK — use exported visibility helpers; do not import internal `surveyLogic` paths directly.
4
+
5
+ ## Distinction from display logic
6
+
7
+ Skip logic, display logic, and answer logic share the **same condition tree** and **same evaluator** (`evaluateCondition`). They differ by **what** the condition gates:
8
+
9
+ | Kind | API attachment | SDK field | When true |
10
+ |------|----------------|-----------|-----------|
11
+ | `SKIP` | Question `controlConfig.skipLogics` | `question.skipLogics` | Navigate to skip target or end of survey |
12
+ | `DISPLAY` | Question `controlConfig.displayLogic` | `question.displayLogic` | Show the whole question (dynamic or page-level) |
13
+ | `ANSWER` | Sub-entity `controlConfig.displayLogic` | `answerLogic` on options / rows / columns | Show that option, statement row, or scale column |
14
+
15
+ The API uses `displayLogic` on sub-entities (MCQ `answerDetails`, matrix `childSurveyQuestions`, scale `answerDetails` / `matrixColumnQuestions`). The SDK maps these to `answerLogic` to avoid confusion with question-level display logic.
16
+
17
+ ## Supported question types
18
+
19
+ | Type | Answer logic on | Not supported |
20
+ |------|-----------------|---------------|
21
+ | MCQ | Each option | — |
22
+ | SLIDER_MATRIX | Each statement row | Scale columns |
23
+ | CSAT_MATRIX | Each statement row + each scale column | — |
24
+ | RATING_MATRIX | Each statement row only | Scale columns |
25
+ | CFM_MATRIX | Each statement row + each scale column | — |
26
+ | RANK_ORDER | Each option | — |
27
+ | NPS_SCALE, TEXTFIELD, FILE_UPLOAD, TEXT_AND_MEDIA | — | No answer logic |
28
+
29
+ ## Page breaks
30
+
31
+ Adding answer logic in the survey builder creates a **page break before that question** (backend page structure). The SDK does not split pages at runtime; it filters visible items within the current page.
32
+
33
+ If **every** answer item is hidden (all options, all rows, or all scale columns), the question is treated as not answerable and may disappear from `state.currentQuestions` (same pattern as display logic empty pages).
34
+
35
+ ## Client responsibilities
36
+
37
+ 1. Use `state.currentQuestions` from `useSurveySDK` — question-level display logic is already applied.
38
+ 2. **MCQ:** Filter options with `getVisibleMcqOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`.
39
+ 3. **Rank order:** Filter options with `getVisibleRankOrderOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`.
40
+ 4. **Matrix (CSAT, CFM, RATING, SLIDER):** Filter rows and (for CSAT/CFM) columns with `getVisibleMatrixItemsForAnswers(question, answers, allQuestions, customFieldValues?)`.
41
+ 5. Pass the same `customFieldValues` map used in `useSurveySDK` options when logic rules reference `CUSTOM_FIELD` entities — see [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
42
+ 6. Wire `onAction({ type: 'CHANGE' })` so answer logic re-evaluates when prior answers change.
43
+
44
+ ## Progress and validation
45
+
46
+ - Progress and validation count only **visible** options, statement rows, and scale columns.
47
+ - Hidden selections are stripped automatically on answer change via SDK sanitization.
48
+ - Required questions with zero visible options are not blocked (treated as answered).
49
+
50
+ See also: [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`progress.md`](progress.md).
@@ -0,0 +1,281 @@
1
+ # AWS Deploy (EC2 Middle API → S3 → CloudFront)
2
+
3
+ > Deploy a **static** survey UI to Sprinklr AWS using the hosted EC2 deploy API.
4
+ > **Do not** embed AWS credentials in client code — the API uses IAM roles on EC2 to presign S3 URLs.
5
+
6
+ Use this doc when the client prompt mentions **AWS**, **CloudFront**, **S3**, **EC2**, or **Sprinklr hosting**.
7
+ For Vercel deploy, use `npx cfm-sdk deploy` instead (see `index.md` Phase 7).
8
+
9
+ ---
10
+
11
+ ## Architecture — Three-Channel Flow
12
+
13
+ ```mermaid
14
+ sequenceDiagram
15
+ participant Dev as Agent_DevMachine
16
+ participant API as EC2_Deploy_API
17
+ participant S3 as Hosting_Bucket
18
+ participant CF as CloudFront
19
+
20
+ Dev->>Dev: yarn build or npm run build
21
+ Dev->>API: POST /deploy/start + JWT + file manifest
22
+ API-->>Dev: deployId + presigned PUT URLs
23
+ loop each file in out/
24
+ Dev->>S3: PUT file direct
25
+ end
26
+ Dev->>API: POST /deploy/complete
27
+ API->>S3: HeadObject index.html
28
+ API->>CF: CreateInvalidation
29
+ API-->>Dev: public HTTPS URL
30
+ ```
31
+
32
+ | Channel | Direction | Action |
33
+ |---------|-----------|--------|
34
+ | 1 Control | Dev → EC2 API | `POST /deploy/start` with JWT + file manifest |
35
+ | 2 Data | Dev → S3 direct | `PUT` each static file to presigned URLs |
36
+ | 3 Finalize | Dev → EC2 API | `POST /deploy/complete` → CloudFront URL |
37
+
38
+ S3 path shape: `sites/{surveyId}/{deployId}/` — extracted from JWT at deploy time.
39
+
40
+ ---
41
+
42
+ ## Prerequisites (verify before deploy)
43
+
44
+ 1. **Phase 6 passed** — `npm run build` / `yarn build` exits 0
45
+ 2. **JWT alignment** — deploy JWT **must equal** `SURVEY_INSTANCE_ID` in `SurveyPage.tsx`
46
+ 3. **Static output exists** — Next.js: `out/index.html`; Vite: `dist/index.html`
47
+ 4. **File count ≤ 500** (API limit)
48
+
49
+ ### Environment variables (dev machine)
50
+
51
+ ```bash
52
+ export CFM_INSTANCE_ID="<same JWT as SurveyPage SURVEY_INSTANCE_ID>"
53
+ export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
54
+ export OUT_DIR="<absolute path to static output folder>"
55
+ # Next.js example:
56
+ # export OUT_DIR="/path/to/project/out"
57
+ # Vite example:
58
+ # export OUT_DIR="/path/to/project/dist"
59
+ ```
60
+
61
+ ### Health check
62
+
63
+ ```bash
64
+ curl -s "$CFM_DEPLOY_API_BASE/health"
65
+ # Expected: {"ok":true,"service":"sprinklr-deploy-api"}
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Build Config (apply during Phase 5 BUILD)
71
+
72
+ ### Next.js — static export (required for AWS)
73
+
74
+ See also [`setup.md#nextjs-aws-static-export`](setup.md#nextjs-aws-static-export).
75
+
76
+ **`next.config.js`:**
77
+
78
+ ```javascript
79
+ /** @type {import('next').NextConfig} */
80
+ const nextConfig = {
81
+ reactStrictMode: true,
82
+ transpilePackages: ['@explorer02/cfm-survey-sdk'], // or '@repo/sdk'
83
+ output: 'export',
84
+ assetPrefix: './', // required for nested CloudFront path
85
+ images: { unoptimized: true },
86
+ };
87
+ module.exports = nextConfig;
88
+ ```
89
+
90
+ **`package.json` build script** — copy RSC payload for `index.html` URLs:
91
+
92
+ ```json
93
+ "build": "next build && cp out/index.txt out/index.html.txt"
94
+ ```
95
+
96
+ | Setting | Why |
97
+ |---------|-----|
98
+ | `output: 'export'` | Produces uploadable `out/` folder (no Node server) |
99
+ | `assetPrefix: './'` | Assets resolve under `/sites/{surveyId}/{deployId}/`, not CloudFront root |
100
+ | `images.unoptimized` | Image optimization needs a server; static export does not |
101
+ | `index.html.txt` copy | Fixes 403 on `index.html.txt?_rsc` when page URL ends with `index.html` |
102
+
103
+ ### Vite
104
+
105
+ Set `OUT_DIR` to `dist/`. Ensure asset paths in built HTML are **relative** (no leading `/`). See Vite `base: './'` if needed.
106
+
107
+ ---
108
+
109
+ ## Static Asset Rules (apply during BUILD)
110
+
111
+ | Rule | Detail |
112
+ |------|--------|
113
+ | Public logos | Use relative paths (`logo.svg`), not `/logo.svg` |
114
+ | Rank-order images | Prefer `option.imageUrl` over `option.previewImageUrl` — preview `*_p.*` URLs often return 403 |
115
+ | Footer hash links | Use plain `<a href="#">`, not `next/link` — avoids RSC prefetch on static export |
116
+ | No AWS keys in code | JWT only in `Authorization: Bearer` header at deploy time |
117
+
118
+ ---
119
+
120
+ ## Deploy Procedure (agent executes in order)
121
+
122
+ ### Step 0 — Build
123
+
124
+ ```bash
125
+ cd <project-root>
126
+ yarn build # or npm run build
127
+ test -f "$OUT_DIR/index.html" || { echo "Missing index.html"; exit 1; }
128
+ find "$OUT_DIR" -type f | wc -l # must be ≤ 500
129
+ ```
130
+
131
+ ### Step 1 — Channel 1: manifest + start
132
+
133
+ ```bash
134
+ python3 <<'PY' > /tmp/deploy-manifest.json
135
+ import json, os, mimetypes
136
+ from pathlib import Path
137
+ out = Path(os.environ["OUT_DIR"])
138
+ files = []
139
+ for path in sorted(out.rglob("*")):
140
+ if not path.is_file():
141
+ continue
142
+ rel = path.relative_to(out).as_posix()
143
+ mime, _ = mimetypes.guess_type(rel)
144
+ files.append({
145
+ "path": rel,
146
+ "contentType": mime or "application/octet-stream",
147
+ "sizeBytes": path.stat().st_size,
148
+ })
149
+ print(json.dumps({"files": files}))
150
+ print(f"files: {len(files)}", file=__import__('sys').stderr)
151
+ PY
152
+
153
+ curl -s -X POST "$CFM_DEPLOY_API_BASE/deploy/start" \
154
+ -H "Authorization: Bearer $CFM_INSTANCE_ID" \
155
+ -H "Content-Type: application/json" \
156
+ -d @/tmp/deploy-manifest.json \
157
+ | tee /tmp/deploy-start.json
158
+ ```
159
+
160
+ **Success:** JSON with `deployId`, `s3Prefix`, `uploads[]`.
161
+
162
+ | Error | Fix |
163
+ |-------|-----|
164
+ | `401 TOKEN_EXPIRED` | Ask client for fresh instanceId JWT |
165
+ | `503 DEPLOY_UNAVAILABLE` | EC2 hosting env misconfigured — report to operator |
166
+ | `400 files must not exceed 500` | Reduce build output |
167
+
168
+ ### Step 2 — Channel 2: upload every file to S3
169
+
170
+ ```bash
171
+ python3 <<'PY'
172
+ import json, os, subprocess
173
+ from pathlib import Path
174
+
175
+ out = Path(os.environ["OUT_DIR"])
176
+ start = json.load(open("/tmp/deploy-start.json"))
177
+ manifest = {f["path"]: f["contentType"] for f in json.load(open("/tmp/deploy-manifest.json"))["files"]}
178
+ failed = []
179
+
180
+ for item in start["uploads"]:
181
+ path = item["path"]
182
+ url = item["uploadUrl"]
183
+ mime = manifest.get(path, "application/octet-stream")
184
+ proc = subprocess.run(
185
+ ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
186
+ "-X", "PUT", url, "-H", f"Content-Type: {mime}",
187
+ "--data-binary", f"@{out / path}"],
188
+ capture_output=True, text=True,
189
+ )
190
+ code = proc.stdout.strip()
191
+ mark = "OK" if code == "200" else "FAIL"
192
+ print(f"{mark} {code} {path}")
193
+ if code != "200":
194
+ failed.append(path)
195
+
196
+ if failed:
197
+ print(f"\n{len(failed)} failed — rerun from Step 1 (presigned URLs expire)")
198
+ raise SystemExit(1)
199
+ print("\nAll uploads succeeded")
200
+ PY
201
+ ```
202
+
203
+ **Success:** every line shows `OK 200`.
204
+
205
+ ### Step 3 — Channel 3: complete + live URL
206
+
207
+ ```bash
208
+ DEPLOY_ID=$(python3 -c "import json; print(json.load(open('/tmp/deploy-start.json'))['deployId'])")
209
+
210
+ curl -s -X POST "$CFM_DEPLOY_API_BASE/deploy/complete" \
211
+ -H "Authorization: Bearer $CFM_INSTANCE_ID" \
212
+ -H "Content-Type: application/json" \
213
+ -d "{\"deployId\":\"$DEPLOY_ID\"}" \
214
+ | tee /tmp/deploy-complete.json
215
+
216
+ python3 -c "import json; print('\nLive URL:\n' + json.load(open('/tmp/deploy-complete.json'))['url'])"
217
+ ```
218
+
219
+ **Success:**
220
+
221
+ ```json
222
+ {
223
+ "url": "https://<cloudfront-domain>/sites/{surveyId}/{deployId}/index.html",
224
+ "deployId": "...",
225
+ "surveyId": "..."
226
+ }
227
+ ```
228
+
229
+ ### Step 4 — Verify
230
+
231
+ ```bash
232
+ LIVE_URL=$(python3 -c "import json; print(json.load(open('/tmp/deploy-complete.json'))['url'])")
233
+ curl -s -o /dev/null -w "index.html: %{http_code}\n" "$LIVE_URL"
234
+ # Optional: grep one CSS href from HTML and curl it under the deploy prefix — expect 200
235
+ ```
236
+
237
+ Tell the client to open the printed URL in a browser.
238
+
239
+ ---
240
+
241
+ ## One-Command Wrapper (optional)
242
+
243
+ After build, from project root:
244
+
245
+ ```bash
246
+ export CFM_INSTANCE_ID="<JWT>"
247
+ export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
248
+ export OUT_DIR="<absolute path to out/ or dist/>"
249
+
250
+ bash templates/deploy-to-aws.sh
251
+ # or: bash node_modules/@explorer02/cfm-survey-sdk/templates/docs/templates/deploy-to-aws.sh
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Repeat Deploys
257
+
258
+ Each deploy creates a **new** `deployId` folder under `sites/{surveyId}/`. Old deploys remain in S3.
259
+
260
+ After UI changes: rebuild → re-run Steps 1–3 (same `CFM_INSTANCE_ID` if same survey).
261
+
262
+ ---
263
+
264
+ ## Troubleshooting
265
+
266
+ | Symptom | Cause | Agent fix |
267
+ |---------|-------|-----------|
268
+ | 403 on `/_next/static/...` | Absolute asset paths | Add `assetPrefix: "./"`, rebuild |
269
+ | 403 on `index.html.txt?_rsc` | Missing RSC payload file | Add `cp out/index.txt out/index.html.txt` to build script |
270
+ | 403 on `*_p.png` / `*_p.jpg` images | Preview URL used | Use `option.imageUrl \|\| option.previewImageUrl` in RankOrderScale |
271
+ | 401 `TOKEN_EXPIRED` | JWT expired | Ask client for new instanceId |
272
+ | 503 `DEPLOY_UNAVAILABLE` | EC2 CloudFront/S3 env wrong | Report to operator |
273
+ | Some PUTs 403 | Presigned URLs expired (default 5 min) | Restart from Step 1; complete within window |
274
+ | Blank page, HTML 200 | JS/CSS not uploaded or wrong paths | Re-check Channel 2 logs |
275
+ | Survey API errors in browser | Deploy JWT ≠ SurveyPage JWT | Align `SURVEY_INSTANCE_ID` |
276
+
277
+ ---
278
+
279
+ ## Future: CLI automation (Phase 7b)
280
+
281
+ `npx cfm-sdk deploy --aws` is **not implemented yet**. Use the manual flow above or `templates/deploy-to-aws.sh` until the CLI ships.