@explorer02/cfm-survey-sdk 0.2.3 → 0.2.5

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 (100) hide show
  1. package/dist/cli/index.js +141 -33
  2. package/dist/cli/index.mjs +141 -33
  3. package/dist/index.js +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/package.json +3 -1
  6. package/postinstall.js +9 -5
  7. package/templates/AGENT.md +60 -19
  8. package/templates/deploy-wizard-to-s3.sh +52 -0
  9. package/templates/docs/00-integration/agent-execution-flow.md +293 -0
  10. package/templates/docs/00-integration/agent-operating-contract.md +268 -0
  11. package/templates/docs/00-integration/apply-ui-config.md +390 -0
  12. package/templates/docs/00-integration/aws-deploy.md +117 -3
  13. package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
  14. package/templates/docs/00-integration/client-integration-guide.md +9 -7
  15. package/templates/docs/00-integration/client-lib-folder.md +115 -0
  16. package/templates/docs/00-integration/component-checklist.md +90 -62
  17. package/templates/docs/00-integration/constraints.md +14 -5
  18. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
  19. package/templates/docs/00-integration/mockup-ui-extraction.md +113 -0
  20. package/templates/docs/00-integration/placeholders-and-custom-fields-wiring.md +192 -0
  21. package/templates/docs/00-integration/placeholders-and-tokens.md +2 -1
  22. package/templates/docs/00-integration/progress.md +4 -2
  23. package/templates/docs/00-integration/setup.md +12 -0
  24. package/templates/docs/00-integration/ui-customization-wizard.md +123 -0
  25. package/templates/docs/00-integration/useSurveySDK.md +35 -1
  26. package/templates/docs/00-integration/wizard-api.md +126 -0
  27. package/templates/docs/00-integration/wizard-config-handoff.md +292 -0
  28. package/templates/docs/00-integration/wizard-preview-build-guide.md +405 -0
  29. package/templates/docs/00-integration/wizard-question-type-styling.md +197 -0
  30. package/templates/docs/00-integration/wizard-troubleshooting.md +80 -0
  31. package/templates/docs/01-components/01-survey-page.md +18 -8
  32. package/templates/docs/01-components/02-question.md +2 -2
  33. package/templates/docs/01-components/03-rating-scale.md +12 -18
  34. package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
  35. package/templates/docs/01-components/06-likert-matrix-scale.md +19 -39
  36. package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
  37. package/templates/docs/01-components/09-custom-slider-track.md +15 -25
  38. package/templates/docs/01-components/10-header-footer.md +23 -4
  39. package/templates/docs/01-components/11-progress-bar.md +14 -6
  40. package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
  41. package/templates/docs/01-components/README.md +2 -2
  42. package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
  43. package/templates/docs/02-reference/routing-table.md +1 -0
  44. package/templates/docs/02-reference/value-derivation.md +1 -1
  45. package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
  46. package/templates/docs/03-ui-specs/01-rating.md +18 -0
  47. package/templates/docs/03-ui-specs/02-radio.md +16 -1
  48. package/templates/docs/03-ui-specs/03-text.md +12 -1
  49. package/templates/docs/03-ui-specs/04-csat.md +84 -50
  50. package/templates/docs/03-ui-specs/06-slider.md +3 -1
  51. package/templates/docs/03-ui-specs/07-matrix-cfm.md +71 -48
  52. package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +48 -18
  53. package/templates/docs/03-ui-specs/09-slider-matrix.md +59 -32
  54. package/templates/docs/03-ui-specs/10-file-upload.md +11 -0
  55. package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
  56. package/templates/docs/03-ui-specs/12-survey-chrome.md +54 -5
  57. package/templates/docs/03-ui-specs/13-heatmap.md +12 -1
  58. package/templates/docs/03-ui-specs/14-rank-order.md +11 -0
  59. package/templates/docs/03-ui-specs/README.md +4 -2
  60. package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
  61. package/templates/docs/MANIFEST.json +180 -7
  62. package/templates/docs/index.md +114 -117
  63. package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
  64. package/templates/docs/templates/FileUploadScale.tsx +262 -0
  65. package/templates/docs/templates/HeatmapScale.tsx +114 -0
  66. package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
  67. package/templates/docs/templates/Question.tsx +3 -0
  68. package/templates/docs/templates/RatingScale.tsx +129 -0
  69. package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
  70. package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
  71. package/templates/docs/templates/customFieldValues.ts +36 -0
  72. package/templates/docs/templates/deploy-to-aws.sh +74 -0
  73. package/templates/docs/templates/implementation_plan.md +64 -10
  74. package/templates/docs/templates/labelStyles.ts +33 -0
  75. package/templates/docs/templates/selectionStyles.ts +100 -0
  76. package/templates/docs/templates/surveyUiIcons.tsx +11 -0
  77. package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
  78. package/templates/docs/templates/verify-agent-build.sh +119 -0
  79. package/templates/preview-harness/preview-bridge.inline.js +201 -0
  80. package/templates/preview-harness/previewPages.js +108 -0
  81. package/templates/preview-harness/previewPages.ts +298 -0
  82. package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
  83. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
  84. package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
  85. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
  86. package/templates/preview-harness/vite-app/src/globals.css +16 -0
  87. package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
  88. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
  89. package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
  90. package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
  91. package/templates/previewBridge.ts +153 -0
  92. package/templates/survey-theme.css +295 -0
  93. package/templates/survey-ui-config.schema.json +213 -0
  94. package/templates/wizard-dist/assets/PreviewMock-AoZdAEPZ.js +1 -0
  95. package/templates/wizard-dist/assets/TypePanel-39FnbtvG.js +1 -0
  96. package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
  97. package/templates/wizard-dist/assets/index-CWKdsJOp.js +34 -0
  98. package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
  99. package/templates/wizard-dist/index.html +20 -0
  100. package/templates/wizard.html +13 -1129
@@ -110,13 +110,104 @@ Set `OUT_DIR` to `dist/`. Ensure asset paths in built HTML are **relative** (no
110
110
 
111
111
  | Rule | Detail |
112
112
  |------|--------|
113
- | Public logos | Use relative paths (`logo.svg`), not `/logo.svg` |
113
+ | Public logos | Use **relative** paths (`./logo.svg` for AWS static export), not `/logo.svg` or `https://…` |
114
+ | Wizard logos | Must exist in `public/` **and** `out/` before deploy — see § Logos & local assets below |
114
115
  | Rank-order images | Prefer `option.imageUrl` over `option.previewImageUrl` — preview `*_p.*` URLs often return 403 |
115
116
  | Footer hash links | Use plain `<a href="#">`, not `next/link` — avoids RSC prefetch on static export |
116
117
  | No AWS keys in code | JWT only in `Authorization: Bearer` header at deploy time |
117
118
 
118
119
  ---
119
120
 
121
+ ## Logos & local assets (wizard → S3 → CloudFront)
122
+
123
+ ### Why logos break on CloudFront
124
+
125
+ The wizard **preview** shows logos from **browser memory** (`data:` URLs / base64). That works on `localhost:4200` only. CloudFront serves files from the **deploy bundle** at:
126
+
127
+ ```
128
+ https://<cloudfront>/sites/{surveyId}/{deployId}/<filename>
129
+ ```
130
+
131
+ It does **not** read wizard preview memory, and it does **not** automatically include logos stored under the separate wizard asset path (`sites/{surveyId}/assets/{fileName}`). Those S3 assets are for wizard handoff / re-download — the agent must copy the file into `public/` and ship it inside `out/` on every deploy.
132
+
133
+ ```mermaid
134
+ flowchart LR
135
+ subgraph wizard [Wizard session]
136
+ Upload[Client uploads logo]
137
+ Mem[Preview: base64 in memory]
138
+ Disk[public/logo_*.jpg local OR S3 assets/ EC2]
139
+ end
140
+ subgraph agent [Agent Phase 6c]
141
+ Apply[Header data-cfm-logo src=./fileName]
142
+ Build[yarn build copies public to out/]
143
+ end
144
+ subgraph deploy [Phase 7 AWS]
145
+ S3[sites/surveyId/deployId/logo_*.jpg]
146
+ CF[CloudFront serves same prefix as index.html]
147
+ end
148
+ Upload --> Mem
149
+ Upload --> Disk
150
+ Disk --> Apply
151
+ Apply --> Build
152
+ Build --> S3
153
+ S3 --> CF
154
+ ```
155
+
156
+ ### Two storage locations (do not confuse)
157
+
158
+ | Location | When written | Served on live survey? |
159
+ |----------|--------------|------------------------|
160
+ | `public/{fileName}` on dev machine | `customize` upload-logo API, or `confirm-ui-config` download | **Only after** `build` copies into `out/` and deploy uploads |
161
+ | `sites/{surveyId}/assets/{fileName}` on S3 | EC2 wizard **Review & Build** complete | **No** — not in deploy prefix; use only as download source for `confirm-ui-config` |
162
+
163
+ ### Correct agent flow (mandatory when wizard uploaded a logo)
164
+
165
+ **After Phase 6c apply, before Phase 7 deploy:**
166
+
167
+ 1. Read `global.logo.fileName` from `survey-ui-config.json` (local) or `.final.json` (EC2).
168
+ 2. Ensure the binary exists in `public/{fileName}`:
169
+ - **Local `customize`:** file written by `POST /api/upload-logo` when client uploaded in wizard.
170
+ - **EC2 `customize-ui`:** run `npx cfm-sdk confirm-ui-config` — downloads from `global.logo.url` (CloudFront assets URL) to `public/`.
171
+ 3. Update `Header.tsx`:
172
+
173
+ ```tsx
174
+ <img data-cfm-logo src="./logo_1783069846059_download2.jpg" alt="Brand" />
175
+ ```
176
+
177
+ Use `./{fileName}` for AWS static export (`assetPrefix: './'`). **Never** set `src` to:
178
+ - `global.logo.url` when it is a `data:` URL (preview memory)
179
+ - `https://…/sites/.../assets/...` (wrong path — outside deploy folder)
180
+ - `/logo.png` (absolute — breaks under nested CloudFront prefix)
181
+
182
+ 4. Rebuild and verify file is in static output:
183
+
184
+ ```bash
185
+ yarn build
186
+ LOGO=$(python3 -c "import json; print(json.load(open('survey-ui-config.json'))['global']['logo']['fileName'])")
187
+ test -f "public/$LOGO" && test -f "out/$LOGO" && echo "Logo ready for deploy"
188
+ ```
189
+
190
+ 5. Deploy — `deploy-to-aws.sh` uploads **every file under `out/`**, including the logo.
191
+
192
+ 6. Post-deploy verify (same deploy prefix as `index.html`):
193
+
194
+ ```bash
195
+ # If index.html is .../66593925-.../index.html, logo must be:
196
+ # .../66593925-.../logo_*.jpg → HTTP 200
197
+ ```
198
+
199
+ ### Pre-deploy checklist (logo)
200
+
201
+ - [ ] `global.logo.fileName` set in active config
202
+ - [ ] `public/{fileName}` exists on disk
203
+ - [ ] `Header.tsx` `data-cfm-logo` `src="./{fileName}"` (or `./your-seed-logo.svg` if no wizard upload)
204
+ - [ ] `out/{fileName}` exists after `yarn build`
205
+ - [ ] No `data:` or `https://` logo `src` in Header/Footer
206
+
207
+ See also: [`apply-ui-config.md`](apply-ui-config.md) § Logo file, [`wizard-config-handoff.md`](wizard-config-handoff.md) § Logo capture.
208
+
209
+ ---
210
+
120
211
  ## Deploy Procedure (agent executes in order)
121
212
 
122
213
  ### Step 0 — Build
@@ -253,7 +344,26 @@ bash templates/deploy-to-aws.sh
253
344
 
254
345
  ---
255
346
 
256
- ## Repeat Deploys
347
+ ## EC2 operator setup (one-time)
348
+
349
+ On `43.204.26.213`, `/home/ec2-user/deploy-api/.env` must use **real** CloudFront values (not `.env.example` placeholders):
350
+
351
+ ```bash
352
+ HOSTING_BUCKET=sprinklr-cfm-survey-sites-prod-unnat
353
+ CLOUDFRONT_DISTRIBUTION_ID=EY6QXOHVFMVZ2
354
+ CLOUDFRONT_BASE_URL=https://d81qbkjijbkjd.cloudfront.net
355
+ ```
356
+
357
+ Verify after `pm2 restart sprinklr-api`:
358
+
359
+ ```bash
360
+ curl -s http://43.204.26.213:3000/health/cloudfront | python3 -m json.tool
361
+ # expect "ok": true
362
+ ```
363
+
364
+ Placeholder IDs like `E1234567890ABC` cause `STORAGE_UNAVAILABLE` on `/deploy/complete` even when S3 uploads succeed.
365
+
366
+ ---
257
367
 
258
368
  Each deploy creates a **new** `deployId` folder under `sites/{surveyId}/`. Old deploys remain in S3.
259
369
 
@@ -269,9 +379,13 @@ After UI changes: rebuild → re-run Steps 1–3 (same `CFM_INSTANCE_ID` if same
269
379
  | 403 on `index.html.txt?_rsc` | Missing RSC payload file | Add `cp out/index.txt out/index.html.txt` to build script |
270
380
  | 403 on `*_p.png` / `*_p.jpg` images | Preview URL used | Use `option.imageUrl \|\| option.previewImageUrl` in RankOrderScale |
271
381
  | 401 `TOKEN_EXPIRED` | JWT expired | Ask client for new instanceId |
272
- | 503 `DEPLOY_UNAVAILABLE` | EC2 CloudFront/S3 env wrong | Report to operator |
382
+ | 503 `STORAGE_UNAVAILABLE` on `/deploy/complete` | EC2 `.env` has placeholder `CLOUDFRONT_*` values | Fix `.env` with real distribution ID + domain; `pm2 restart` |
383
+ | 503 `DEPLOY_UNAVAILABLE` | EC2 hosting env misconfigured — report to operator |
273
384
  | Some PUTs 403 | Presigned URLs expired (default 5 min) | Restart from Step 1; complete within window |
274
385
  | Blank page, HTML 200 | JS/CSS not uploaded or wrong paths | Re-check Channel 2 logs |
386
+ | Broken logo on live site, HTML 200 | Logo only in wizard preview memory or wrong `src` | Apply `global.logo.fileName` to `data-cfm-logo` as `./fileName`; verify `out/{fileName}` before deploy |
387
+ | Logo 404 under deploy URL | File not in `out/` or absolute `/logo.png` path | Rebuild after copying to `public/`; use `./fileName` not `/fileName` |
388
+ | Used `global.logo.url` as img src | Often `data:` base64 or `/assets/` path outside deploy | Use local `./fileName` only — see § Logos & local assets |
275
389
  | Survey API errors in browser | Deploy JWT ≠ SurveyPage JWT | Align `SURVEY_INSTANCE_ID` |
276
390
 
277
391
  ---
@@ -0,0 +1,94 @@
1
+ # Canonical Survey Fixtures
2
+
3
+ > Ground-truth UI expectations for integration-test surveys. When your fetched survey matches these patterns, **copy portable templates** from [`templates/`](../templates/) — do not rebuild matrix/slider UI from prose.
4
+
5
+ Related: [`component-checklist.md`](component-checklist.md) · [`question-type-sdk-matrix.md`](question-type-sdk-matrix.md)
6
+
7
+ ---
8
+
9
+ ## Template copy order (all matrix types)
10
+
11
+ 0. `customFieldValues.ts` → `src/lib/` when survey logic references `_c_…` ids
12
+ 1. `surveyUiScaleUtils.ts` + `surveyUiIcons.tsx`
13
+ 2. `CustomSliderTrack.tsx` + `MatrixDropdown.tsx`
14
+ 3. Scale component for each type in survey
15
+ 4. `Question.tsx` from template (includes `questionNumber`, matrix visibility helpers, `customFieldValues` prop)
16
+
17
+ ---
18
+
19
+ ## Page-by-page fixture map
20
+
21
+ | Page | Type | Key config | Expected UI | Copy |
22
+ |------|------|------------|-------------|------|
23
+ | 1 | `NPS_SCALE` | 11 options 0–10, `minLabel`/`maxLabel` | Traffic-light badges + anchor labels | `RatingScale.tsx` |
24
+ | 2 | `SLIDER_MATRIX` | 1 row, `ticks.count: 11`; skip uses `_c_67199096d294be011a2b4773` | Colored 0–10 header + gray row + track; CRM field in `customFieldValues.ts` | `SliderMatrixScale.tsx` + `customFieldValues.ts` |
25
+ | 3 | `FILE_UPLOAD` | `maxFileCount: 1` | Dropzone → presigned upload → `UploadedFileAnswer[]` | `FileUploadScale.tsx` |
26
+ | 4 | `MCQ` | `selectionMode: single`, `defaultOptionIds` | Radio; pre-select default | `Question.tsx` inline |
27
+ | 6 | `MCQ` | `minSelections`/`maxSelections` | Checkbox + validation message | `Question.tsx` inline |
28
+ | 7 | `CFM_MATRIX` | `statementLayout: bipolar`, `showColumnHeaders: false`, 3 anchor labels, 10 cols | 25% left stmt \| radios \| 25% right stmt | `LikertMatrixScale.tsx` |
29
+ | 9 | `CSAT_MATRIX` | `displayStyle: emoji`, 5 anchors, N/A | 180px stmt column + emoji row + anchors | `CsatMatrixScale.tsx` |
30
+ | 10 | `CFM_MATRIX` | `transposeTable: true`, `selectionMode: multiple` | Rows=Scale Items, cols=Statement Items, **checkboxes** | `LikertMatrixScale.tsx` |
31
+ | 11 | `RATING_MATRIX` | `displayStyle: graphics` | Per-row `CustomSliderTrack` (NOT SliderMatrixScale) | `CsatMatrixScale.tsx` |
32
+ | 12 | `SLIDER_MATRIX` | 2 rows + 2 anchor labels | Shared header + 2 row bands with visible statements | `SliderMatrixScale.tsx` |
33
+ | 14 | multi | star + slider + numbered rating | Three separate components; all labels visible | all three templates |
34
+
35
+ ---
36
+
37
+ ## Common agent failures
38
+
39
+ | Symptom | Cause | Fix |
40
+ |---------|-------|-----|
41
+ | Lone pink circle, no slider track | Built stub thumb instead of `SliderMatrixScale` | Copy template |
42
+ | Matrix with no row labels | Omitted 180px / 25% statement column | See ui-spec statement visibility |
43
+ | Matrix with no column labels | Ignored `scaleAnchorLabels` and `showColumnHeaders` | Render header row |
44
+ | CSAT shows radios instead of emojis | Skipped `displayStyle: emoji` branch | Copy `CsatMatrixScale` |
45
+ | RATING numbered shows native radios | Skipped `displayStyle: numbered` branch | 40×40 badges |
46
+ | Bipolar missing right text | Ignored `oppositeStatementText` | 25% right column |
47
+ | Transposed matrix wrong orientation | Ignored `transposeTable` | Swap gridRows/gridCols |
48
+ | Custom-field skip never fires | `customFieldValues` omitted from hook or wrong `_c_` id | Copy `customFieldValues.ts`; wire hook + Question |
49
+
50
+ ---
51
+
52
+ ## Custom field fixture (Page 2)
53
+
54
+ SLIDER_MATRIX skip when CRM field `_c_67199096d294be011a2b4773` `CASE_SENSITIVE_TEXT` equals `"abc"`:
55
+
56
+ ```typescript
57
+ // src/lib/customFieldValues.ts
58
+ export const CUSTOM_FIELD_VALUES = {
59
+ '_c_67199096d294be011a2b4773': 'abc',
60
+ };
61
+ ```
62
+
63
+ Import in `SurveyPage` and pass to `useSurveySDK` + `Question`. See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
64
+
65
+ ## Statement text rules (all matrix types)
66
+
67
+ ```tsx
68
+ // Always render — never hide statementRows[].statementText
69
+ <span dangerouslySetInnerHTML={{ __html: row.statementText }} />
70
+ ```
71
+
72
+ For CFM bipolar, also render `row.oppositeStatementText` on the right.
73
+
74
+ For CFM transposed, row labels come from `scaleColumns[].label`; column headers from `statementRows[].statementText`.
75
+
76
+ ---
77
+
78
+ ## Label header rules
79
+
80
+ At least one header row MUST be visible when configured:
81
+
82
+ - `scaleAnchorLabels.length > 0` → anchor row (positioning formulas in ui-specs)
83
+ - `showColumnHeaders === true` → column label row from `scaleColumns[].label` or transposed `statementText`
84
+ - CSAT without anchors → column header row from `scaleColumns[].label`
85
+
86
+ ---
87
+
88
+ ## Agent checklist
89
+
90
+ - [ ] Read this doc when survey contains matrix or slider types
91
+ - [ ] Copied all required templates before Phase 5 BUILD
92
+ - [ ] Verified row labels + column/anchor labels render on every matrix question
93
+ - [ ] Verified slider has header badges + track (not orphan circle)
94
+ - [ ] `customFieldValues.ts` populated and wired to hook + Question when survey uses CRM logic
@@ -1,6 +1,6 @@
1
1
  # Client Integration Guide
2
2
 
3
- > **Start here** after Phase 2 (instanceId). Single map of everything the client must wire to integrate all SDK features correctly.
3
+ > **Start here** after Phase 2 (instanceId). Full pipeline order: [`agent-execution-flow.md`](agent-execution-flow.md).
4
4
 
5
5
  Import only from the package root (`@explorer02/cfm-survey-sdk` or `@repo/sdk`). Never from `/src/` paths.
6
6
 
@@ -34,7 +34,7 @@ flowchart TB
34
34
  | Dispatcher | `Question.tsx` | Route on `question.type`; filter options/rows via visibility helpers; placeholder text |
35
35
  | Scales | `*Scale.tsx` | Render one question type; store answers via `onSelect` callback |
36
36
 
37
- Reference implementation: `apps/client/src/components/SurveyPage.tsx`.
37
+ Reference implementation: portable templates in `docs/templates/` — not `apps/client` (monorepo snapshot only).
38
38
 
39
39
  ---
40
40
 
@@ -51,7 +51,7 @@ Reference implementation: `apps/client/src/components/SurveyPage.tsx`.
51
51
  | Question display variants | Render `question.questionText` from `currentQuestions` (no client variant logic) | Conditional variant text via same logic engine |
52
52
  | Answer logic UI | `getVisible*ForAnswers` + pass `customFieldValues` | Condition evaluation engine |
53
53
  | Custom fields | Stable `customFieldValues` → hook **and** `Question` | Skip/display/validation/progress evaluation |
54
- | Progress | Bind `state.progressPercentage` | Active-path denominator + earned credits |
54
+ | Progress | `SurveyStickyChrome` with `state.progressPercentage` | Sticky header + bar; active-path denominator + earned credits |
55
55
  | Question numbering | Render `question.questionNumber` from `currentQuestions` | Active-path ordinals (same denominator as progress); see [`question-numbering.md`](question-numbering.md) |
56
56
  | Placeholders | Stable `placeholders` ref + `language` option | Token replacement at map time |
57
57
  | Mark as started | `onAction(SCROLL)` once on first scroll | One-time API call (also fires on CHANGE) |
@@ -161,10 +161,11 @@ function SurveyContent({
161
161
  }, [state.currentQuestions, onAction]);
162
162
 
163
163
  return (
164
- <main>
165
- <ProgressBar progressPercentage={state.progressPercentage} />
164
+ <>
165
+ <SurveyStickyChrome progressPercentage={state.progressPercentage} />
166
166
 
167
- {state.currentQuestions.map(question => (
167
+ <main className="mx-auto w-full max-w-4xl flex-1 px-4 py-10">
168
+ {state.currentQuestions.map(question => (
168
169
  <Question
169
170
  key={question.id}
170
171
  question={question}
@@ -189,7 +190,8 @@ function SurveyContent({
189
190
  >
190
191
  {state.primaryButtonType === 'submit' ? 'Submit' : 'Next'}
191
192
  </button>
192
- </main>
193
+ </main>
194
+ </>
193
195
  );
194
196
  }
195
197
 
@@ -0,0 +1,115 @@
1
+ # Client Lib Folder — Portable Templates
2
+
3
+ > Maps the reference monorepo [`apps/client/src/lib/`](../../../../apps/client/src/lib/) layout to portable templates agents copy into their project. **Do not import from `apps/client/` in npm installs.**
4
+
5
+ Related: [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) · [`component-checklist.md`](component-checklist.md) · [`constraints.md`](constraints.md)
6
+
7
+ ---
8
+
9
+ ## Reference → portable mapping
10
+
11
+ | Reference file (monorepo) | Portable template | Used by | Agent action |
12
+ |---------------------------|-------------------|---------|--------------|
13
+ | `lib/customFieldValues.ts` | [`templates/customFieldValues.ts`](../templates/customFieldValues.ts) | `SurveyPage` → `useSurveySDK` + `Question` | Copy to `src/lib/`; populate `_c_…` ids from fetched survey |
14
+ | `lib/surveyUi/getAccentColor.ts` + `scaleValues.ts` | [`templates/surveyUiScaleUtils.ts`](../templates/surveyUiScaleUtils.ts) | NPS colors, slider tick colors, matrix column helpers | Copy to `src/lib/` or co-locate with scale components |
15
+ | `lib/surveyUi/icons.tsx` | [`templates/surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx) | CSAT emoji/star (`CsatMatrixScale`) | Copy; add `react-icons` to `package.json` |
16
+ | `lib/surveyUi/heatmapCoords.ts` | [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts) | `HeatmapScale` | Copy to `src/lib/heatmapCoords.ts` |
17
+ | — (wizard styling) | [`templates/selectionStyles.ts`](../templates/selectionStyles.ts) | All scale radio/numbered selections | Copy to `src/lib/surveyUi/selectionStyles.ts` |
18
+ | — (wizard styling) | [`templates/labelStyles.ts`](../templates/labelStyles.ts) | Matrix/slider label items | Copy to `src/lib/surveyUi/labelStyles.ts` |
19
+
20
+ Scale **components** live in `src/components/` — see [`component-checklist.md`](component-checklist.md). Lib files are shared utilities and CRM data, not React components.
21
+
22
+ ---
23
+
24
+ ## Recommended project layout
25
+
26
+ ```
27
+ src/
28
+ ├── lib/
29
+ │ ├── customFieldValues.ts ← CRM map for useSurveySDK (copy template)
30
+ │ ├── surveyUiScaleUtils.ts ← NPS/tick/matrix helpers
31
+ │ ├── surveyUiIcons.tsx ← emoji/star icons (optional co-locate)
32
+ │ ├── surveyUi/selectionStyles.ts ← wizard: focus ring + cell selected (copy template)
33
+ │ ├── surveyUi/labelStyles.ts ← wizard: label item colors (copy template)
34
+ │ └── heatmapCoords.ts ← when survey has HEATMAP
35
+ └── components/
36
+ ├── SurveyPage.tsx
37
+ ├── Question.tsx
38
+ ├── RatingScale.tsx
39
+ └── ...scale components from templates/
40
+ ```
41
+
42
+ Placeholders follow the same stable-ref rule — define **`SURVEY_PLACEHOLDERS`** outside the component (often in `SurveyPage.tsx` module scope or a separate `lib/placeholders.ts`).
43
+
44
+ ---
45
+
46
+ ## `customFieldValues.ts` — CRM input for SDK logic
47
+
48
+ Custom field values are **not** survey UI inputs. They come from CRM/transaction context (API payload, JWT claims, host app state) and are passed to the SDK so skip, display, progress, end-page, and answer logic can evaluate rules referencing `entityType: 'CUSTOM_FIELD'`.
49
+
50
+ ```typescript
51
+ import type { CustomFieldValueMap } from '@explorer02/cfm-survey-sdk';
52
+ import { CUSTOM_FIELD_VALUES } from '@/lib/customFieldValues';
53
+
54
+ const surveyOptions = useMemo(
55
+ () => ({
56
+ instanceId,
57
+ placeholders: SURVEY_PLACEHOLDERS,
58
+ customFieldValues: CUSTOM_FIELD_VALUES,
59
+ }),
60
+ [instanceId, selectedLanguage]
61
+ );
62
+ ```
63
+
64
+ Pass the **same map** to `Question`:
65
+
66
+ ```tsx
67
+ <Question customFieldValues={CUSTOM_FIELD_VALUES} ... />
68
+ ```
69
+
70
+ See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) for discovery, value shapes, and the two wiring points.
71
+
72
+ ---
73
+
74
+ ## `surveyUiScaleUtils.ts` — scale UI helpers
75
+
76
+ Exports used by portable scale templates:
77
+
78
+ | Export | Purpose |
79
+ |--------|---------|
80
+ | `getAccentColor(value)` | NPS 0–10 traffic-light badge colors |
81
+ | `tickColorFromIndex(index, total)` | SLIDER_MATRIX header tick badge colors |
82
+ | `matrixColumnStoredValue(col)` | Matrix answer storage — column `id` or `null` for N/A |
83
+ | `npsValueFromOption` / `starValueFromOption` | NPS badge numeric values |
84
+ | `nonNaScaleColumns` | Filter N/A column from scale columns |
85
+
86
+ Prefer SDK `matrixColumnStoredValue` when importing from package; template re-exports the same contract for co-located components.
87
+
88
+ ---
89
+
90
+ ## `surveyUiIcons.tsx` — CSAT emoji and star icons
91
+
92
+ - `getEmojiForIndex(index, totalColumns)` — CSAT emoji matrix cells
93
+ - `CsatStarIcons` — filled/empty star nodes for `displayStyle: 'star'`
94
+
95
+ Requires `react-icons` (`fa6`, `fa`).
96
+
97
+ ---
98
+
99
+ ## `heatmapCoords.ts` — normalized click coordinates
100
+
101
+ Used by `HeatmapScale`:
102
+
103
+ - `getNormalizedClickFromImage(event, imageElement)`
104
+ - `findSpotNearClick(clickX, clickY, spots, imageRect)`
105
+ - `createNewSpot(x, y)`
106
+
107
+ ---
108
+
109
+ ## Agent checklist
110
+
111
+ - [ ] Copied lib templates before Phase 5 BUILD — do not read `apps/client/src/lib` at runtime
112
+ - [ ] `customFieldValues.ts` created when survey logic references any `_c_…` custom field id
113
+ - [ ] `CUSTOM_FIELD_VALUES` defined **outside** React components (stable ref)
114
+ - [ ] Same map passed to `useSurveySDK` options **and** `Question` prop
115
+ - [ ] Scale utils/icons copied when survey contains NPS, matrix emoji/star, SLIDER_MATRIX, or HEATMAP
@@ -1,94 +1,122 @@
1
1
  # Required Component Files Checklist
2
2
 
3
- > **Blocking gate before Phase 5 (BUILD).** After fetching the survey with `instanceId`, list every `question.type` and ensure all required files exist.
3
+ > **Blocking gate before Phase 5 (BUILD).** Read [`agent-operating-contract.md`](agent-operating-contract.md) §2–§3 first do not invent files or paths from memory.
4
+ >
5
+ > **Wizard-ready build (Phase 5 — mandatory):** Every agent build wires live-preview contract per [`agent-execution-flow.md`](agent-execution-flow.md), [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md), and [`wizard-question-type-styling.md`](wizard-question-type-styling.md) — `var(--cfm-*)`, `data-cfm-*` hooks, `selectionStyles.ts`, `labelStyles.ts`, draft `survey-ui-config.json`. Running the wizard (Phase 6b) is optional; wizard-ready artifacts are not.
6
+ >
7
+ > Read [`canonical-survey-fixtures.md`](canonical-survey-fixtures.md) when matrix/slider labels are missing.
4
8
 
5
9
  ## Step 1: Inventory Question Types
6
10
 
7
11
  From `survey.pages`, collect unique `question.type` values — see `MANIFEST.json` `typeLiterals`:
8
12
 
9
- | questionId | type |
10
- |------------|------|
11
- | | |
13
+ | questionId | type | displayStyle / statementLayout / transposeTable |
14
+ |------------|------|--------------------------------------------------|
15
+ | | | |
12
16
 
13
- ## Step 2: Create Required Files
17
+ Note matrix variants: `displayStyle`, `statementLayout`, `transposeTable`, `selectionMode`.
14
18
 
15
- **Copy portable templates from [`templates/`](../templates/) when available** — then customize branding. Otherwise implement from ui-spec Implementation Recipe.
19
+ ## Step 2: Copy Portable Templates (CRITICAL)
16
20
 
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` |
21
+ **Copy from [`templates/`](../templates/) before customizing branding.** Do not implement matrix/slider UI from scratch.
24
22
 
25
- | If survey contains | Agent MUST create before `npm run build` | UI spec |
26
- |--------------------|------------------------------------------|---------|
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` |
23
+ ### Copy order
36
24
 
37
- **Survey chrome (when applicable):**
25
+ 0. **`customFieldValues.ts`** → `src/lib/` when any `_c_…` id in survey logic (see [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md))
26
+ 0b. **`selectionStyles.ts`**, **`labelStyles.ts`** → `src/lib/surveyUi/` (**required** — see [`wizard-question-type-styling.md`](wizard-question-type-styling.md))
27
+ 1. `surveyUiScaleUtils.ts` → `src/lib/` (or co-locate with components)
28
+ 2. `surveyUiIcons.tsx` → same folder as scale components
29
+ 3. `CustomSliderTrack.tsx`, `MatrixDropdown.tsx`, `heatmapCoords.ts`
30
+ 4. Scale components per survey types
31
+ 5. `Question.tsx` from template
38
32
 
39
- | Component | When |
40
- |-----------|------|
41
- | `IntroPage.tsx` | `survey.introPage` |
42
- | `EndPage.tsx` | `survey.endPages` |
43
- | `PausedPage.tsx` | `survey.phaseFlags.paused` |
33
+ | `templates/customFieldValues.ts` | `src/lib/customFieldValues.ts` | Any `CUSTOM_FIELD` in skip/display/answer/end logic |
34
+ | `templates/selectionStyles.ts` | `src/lib/surveyUi/selectionStyles.ts` | **Required** — radio/scale selection colors |
35
+ | `templates/labelStyles.ts` | `src/lib/surveyUi/labelStyles.ts` | **Required** — matrix/slider label items |
36
+ | `templates/survey-theme.css` | `src/styles/survey-theme.css` | **Required** — full `--cfm-*` defaults |
37
+ | `templates/previewBridge.ts` | `src/lib/previewBridge.ts` | **Required** — local `customize` live preview |
44
38
 
45
- Always create **`Question.tsx`** from [`templates/Question.tsx`](../templates/Question.tsx).
39
+ See [`client-lib-folder.md`](client-lib-folder.md) for full lib layout.
46
40
 
47
- ## Flat Type Routing (CRITICAL)
41
+ | Portable template | Copy to | When survey contains |
42
+ |-------------------|---------|----------------------|
43
+ | `templates/Question.tsx` | `src/components/Question.tsx` | Always |
44
+ | `templates/RatingScale.tsx` | `src/components/RatingScale.tsx` | `NPS_SCALE` |
45
+ | `templates/LikertMatrixScale.tsx` | `src/components/LikertMatrixScale.tsx` | `CFM_MATRIX` |
46
+ | `templates/CsatMatrixScale.tsx` | `src/components/CsatMatrixScale.tsx` | `CSAT_MATRIX`, `RATING_MATRIX` |
47
+ | `templates/SliderMatrixScale.tsx` | `src/components/SliderMatrixScale.tsx` | `SLIDER_MATRIX` |
48
+ | `templates/FileUploadScale.tsx` | `src/components/FileUploadScale.tsx` | `FILE_UPLOAD` |
49
+ | `templates/HeatmapScale.tsx` | `src/components/HeatmapScale.tsx` | `HEATMAP` |
50
+ | `templates/RankOrderScale.tsx` | `src/components/RankOrderScale.tsx` | `RANK_ORDER` |
48
51
 
49
- Use `QUESTION_TYPE` **no `subType`**:
52
+ ### Matrix label visibility gate
50
53
 
51
- ```tsx
52
- import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
54
+ Before `npm run build`, confirm every matrix question in your inventory will render:
53
55
 
54
- {question.type === QUESTION_TYPE.CFM_MATRIX && (
55
- <LikertMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
56
- )}
56
+ - [ ] **Row labels**: `statementRows[].statementText` in 180px (or 25% bipolar) column
57
+ - [ ] **Column labels**: `scaleColumns[].label` header row OR `scaleAnchorLabels` anchor row
58
+ - [ ] **Bipolar**: `oppositeStatementText` in 25% right column
59
+ - [ ] **Transposed**: row labels from `scaleColumns[].label`; column headers from `statementRows[].statementText`
57
60
 
58
- {(question.type === QUESTION_TYPE.CSAT_MATRIX ||
59
- question.type === QUESTION_TYPE.RATING_MATRIX) && (
60
- <CsatMatrixScale question={question} selectedValue={...} onSelect={onSelect} />
61
- )}
62
- ```
61
+ ### Slider visibility gate
62
+
63
+ - [ ] Colored tick header row (0–10 badges from `ticks.marks`)
64
+ - [ ] Gray row band with statement text + `CustomSliderTrack` (not orphan circle)
65
+
66
+ | If survey contains | Also copy | UI spec |
67
+ |--------------------|-----------|---------|
68
+ | `SLIDER_MATRIX` | `CustomSliderTrack.tsx`, `surveyUiScaleUtils.ts` | `03-ui-specs/09-slider-matrix.md` |
69
+ | `CFM_MATRIX` | `MatrixDropdown.tsx` (if dropdown layout) | `03-ui-specs/07-matrix-cfm.md` |
70
+ | `CSAT_MATRIX` / `RATING_MATRIX` | `surveyUiIcons.tsx`, `CustomSliderTrack.tsx` (graphics) | `03-ui-specs/04-csat.md`, `08-matrix-csat-rating.md` |
71
+ | `RANK_ORDER` | `@dnd-kit/*` in package.json | `03-ui-specs/14-rank-order.md` |
72
+ | `HEATMAP` | `heatmapCoords.ts` | `03-ui-specs/13-heatmap.md` |
73
+ | `MCQ`, `TEXTFIELD`, `TEXT_AND_MEDIA` | inline in `Question.tsx` | `03-ui-specs/02-radio.md`, etc. |
74
+
75
+ ## Flat Type Routing (CRITICAL)
76
+
77
+ Use `QUESTION_TYPE` — **no `subType`**. Pass filtered matrix questions from `getVisibleMatrixItemsForAnswers`.
63
78
 
64
79
  ## Shared Primitives
65
80
 
66
81
  | File | Used by |
67
82
  |------|---------|
68
- | `CustomSliderTrack.tsx` | `SLIDER_MATRIX`, matrix `displayStyle: graphics` |
69
- | `MatrixDropdown.tsx` | `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX` dropdown/selectbox modes |
83
+ | `CustomSliderTrack.tsx` | `SLIDER_MATRIX`, RATING/CSAT `displayStyle: graphics` |
84
+ | `MatrixDropdown.tsx` | Matrix dropdown/selectbox modes |
85
+ | `surveyUiScaleUtils.ts` | NPS colors, tick colors, matrix column values |
86
+ | `surveyUiIcons.tsx` | CSAT emoji/star icons |
87
+ | `selectionStyles.ts` | Focus ring + cell selected pattern (all scale radios) |
88
+ | `labelStyles.ts` | Hint / column / tick label item colors |
70
89
 
71
90
  Store matrix values as column **`id`** — `matrixColumnStoredValue(col)` from SDK.
72
91
 
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
79
-
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).
81
-
82
92
  ## Step 3: Verify Before Build
83
93
 
84
- - [ ] Every type in the survey inventory has a matching component file
85
- - [ ] `Question.tsx` copied from `docs/templates/Question.tsx` with all **11** type branches wired
86
- - [ ] `SurveyPage` handles all `state.phase` values
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`
90
- - [ ] Grep project: `not yet implemented` **zero matches**
94
+ - [ ] Every type in survey inventory has matching component file(s)
95
+ - [ ] **`src/lib/customFieldValues.ts`** when survey logic references `_c_…` ids keys from **fetch**, wire hook + Question ([`placeholders-and-custom-fields-wiring.md`](00-integration/placeholders-and-custom-fields-wiring.md))
96
+ - [ ] **`SURVEY_PLACEHOLDERS`** keys discovered from fetched `{{TOKEN}}` text — not mockup
97
+ - [ ] `CUSTOM_FIELD_VALUES` wired in `SurveyPage` hook options **and** `Question` prop
98
+ - [ ] Matrix row + column/anchor labels visible (see canonical fixtures)
99
+ - [ ] Slider has header + track (see canonical fixtures)
100
+ - [ ] `Question.tsx` has `questionNumber` prefix and all 11 type branches
101
+ - [ ] `react-icons` in package.json when CSAT/RATING/NPS emoji modes present
102
+ - [ ] Grep: `not yet implemented` → zero matches
91
103
 
92
104
  ```bash
93
- grep -r "not yet implemented" src/ components/ app/ 2>/dev/null || true
105
+ bash templates/verify-agent-build.sh
106
+ # or from SDK package root: node_modules/@explorer02/cfm-survey-sdk/templates/docs/templates/verify-agent-build.sh
94
107
  ```
108
+
109
+ ## Step 4: Wizard-ready build (Phase 5 — mandatory)
110
+
111
+ Read: [`agent-execution-flow.md`](agent-execution-flow.md), [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md), [`wizard-question-type-styling.md`](wizard-question-type-styling.md)
112
+
113
+ - [ ] `src/styles/survey-theme.css` with full `--cfm-*` set (copy from `templates/survey-theme.css`)
114
+ - [ ] `selectionStyles.ts` + `labelStyles.ts` in `src/lib/surveyUi/`
115
+ - [ ] Draft `./survey-ui-config.json` synced with mockup branding
116
+ - [ ] `previewBridge.ts` + `PreviewBridgeInit` in root layout
117
+ - [ ] Header/Footer/ProgressBar/SurveyPage use `var(--cfm-*)` — no hardcoded brand hex for wizard-controlled tokens
118
+ - [ ] `data-cfm-logo` / `data-cfm-logo-text`, `data-cfm-company`, `data-cfm-survey-title`, `data-cfm-btn-next`, `data-cfm-btn-back`, `data-cfm-thank-you` on chrome
119
+ - [ ] **Layout toggles:** `data-cfm-progress`, `[data-cfm-question-number]`, `[data-cfm-required]`, `.cfm-footer` — see `wizard-troubleshooting.md` §3
120
+ - [ ] Per-type scale vars per `wizard-question-type-styling.md` for **all 11 types** (wizard exports all; survey inventory may be smaller)
121
+ - [ ] Selection pattern: focus ring = outer border + inner dot; cell selected = annulus fill
122
+ - [ ] `npx cfm-sdk export-previews` succeeds from project root (when previews needed for AWS wizard)
@@ -1,6 +1,7 @@
1
1
  # Critical Constraints
2
2
 
3
- > Non-negotiable rules. Violating any of these breaks the build or survey UX.
3
+ > Non-negotiable rules. Violating any of these breaks the build or survey UX.
4
+ > Agent enforcement: [`agent-operating-contract.md`](agent-operating-contract.md) §4 hallucination blocklist.
4
5
 
5
6
  ## 1. Top-Level Imports Only
6
7
 
@@ -26,11 +27,19 @@ Every question card **must** have `id={question.id}` on its outermost element:
26
27
 
27
28
  Required for scroll-to-error when validation fails.
28
29
 
29
- ## 4. CSS-Only Logo
30
+ ## 4. Logo (wizard-compatible)
30
31
 
31
- - **Never** use `<img>` with external URLs for the brand logo
32
- - **Never** save logo files to `public/`
33
- - Build logos with Tailwind CSS divs — derive design from **client prompt** (INPUT 1)
32
+ Choose **one** pattern in `Header.tsx` see [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) and [`10-header-footer.md`](../01-components/10-header-footer.md):
33
+
34
+ | Mode | Rules |
35
+ |------|--------|
36
+ | **Text / CSS-only** | Build logo with HTML + CSS; use `<span data-cfm-logo-text>`. No `public/` logo file required. |
37
+ | **Local image** | Put asset in `public/`; reference with `<img data-cfm-logo src="/your-logo.svg" />` for local dev. For **AWS static export**, use `src="./your-logo.svg"` (relative — required under `/sites/{surveyId}/{deployId}/`). **Never** use external URLs (`https://…`) or wizard `data:` URLs in the built survey. |
38
+
39
+ - Do **not** drop unrelated `public/logo_*` files during agent build — CLI seeds from `Header.tsx`, not newest `logo_*`.
40
+ - After wizard upload, `survey-ui-config.json` `global.logo.fileName` is authoritative on re-open.
41
+ - Wizard preview shows logo from **browser memory**; CloudFront serves only files in the **deploy bundle** (`out/` → S3 `sites/{surveyId}/{deployId}/`). See [`aws-deploy.md`](aws-deploy.md) § Logos & local assets.
42
+ - After EC2 wizard complete, poll/final JSON includes `global.logo.url` (CloudFront `/assets/` URL) — agent downloads to `public/` via `confirm-ui-config`; still use `./{fileName}` in `Header.tsx`, not the assets URL.
34
43
 
35
44
  ## 5. Client Components
36
45