@explorer02/cfm-survey-sdk 0.2.4 → 0.2.6

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 (43) hide show
  1. package/dist/cli/index.js +45 -44
  2. package/dist/cli/index.mjs +43 -42
  3. package/package.json +1 -6
  4. package/postinstall.js +2 -1
  5. package/templates/AGENT.md +3 -3
  6. package/templates/docs/00-integration/agent-execution-flow.md +46 -27
  7. package/templates/docs/00-integration/agent-operating-contract.md +13 -5
  8. package/templates/docs/00-integration/apply-ui-config.md +35 -10
  9. package/templates/docs/00-integration/aws-deploy.md +117 -3
  10. package/templates/docs/00-integration/component-checklist.md +8 -4
  11. package/templates/docs/00-integration/constraints.md +3 -2
  12. package/templates/docs/00-integration/custom-field-logic-and-navigation.md +1 -1
  13. package/templates/docs/00-integration/mockup-ui-extraction.md +113 -0
  14. package/templates/docs/00-integration/placeholders-and-custom-fields-wiring.md +192 -0
  15. package/templates/docs/00-integration/placeholders-and-tokens.md +2 -1
  16. package/templates/docs/00-integration/ui-customization-wizard.md +83 -104
  17. package/templates/docs/00-integration/wizard-api.md +74 -82
  18. package/templates/docs/00-integration/wizard-chrome-contract.md +210 -0
  19. package/templates/docs/00-integration/wizard-config-handoff.md +57 -9
  20. package/templates/docs/00-integration/wizard-preview-build-guide.md +30 -11
  21. package/templates/docs/00-integration/wizard-question-type-styling.md +4 -2
  22. package/templates/docs/00-integration/wizard-troubleshooting.md +80 -0
  23. package/templates/docs/01-components/10-header-footer.md +3 -3
  24. package/templates/docs/01-components/19-survey-sticky-chrome.md +16 -55
  25. package/templates/docs/MANIFEST.json +34 -13
  26. package/templates/docs/index.md +9 -11
  27. package/templates/docs/templates/Footer.tsx +72 -0
  28. package/templates/docs/templates/Header.tsx +84 -0
  29. package/templates/docs/templates/LanguageSelector.tsx +40 -0
  30. package/templates/docs/templates/PreviewBridgeInit.tsx +13 -0
  31. package/templates/docs/templates/ProgressBar.tsx +33 -0
  32. package/templates/docs/templates/Question.tsx +50 -7
  33. package/templates/docs/templates/SurveyStickyChrome.tsx +19 -5
  34. package/templates/docs/templates/deploy-to-aws.sh +74 -0
  35. package/templates/docs/templates/implementation_plan.md +19 -12
  36. package/templates/docs/templates/verify-agent-build.sh +25 -1
  37. package/templates/preview-harness/preview-bridge.inline.js +1 -1
  38. package/templates/previewBridge.ts +131 -38
  39. package/templates/wizard-dist/assets/{PreviewMock-DgHfrVeb.js → PreviewMock-7peNZK-z.js} +1 -1
  40. package/templates/wizard-dist/assets/{TypePanel-CFVC3Ptn.js → TypePanel-BxHnE3cm.js} +1 -1
  41. package/templates/wizard-dist/assets/index-DtZhNwPS.js +34 -0
  42. package/templates/wizard-dist/index.html +1 -1
  43. package/templates/wizard-dist/assets/index-DYK3X1e5.js +0 -34
@@ -19,10 +19,10 @@
19
19
  "surveyUiConfig": {
20
20
  "artifact": "survey-ui-config.json",
21
21
  "schema": "templates/survey-ui-config.schema.json",
22
- "cliCommand": "npx cfm-sdk customize-ui",
22
+ "cliCommand": "npx cfm-sdk customize",
23
23
  "cliCommands": {
24
- "aws": "npx cfm-sdk customize-ui",
25
- "local": "npx cfm-sdk customize",
24
+ "default": "npx cfm-sdk customize",
25
+ "remoteClient": "npx cfm-sdk customize-ui",
26
26
  "exportPreviews": "npx cfm-sdk export-previews"
27
27
  },
28
28
  "wizardApiDoc": "00-integration/wizard-api.md",
@@ -156,6 +156,10 @@
156
156
  "path": "00-integration/client-lib-folder.md",
157
157
  "purpose": "src/lib layout — customFieldValues + surveyUi portable templates"
158
158
  },
159
+ {
160
+ "path": "00-integration/wizard-chrome-contract.md",
161
+ "purpose": "Wizard DOM/CSS hook contract — copy-first Header/Footer/ProgressBar templates (Phase 5)"
162
+ },
159
163
  {
160
164
  "path": "00-integration/wizard-preview-build-guide.md",
161
165
  "purpose": "Build client components for wizard live preview — --cfm-* vars and data-cfm-* hooks (Phase 5)"
@@ -178,7 +182,19 @@
178
182
  },
179
183
  {
180
184
  "path": "00-integration/wizard-api.md",
181
- "purpose": "EC2 /wizard/* API for customize-ui and preview hosting"
185
+ "purpose": "EC2 /wizard/* API customize-ui remote-client fallback only"
186
+ },
187
+ {
188
+ "path": "00-integration/wizard-troubleshooting.md",
189
+ "purpose": "Fix mock preview, local vs EC2, layout toggles, all-11-types wizard export (Phase 6b)"
190
+ },
191
+ {
192
+ "path": "00-integration/mockup-ui-extraction.md",
193
+ "purpose": "UI-only extraction from client mockup — no logic/language/types from images (Phase 0–4)"
194
+ },
195
+ {
196
+ "path": "00-integration/placeholders-and-custom-fields-wiring.md",
197
+ "purpose": "Discover placeholder + _c_ keys from fetch; precise useSurveySDK wiring (Phase 4–5)"
182
198
  }
183
199
  ],
184
200
  "templateScaleFiles": [
@@ -203,7 +219,7 @@
203
219
  "apiBaseEnv": "CFM_DEPLOY_API_BASE",
204
220
  "apiBaseDefault": "http://43.204.26.213:3000",
205
221
  "wizardClientUrlPattern": "{CFM_DEPLOY_API_BASE}/wizard-app/index.html?session={sessionId}&secret={sessionSecret}",
206
- "wizardClientUrlNote": "Printed by customize-ui after component previews upload to S3agent copies link and sends to client",
222
+ "wizardClientUrlNote": "customize-ui only (remote client): EC2 link after preview upload default is npx cfm-sdk customize on localhost",
207
223
  "instanceIdEnv": "CFM_INSTANCE_ID",
208
224
  "outDirEnv": "OUT_DIR",
209
225
  "healthPath": "/health",
@@ -263,11 +279,12 @@
263
279
  "../AGENT.md",
264
280
  "00-integration/agent-operating-contract.md",
265
281
  "00-integration/agent-execution-flow.md",
282
+ "00-integration/mockup-ui-extraction.md",
266
283
  "MANIFEST.json"
267
284
  ],
268
285
  "actions": [
269
286
  "detectFramework",
270
- "extractBrandingFromClientPrompt"
287
+ "extractUiOnlyFromMockup"
271
288
  ]
272
289
  },
273
290
  {
@@ -320,6 +337,8 @@
320
337
  "readAlways": [
321
338
  "00-integration/agent-operating-contract.md",
322
339
  "00-integration/agent-execution-flow.md",
340
+ "00-integration/mockup-ui-extraction.md",
341
+ "00-integration/placeholders-and-custom-fields-wiring.md",
323
342
  "00-integration/component-checklist.md",
324
343
  "00-integration/canonical-survey-fixtures.md",
325
344
  "00-integration/client-lib-folder.md",
@@ -409,21 +428,23 @@
409
428
  "00-integration/ui-customization-wizard.md",
410
429
  "00-integration/wizard-preview-build-guide.md",
411
430
  "00-integration/wizard-question-type-styling.md",
412
- "00-integration/wizard-api.md"
431
+ "00-integration/wizard-api.md",
432
+ "00-integration/wizard-troubleshooting.md"
413
433
  ],
414
- "cliCommand": "npx cfm-sdk customize-ui",
415
- "cliCommandLocal": "npx cfm-sdk customize",
434
+ "cliCommand": "npx cfm-sdk customize",
435
+ "cliCommandRemote": "npx cfm-sdk customize-ui",
416
436
  "cliConfirmCommand": "npx cfm-sdk confirm-ui-config",
417
- "output": "survey-ui-config.final.json",
418
- "outputArtifacts": [
437
+ "output": "survey-ui-config.json",
438
+ "outputArtifactsRemote": [
419
439
  "survey-ui-config.seed.json",
420
440
  "survey-ui-config.final.json",
421
441
  "survey-ui-config.diff.json",
422
442
  "survey-ui-config.diff.md"
423
443
  ],
424
444
  "agentMustWait": true,
425
- "waitUntil": "cliExit0ConfigurationReady",
426
- "clientHandoff": "shareDeployedWizardUrlAfterS3Upload",
445
+ "waitUntil": "cliExit0UiConfigurationSaved",
446
+ "clientHandoff": "openLocalhostWizardSameMachine",
447
+ "clientHandoffRemote": "shareDeployedWizardUrlAfterPreviewUpload",
427
448
  "prerequisites": [
428
449
  "phase6VerifyPassed",
429
450
  "devServerRunning"
@@ -77,9 +77,9 @@ Full step-by-step: [`agent-execution-flow.md`](00-integration/agent-execution-fl
77
77
  | **2** Instance ID | Get JWT | `useSurveySDK.md` — **ask client** |
78
78
  | **3** SDK contract | Hook + logic | `client-integration-guide.md`, `question-type-sdk-matrix.md`, logic docs |
79
79
  | **4** Plan | Inventory + copy dispatcher | `component-checklist.md`, per-type MANIFEST docs, `implementation_plan.md` |
80
- | **5** Build | Wizard-ready UI | `constraints.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md`, `03-ui-specs/*` |
80
+ | **5** Build | Wizard-ready UI | `wizard-chrome-contract.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md`, `03-ui-specs/*` |
81
81
  | **6** Verify | Build + dev server | `verify-agent-build.sh` — **ask wizard** |
82
- | **6b** Wizard | Optional customize | `customize-ui` only if client said yes |
82
+ | **6b** Wizard | Optional customize | **`npx cfm-sdk customize`** (default) — EC2 `customize-ui` only if client on another device |
83
83
  | **6c** Apply | Sync final config | `apply-ui-config.md` |
84
84
  | **7** Deploy | Production | **ask deploy** — `aws-deploy.md` or Vercel |
85
85
 
@@ -101,7 +101,7 @@ Every agent build includes these even if the client later skips the wizard:
101
101
  | Scale components | `templates/*Scale*.tsx` etc. | `src/components/` per inventory |
102
102
  | Draft config | agent-written | `./survey-ui-config.json` |
103
103
 
104
- **Chrome:** `data-cfm-logo` / `data-cfm-logo-text`, `data-cfm-company`, `data-cfm-btn-next`, `data-cfm-btn-back`, `data-cfm-survey-title`, `data-cfm-thank-you` see `wizard-preview-build-guide.md`.
104
+ **Chrome:** copy `templates/docs/templates/Header.tsx` etc. see `wizard-chrome-contract.md` for full hook list.
105
105
 
106
106
  **Selection pattern:** focus ring = outer border + inner dot; cell selected = annulus fill — see `wizard-question-type-styling.md`.
107
107
 
@@ -130,12 +130,10 @@ Run: `bash templates/verify-agent-build.sh`
130
130
  Only after Phase 6 passes and client says **yes**:
131
131
 
132
132
  ```bash
133
- export CFM_INSTANCE_ID="<JWT>"
134
- export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
135
- npx cfm-sdk customize-ui # uploads previews → prints client link → WAITS until submit
133
+ npx cfm-sdk customize # DEFAULT — opens localhost:4200, WAITS until client saves
136
134
  ```
137
135
 
138
- **Phase 6b:** CLI uploads component previews to S3, prints deployed wizard link for the client, polls until `Configuration ready`. Read `survey-ui-config.diff.md`. Do **not** apply yet.
136
+ **Phase 6b:** Client on same machine `customize` writes `survey-ui-config.json` directly. Remote client `customize-ui` + EC2 link (see `wizard-troubleshooting.md`). Apply per `apply-ui-config.md`.
139
137
 
140
138
  **Phase 6c:** Apply from `survey-ui-config.final.json` per [`wizard-config-handoff.md`](00-integration/wizard-config-handoff.md) + [`apply-ui-config.md`](00-integration/apply-ui-config.md) — **every** diff path. Then:
141
139
 
@@ -151,13 +149,13 @@ Re-run verify script + build.
151
149
 
152
150
  | When | Read |
153
151
  |------|------|
154
- | **Always first** | `agent-operating-contract.md`, `agent-execution-flow.md`, `index.md`, `client-integration-guide.md`, `constraints.md` |
152
+ | **Always first** | `agent-operating-contract.md`, `agent-execution-flow.md`, `mockup-ui-extraction.md`, `index.md`, `client-integration-guide.md`, `constraints.md` |
155
153
  | Phase 3 contract | `question-type-sdk-matrix.md`, `useSurveySDK.md`, logic/navigation docs |
156
- | Before React code | `component-checklist.md`, `templates/Question.tsx`, per-type `03-ui-specs/*` |
154
+ | Phase 4 plan | `mockup-ui-extraction.md`, `placeholders-and-custom-fields-wiring.md`, `component-checklist.md`, per-type `03-ui-specs/*` |
157
155
  | Wizard build | `wizard-preview-build-guide.md`, `wizard-question-type-styling.md` |
158
- | Wizard run | `wizard-config-handoff.md`, `ui-customization-wizard.md`, `wizard-api.md` |
156
+ | Wizard run | `wizard-config-handoff.md`, `ui-customization-wizard.md`, `wizard-api.md`, `wizard-troubleshooting.md` |
159
157
  | After wizard (apply all paths) | `apply-ui-config.md` |
160
- | Deploy | `aws-deploy.md`, `setup.md#nextjs-aws-static-export` |
158
+ | Deploy | `aws-deploy.md` (§ Logos & local assets), `setup.md#nextjs-aws-static-export` |
161
159
  | Config audit | `02-reference/config-field-index.md` |
162
160
 
163
161
  ---
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Wizard-ready Footer — copy to src/components/Footer.tsx
3
+ * Required: footer.cfm-footer, data-cfm-copyright, data-cfm-footer-links, data-cfm-footer-logo
4
+ */
5
+ 'use client';
6
+
7
+ export default function Footer() {
8
+ return (
9
+ <footer
10
+ className="cfm-footer w-full"
11
+ style={{
12
+ background: 'var(--cfm-footer-bg, #262626)',
13
+ color: 'var(--cfm-footer-text, #9ca3af)',
14
+ padding: 'var(--cfm-footer-padding-y, 24px) var(--cfm-footer-padding-x, 40px)',
15
+ }}
16
+ >
17
+ <div
18
+ className="cfm-footer-inner mx-auto grid w-full max-w-4xl items-end gap-6"
19
+ style={{
20
+ display: 'var(--cfm-footer-inner-display, grid)',
21
+ flexDirection: 'var(--cfm-footer-inner-direction, row)' as React.CSSProperties['flexDirection'],
22
+ gridTemplateColumns: '1fr 1fr 1fr',
23
+ }}
24
+ >
25
+ <div
26
+ className="space-y-3"
27
+ style={{
28
+ gridColumn: 'var(--cfm-footer-logo-col, 1)',
29
+ justifySelf: 'var(--cfm-footer-logo-justify, start)',
30
+ }}
31
+ >
32
+ <img
33
+ data-cfm-footer-logo
34
+ data-cfm-logo
35
+ src="./logo.svg"
36
+ alt=""
37
+ style={{
38
+ display: 'none',
39
+ width: 'var(--cfm-footer-logo-width, 120px)',
40
+ height: 'var(--cfm-footer-logo-height, 32px)',
41
+ objectFit: 'contain',
42
+ }}
43
+ />
44
+ <p
45
+ data-cfm-copyright
46
+ className="text-xs"
47
+ style={{ color: 'var(--cfm-footer-text, #9ca3af)' }}
48
+ >
49
+ © Company
50
+ </p>
51
+ </div>
52
+
53
+ <nav
54
+ data-cfm-footer-links
55
+ className="flex flex-wrap gap-x-6 gap-y-2 text-xs"
56
+ style={{
57
+ gridColumn: 'var(--cfm-footer-links-col, 3)',
58
+ justifySelf: 'var(--cfm-footer-links-justify, end)',
59
+ }}
60
+ >
61
+ <a
62
+ href="#"
63
+ className="transition-colors hover:opacity-80"
64
+ style={{ color: 'var(--cfm-footer-link, #9ca3af)' }}
65
+ >
66
+ Privacy
67
+ </a>
68
+ </nav>
69
+ </div>
70
+ </footer>
71
+ );
72
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Wizard-ready Header — copy to src/components/Header.tsx
3
+ * Required hooks: data-cfm-logo, data-cfm-logo-text, data-cfm-company
4
+ * Required class: cfm-header (layout toggles target header only — not progress bar)
5
+ */
6
+ 'use client';
7
+
8
+ type HeaderProps = {
9
+ /** When true, omits outer shadow — use inside SurveyStickyChrome */
10
+ embedded?: boolean;
11
+ };
12
+
13
+ export default function Header({ embedded = false }: HeaderProps) {
14
+ return (
15
+ <header
16
+ className={`cfm-header relative w-full ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}
17
+ style={{
18
+ height: 'var(--cfm-header-height, 120px)',
19
+ background: 'var(--cfm-header-bg, #fff)',
20
+ borderBottom: '1px solid var(--cfm-header-border, #e5e7eb)',
21
+ boxShadow: embedded ? undefined : 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
22
+ }}
23
+ >
24
+ <div
25
+ className="cfm-header-inner grid h-full w-full items-center"
26
+ style={{
27
+ gridTemplateColumns: '1fr 1fr 1fr',
28
+ padding: `0 var(--cfm-header-padding-x, 40px)`,
29
+ gap: 'var(--cfm-header-brand-gap, 16px)',
30
+ }}
31
+ >
32
+ <div
33
+ className="flex items-center"
34
+ style={{
35
+ gridColumn: 'var(--cfm-header-logo-col, 1)',
36
+ justifySelf: 'var(--cfm-header-logo-justify, start)',
37
+ }}
38
+ >
39
+ <img
40
+ data-cfm-logo
41
+ src="./logo.svg"
42
+ alt="Logo"
43
+ className="object-contain"
44
+ style={{
45
+ display: 'none',
46
+ width: 'var(--cfm-header-logo-width, 120px)',
47
+ height: 'var(--cfm-header-logo-height, 120px)',
48
+ padding: 'var(--cfm-header-logo-padding, 10px)',
49
+ background: 'var(--cfm-primary)',
50
+ }}
51
+ />
52
+ <span
53
+ data-cfm-logo-text
54
+ className="font-semibold"
55
+ style={{
56
+ color: 'var(--cfm-header-company-color, var(--cfm-text))',
57
+ fontSize: 'var(--cfm-header-company-size, 18px)',
58
+ fontWeight: 'var(--cfm-header-company-weight, 600)',
59
+ }}
60
+ >
61
+ Company
62
+ </span>
63
+ </div>
64
+
65
+ <span
66
+ data-cfm-company
67
+ className="font-semibold"
68
+ style={{
69
+ gridColumn: 'var(--cfm-header-company-col, 2)',
70
+ justifySelf: 'var(--cfm-header-company-justify, center)',
71
+ marginTop: 'var(--cfm-header-company-margin-top, 0)',
72
+ marginLeft: 'var(--cfm-header-company-margin-left, 0)',
73
+ marginRight: 'var(--cfm-header-company-margin-right, 0)',
74
+ color: 'var(--cfm-header-company-color, var(--cfm-text))',
75
+ fontSize: 'var(--cfm-header-company-size, 18px)',
76
+ fontWeight: 'var(--cfm-header-company-weight, 600)',
77
+ }}
78
+ >
79
+ Company Name
80
+ </span>
81
+ </div>
82
+ </header>
83
+ );
84
+ }
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import type { SurveyLanguage } from '@explorer02/cfm-survey-sdk';
4
+
5
+ type LanguageSelectorProps = {
6
+ languages: SurveyLanguage[];
7
+ selectedLanguage: string;
8
+ onChange: (language: string) => void;
9
+ };
10
+
11
+ /** Wizard toggle showLanguageSwitcher targets [data-cfm-language] */
12
+ export default function LanguageSelector({
13
+ languages,
14
+ selectedLanguage,
15
+ onChange,
16
+ }: LanguageSelectorProps) {
17
+ if (languages.length <= 1) return null;
18
+
19
+ return (
20
+ <div data-cfm-language className="mb-6 flex justify-end">
21
+ <select
22
+ value={selectedLanguage}
23
+ onChange={(e) => onChange(e.target.value)}
24
+ className="rounded border px-3 py-1.5 text-sm outline-none"
25
+ style={{
26
+ background: 'var(--cfm-language-bg, #fff)',
27
+ color: 'var(--cfm-language-text, #2563eb)',
28
+ borderColor: 'var(--cfm-language-border, #2563eb)',
29
+ borderRadius: 'var(--cfm-border-radius, 8px)',
30
+ }}
31
+ >
32
+ {languages.map((lang) => (
33
+ <option key={lang.code} value={lang.code}>
34
+ {lang.label}
35
+ </option>
36
+ ))}
37
+ </select>
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import { initPreviewBridge } from '@/lib/previewBridge';
5
+
6
+ /** Mount once in root layout — enables localhost dev preview with npx cfm-sdk customize */
7
+ export function PreviewBridgeInit() {
8
+ useEffect(() => {
9
+ const cleanup = initPreviewBridge();
10
+ return cleanup;
11
+ }, []);
12
+ return null;
13
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Wizard-ready ProgressBar — copy to src/components/ProgressBar.tsx
3
+ * Required: data-cfm-progress + class cfm-progress (separate from header toggle)
4
+ */
5
+ 'use client';
6
+
7
+ type ProgressBarProps = {
8
+ progressPercentage: number;
9
+ };
10
+
11
+ export default function ProgressBar({ progressPercentage }: ProgressBarProps) {
12
+ const width = Math.min(100, Math.max(0, progressPercentage));
13
+
14
+ return (
15
+ <div data-cfm-progress className="cfm-progress w-full">
16
+ <div
17
+ className="w-full overflow-hidden rounded-full"
18
+ style={{
19
+ height: 'var(--cfm-progress-height, 12px)',
20
+ background: 'var(--cfm-progress-track, #e5e7eb)',
21
+ }}
22
+ >
23
+ <div
24
+ className="h-full rounded-full transition-all duration-300 ease-out"
25
+ style={{
26
+ width: `${width}%`,
27
+ background: 'var(--cfm-progress-fill, var(--cfm-primary))',
28
+ }}
29
+ />
30
+ </div>
31
+ </div>
32
+ );
33
+ }
@@ -101,13 +101,35 @@ export default function Question({
101
101
  }, [question, allAnswers, allQuestions, customFieldValues]);
102
102
 
103
103
  return (
104
- <section id={question.id} className="space-y-4 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
105
- <h2 className="text-lg font-medium leading-relaxed text-gray-900">
104
+ <section
105
+ id={question.id}
106
+ className="cfm-question-card space-y-4"
107
+ style={{
108
+ background: 'var(--cfm-card-bg, #fff)',
109
+ borderColor: 'var(--cfm-card-border-color, #e5e7eb)',
110
+ borderWidth: 1,
111
+ borderStyle: 'solid',
112
+ padding: 'var(--cfm-card-padding, 24px)',
113
+ boxShadow: 'var(--cfm-card-shadow, 0 1px 3px rgba(0,0,0,0.1))',
114
+ borderRadius: 'var(--cfm-border-radius, 12px)',
115
+ fontFamily: 'var(--cfm-font-family)',
116
+ }}
117
+ >
118
+ <h2
119
+ className="text-lg font-medium leading-relaxed"
120
+ style={{ color: 'var(--cfm-text, #111827)' }}
121
+ >
106
122
  {question.questionNumber != null && (
107
- <span className="mr-1 font-semibold">{question.questionNumber}.</span>
123
+ <span data-cfm-question-number className="mr-1 font-semibold">
124
+ {question.questionNumber}.
125
+ </span>
108
126
  )}
109
127
  <span dangerouslySetInnerHTML={{ __html: displayQuestionText }} />
110
- {question.required && <span className="text-red-500 ml-1">*</span>}
128
+ {question.required && (
129
+ <span data-cfm-required className="ml-1 text-red-500">
130
+ *
131
+ </span>
132
+ )}
111
133
  </h2>
112
134
  {displayQuestionDescription && (
113
135
  <div
@@ -182,7 +204,14 @@ export default function Question({
182
204
  return (
183
205
  <label
184
206
  key={option.id}
185
- className={`flex items-center gap-4 rounded-lg border py-4 px-5 cursor-pointer transition-all ${isSelected ? 'border-[#e20074] bg-[#fdf2f8]' : 'border-[#e5e5e5] bg-white hover:bg-gray-50/50'}`}
207
+ className="flex cursor-pointer items-center gap-4 transition-all"
208
+ style={{
209
+ borderRadius: 'var(--cfm-mcq-border-radius, 8px)',
210
+ border: `2px solid ${isSelected ? 'var(--cfm-mcq-selected-accent, var(--cfm-primary))' : 'var(--cfm-card-border-color, #e5e7eb)'}`,
211
+ background: isSelected ? 'var(--cfm-mcq-selected-bg, var(--cfm-secondary))' : 'var(--cfm-card-bg, #fff)',
212
+ padding: 'var(--cfm-mcq-card-padding, 12px)',
213
+ marginBottom: 'var(--cfm-mcq-option-gap, 8px)',
214
+ }}
186
215
  >
187
216
  <input
188
217
  type="checkbox"
@@ -211,7 +240,14 @@ export default function Question({
211
240
  return (
212
241
  <label
213
242
  key={option.id}
214
- className={`flex items-center gap-4 rounded-lg border py-4 px-5 cursor-pointer transition-all ${isSelected ? 'border-[#e20074] bg-[#fdf2f8]' : 'border-[#e5e5e5] bg-white hover:bg-gray-50/50'}`}
243
+ className="flex cursor-pointer items-center gap-4 transition-all"
244
+ style={{
245
+ borderRadius: 'var(--cfm-mcq-border-radius, 8px)',
246
+ border: `2px solid ${isSelected ? 'var(--cfm-mcq-selected-accent, var(--cfm-primary))' : 'var(--cfm-card-border-color, #e5e7eb)'}`,
247
+ background: isSelected ? 'var(--cfm-mcq-selected-bg, var(--cfm-secondary))' : 'var(--cfm-card-bg, #fff)',
248
+ padding: 'var(--cfm-mcq-card-padding, 12px)',
249
+ marginBottom: 'var(--cfm-mcq-option-gap, 8px)',
250
+ }}
215
251
  >
216
252
  <input
217
253
  type="radio"
@@ -245,11 +281,18 @@ export default function Question({
245
281
 
246
282
  {question.type === QUESTION_TYPE.TEXTFIELD && (
247
283
  <textarea
284
+ data-cfm-textfield
248
285
  rows={question.inputVariant === 'long' ? 4 : 1}
249
286
  value={(selectedValue as string) ?? ''}
250
287
  onChange={e => onSelect(e.target.value)}
251
288
  placeholder={question.placeholder || 'Type your response here...'}
252
- className="w-full rounded border border-gray-300 p-4 text-sm outline-none focus:border-[#e20074]"
289
+ className="w-full outline-none"
290
+ style={{
291
+ height: question.inputVariant === 'long' ? undefined : 'var(--cfm-input-height, 44px)',
292
+ borderRadius: 'var(--cfm-input-radius, 8px)',
293
+ border: '1px solid var(--cfm-input-border, #d1d5db)',
294
+ padding: 'var(--cfm-input-padding, 12px)',
295
+ }}
253
296
  />
254
297
  )}
255
298
 
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import Header from '@/components/Header';
2
4
  import ProgressBar from '@/components/ProgressBar';
3
5
 
@@ -6,17 +8,29 @@ type SurveyStickyChromeProps = {
6
8
  };
7
9
 
8
10
  /**
9
- * Sticky top chrome: brand header + progress bar.
10
- * Keeps progress visible while scrolling long pages (e.g. last question below the fold).
11
+ * Sticky chrome header and progress are SEPARATE targets for wizard layout toggles.
12
+ * - showHeader .cfm-header only
13
+ * - showProgressBar → [data-cfm-progress] / .cfm-progress only
14
+ * Do NOT put cfm-header on this wrapper.
11
15
  */
12
16
  export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyChromeProps) {
13
17
  return (
14
18
  <div
15
- className="sticky top-0 z-30 shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
16
- style={{ background: 'var(--cfm-header-bg, #fff)' }}
19
+ className="cfm-sticky-chrome sticky top-0 z-30"
20
+ style={{
21
+ background: 'var(--cfm-header-bg, #fff)',
22
+ boxShadow: 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
23
+ }}
17
24
  >
18
25
  <Header embedded />
19
- <div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
26
+ <div
27
+ className="mx-auto w-full px-4 sm:px-6 lg:px-8"
28
+ style={{
29
+ maxWidth: 'var(--cfm-max-width, 48rem)',
30
+ paddingTop: 'var(--cfm-sticky-pt, 16px)',
31
+ paddingBottom: 'var(--cfm-sticky-pb, 20px)',
32
+ }}
33
+ >
20
34
  <ProgressBar progressPercentage={progressPercentage} />
21
35
  </div>
22
36
  </div>
@@ -24,6 +24,55 @@ echo "API: $CFM_DEPLOY_API_BASE"
24
24
  echo "OUT: $OUT_DIR ($FILE_COUNT files)"
25
25
  echo
26
26
 
27
+ # Verify wizard logos and other public assets made it into the static build (not just browser memory).
28
+ python3 <<'PY' || exit 1
29
+ import json, os, re, sys
30
+ from pathlib import Path
31
+
32
+ out = Path(os.environ["OUT_DIR"])
33
+ root = Path(os.environ.get("PROJECT_ROOT", os.getcwd()))
34
+ config_path = root / "survey-ui-config.json"
35
+ missing = []
36
+
37
+ if config_path.is_file():
38
+ cfg = json.load(open(config_path))
39
+ logo = (cfg.get("global") or {}).get("logo") or {}
40
+ fn = logo.get("fileName")
41
+ url = logo.get("url") or ""
42
+ if fn:
43
+ if url.startswith("data:"):
44
+ print(f"FAIL: global.logo.url is a data: URL — apply Phase 6c: Header src=./{fn}, file in public/")
45
+ sys.exit(1)
46
+ for base in (root / "public", out):
47
+ if not (base / fn).is_file():
48
+ missing.append(str(base / fn))
49
+ if missing:
50
+ print("FAIL: logo file missing from build output:")
51
+ for p in missing:
52
+ print(f" - {p}")
53
+ print("Fix: run confirm-ui-config (EC2 wizard) or re-upload in customize; update Header data-cfm-logo src")
54
+ sys.exit(1)
55
+ print(f"Logo OK: {fn} in out/")
56
+ else:
57
+ print("No global.logo.fileName in survey-ui-config.json — skipping logo check")
58
+
59
+ for header in sorted((root / "src").rglob("Header.tsx")):
60
+ text = header.read_text()
61
+ for m in re.finditer(r'data-cfm-logo[^>]*\ssrc=["\']([^"\']+)["\']', text):
62
+ src = m.group(1)
63
+ if src.startswith(("http://", "https://", "data:")):
64
+ print(f"FAIL: {header} uses external/data logo src: {src[:60]}")
65
+ sys.exit(1)
66
+ rel = src.lstrip("./").lstrip("/")
67
+ if rel and not (out / rel).is_file():
68
+ missing.append(str(out / rel))
69
+ print(f"FAIL: Header references {src} but {out / rel} not in build")
70
+ sys.exit(1)
71
+
72
+ if not missing:
73
+ print("Static asset preflight OK")
74
+ PY
75
+
27
76
  HEALTH=$(curl -s "$CFM_DEPLOY_API_BASE/health")
28
77
  echo "Health: $HEALTH"
29
78
  echo
@@ -111,4 +160,29 @@ if [ "$HTTP" != "200" ]; then
111
160
  exit 1
112
161
  fi
113
162
 
163
+ # Optional: verify logo asset under same deploy prefix (broken img = missing from out/ or wrong Header src)
164
+ python3 <<'PY' || true
165
+ import json, os, re, subprocess
166
+ from pathlib import Path
167
+ from urllib.parse import urljoin
168
+
169
+ root = Path(os.environ.get("PROJECT_ROOT", os.getcwd()))
170
+ cfg_path = root / "survey-ui-config.json"
171
+ live = json.load(open("/tmp/deploy-complete.json"))["url"]
172
+ if not cfg_path.is_file():
173
+ raise SystemExit(0)
174
+ fn = ((json.load(open(cfg_path)).get("global") or {}).get("logo") or {}).get("fileName")
175
+ if not fn:
176
+ raise SystemExit(0)
177
+ base = live.rsplit("/", 1)[0] + "/"
178
+ logo_url = urljoin(base, fn)
179
+ code = subprocess.check_output(
180
+ ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", logo_url],
181
+ text=True,
182
+ ).strip()
183
+ print(f"Logo asset: {logo_url} → HTTP {code}")
184
+ if code != "200":
185
+ print("WARN: logo missing on CloudFront — rebuild after Phase 6c logo apply (see aws-deploy.md § Static assets)")
186
+ PY
187
+
114
188
  echo "=== Deploy complete ==="