@explorer02/cfm-survey-sdk 0.2.5 → 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 (27) hide show
  1. package/dist/cli/index.js +45 -45
  2. package/dist/cli/index.mjs +45 -45
  3. package/package.json +1 -1
  4. package/templates/docs/00-integration/agent-execution-flow.md +7 -0
  5. package/templates/docs/00-integration/component-checklist.md +5 -3
  6. package/templates/docs/00-integration/ui-customization-wizard.md +1 -1
  7. package/templates/docs/00-integration/wizard-chrome-contract.md +210 -0
  8. package/templates/docs/00-integration/wizard-config-handoff.md +2 -0
  9. package/templates/docs/00-integration/wizard-preview-build-guide.md +18 -4
  10. package/templates/docs/01-components/19-survey-sticky-chrome.md +16 -55
  11. package/templates/docs/MANIFEST.json +4 -0
  12. package/templates/docs/index.md +2 -2
  13. package/templates/docs/templates/Footer.tsx +72 -0
  14. package/templates/docs/templates/Header.tsx +84 -0
  15. package/templates/docs/templates/LanguageSelector.tsx +40 -0
  16. package/templates/docs/templates/PreviewBridgeInit.tsx +13 -0
  17. package/templates/docs/templates/ProgressBar.tsx +33 -0
  18. package/templates/docs/templates/Question.tsx +50 -7
  19. package/templates/docs/templates/SurveyStickyChrome.tsx +19 -5
  20. package/templates/docs/templates/verify-agent-build.sh +25 -1
  21. package/templates/preview-harness/preview-bridge.inline.js +1 -1
  22. package/templates/previewBridge.ts +131 -38
  23. package/templates/wizard-dist/assets/{PreviewMock-AoZdAEPZ.js → PreviewMock-7peNZK-z.js} +1 -1
  24. package/templates/wizard-dist/assets/{TypePanel-39FnbtvG.js → TypePanel-BxHnE3cm.js} +1 -1
  25. package/templates/wizard-dist/assets/index-DtZhNwPS.js +34 -0
  26. package/templates/wizard-dist/index.html +1 -1
  27. package/templates/wizard-dist/assets/index-CWKdsJOp.js +0 -34
@@ -0,0 +1,210 @@
1
+ # Wizard Chrome Contract — Required DOM + CSS Hooks
2
+
3
+ > **Mandatory Phase 5.** If any hook is missing, wizard live preview fails for that control — even though the survey may look fine in localhost dev without the wizard open.
4
+ > **Copy-first templates:** [`templates/Header.tsx`](../templates/Header.tsx), [`Footer.tsx`](../templates/Footer.tsx), [`ProgressBar.tsx`](../templates/ProgressBar.tsx), [`SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx), [`LanguageSelector.tsx`](../templates/LanguageSelector.tsx), [`PreviewBridgeInit.tsx`](../templates/PreviewBridgeInit.tsx), [`previewBridge.ts`](../../previewBridge.ts).
5
+
6
+ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`apply-ui-config.md`](apply-ui-config.md)
7
+
8
+ ---
9
+
10
+ ## Symptom → missing hook (quick fix)
11
+
12
+ | Wizard control / symptom | Required fix |
13
+ |--------------------------|--------------|
14
+ | Logo not updating in Branding / Header preview | `data-cfm-logo` img + `data-cfm-logo-text` fallback in `Header.tsx` |
15
+ | Company name not updating | `data-cfm-company` span in `Header.tsx` |
16
+ | Show header works but progress bar toggle does nothing | Progress bar needs `data-cfm-progress` + class `cfm-progress`; header needs class `cfm-header` only — **not** on sticky wrapper |
17
+ | Show footer toggle does nothing | `footer` + class `cfm-footer` |
18
+ | Question numbers / required asterisk toggles | `data-cfm-question-number` + `data-cfm-required` on every question title |
19
+ | Question card padding/shadow/colors not live-updating | Question wrapper uses `var(--cfm-card-*)` — not hardcoded `bg-white shadow-sm` |
20
+ | MCQ / NPS / dropzone style pickers | Root reads `html[data-cfm-mcq-style]`, `[data-cfm-nps-button-style]`, `[data-cfm-dropzone-style]` from bridge |
21
+ | Scale selection colors not updating | Import `selectionStyles.ts`; use `var(--cfm-matrix-selected)` / `var(--cfm-input-focus-ring)` |
22
+ | Nothing updates except static harness | Agent build missing token contract — re-copy templates above |
23
+
24
+ ---
25
+
26
+ ## Copy order (agents — do this in Phase 5)
27
+
28
+ ```bash
29
+ # From SDK templates folder (monorepo or node_modules)
30
+ cp templates/docs/templates/Header.tsx src/components/Header.tsx
31
+ cp templates/docs/templates/Footer.tsx src/components/Footer.tsx
32
+ cp templates/docs/templates/ProgressBar.tsx src/components/ProgressBar.tsx
33
+ cp templates/docs/templates/SurveyStickyChrome.tsx src/components/SurveyStickyChrome.tsx
34
+ cp templates/docs/templates/LanguageSelector.tsx src/components/LanguageSelector.tsx
35
+ cp templates/docs/templates/PreviewBridgeInit.tsx src/components/PreviewBridgeInit.tsx
36
+ cp templates/previewBridge.ts src/lib/previewBridge.ts
37
+ cp templates/survey-theme.css src/styles/survey-theme.css
38
+ cp templates/docs/templates/selectionStyles.ts src/lib/surveyUi/selectionStyles.ts
39
+ cp templates/docs/templates/labelStyles.ts src/lib/surveyUi/labelStyles.ts
40
+ ```
41
+
42
+ Wire `PreviewBridgeInit` in `layout.tsx` (local dev). Wizard iframes use injected `preview-bridge.inline.js` at export time.
43
+
44
+ Run verification:
45
+
46
+ ```bash
47
+ bash templates/verify-agent-build.sh
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Layer 1 — CSS variables (`--cfm-*`)
53
+
54
+ Wizard sends SNAPSHOT (full) + DELTA (partial) CSS vars to `:root`.
55
+
56
+ **Rule:** Every color, radius, spacing, or chrome dimension editable in the wizard must be consumed via `var(--cfm-…)` — not hardcoded `#e20074` / `bg-[#e20074]`.
57
+
58
+ Minimum files:
59
+
60
+ | File | Role |
61
+ |------|------|
62
+ | `src/styles/survey-theme.css` | All default `--cfm-*` vars |
63
+ | `globals.css` | `@import './styles/survey-theme.css'` |
64
+ | Scale components | `selectionStyles.ts` + `labelStyles.ts` |
65
+
66
+ ---
67
+
68
+ ## Layer 2 — Content patches (`data-cfm-*` + layout flags)
69
+
70
+ Wizard sends `contentPatch` on SNAPSHOT (not DELTA). Preview bridge applies patches to matching DOM nodes.
71
+
72
+ ### Branding & header
73
+
74
+ | Patch field | DOM target | Template location |
75
+ |-------------|------------|-------------------|
76
+ | `logoUrl` | `[data-cfm-logo]` → sets `src`, shows img; hides `[data-cfm-logo-text]` | `Header.tsx` |
77
+ | `companyName` | `[data-cfm-company]` text; fills logo-text when img hidden | `Header.tsx` |
78
+ | `layoutFlags.showCompanyName` | `[data-cfm-company]` display | bridge |
79
+
80
+ ### Layout toggles (independent — critical)
81
+
82
+ | `layoutFlags` key | Bridge selector | Component requirement |
83
+ |-------------------|-----------------|----------------------|
84
+ | `showHeader` | `.cfm-header`, `header.cfm-header` | Class on `<header>` only |
85
+ | `showProgressBar` | `.cfm-progress`, `[data-cfm-progress]` | **Separate** from header — `ProgressBar.tsx` |
86
+ | `showFooter` | `.cfm-footer`, `footer.cfm-footer` | Class on `<footer>` |
87
+ | `showLanguageSwitcher` | `[data-cfm-language]` | `LanguageSelector.tsx` |
88
+ | `showNextButton` | `[data-cfm-btn-next]` | `SurveyPage.tsx` nav |
89
+ | `showBackButton` | `[data-cfm-btn-back]` | `SurveyPage.tsx` nav |
90
+ | `showQuestionNumbers` | `[data-cfm-question-number]` | `Question.tsx` title |
91
+ | `showRequiredAsterisk` | `[data-cfm-required]` | `Question.tsx` title |
92
+ | `showFooterLogo` | `[data-cfm-footer-logo]` | `Footer.tsx` |
93
+
94
+ **Anti-pattern:** Putting `sticky top-0` on the same element as `cfm-header` — bridge used to hide progress when toggling header. Templates use `cfm-sticky-chrome` wrapper without using it for `showHeader`.
95
+
96
+ ### Survey chrome content
97
+
98
+ | Patch field | DOM target |
99
+ |-------------|------------|
100
+ | `surveyTitle` | `[data-cfm-survey-title]` |
101
+ | `thankYouMessage` | `[data-cfm-thank-you]` |
102
+ | `buttonLabels.next` / `.back` | `[data-cfm-btn-next]` / `[data-cfm-btn-back]` |
103
+ | `copyrightText` | `[data-cfm-copyright]` |
104
+ | `footerLinks[]` | `[data-cfm-footer-links]` innerHTML |
105
+
106
+ ### Question-type DOM attributes (on `<html>`)
107
+
108
+ Set via `contentPatch.domAttributes`:
109
+
110
+ | Attribute | Wizard source | Component reads |
111
+ |-----------|---------------|-----------------|
112
+ | `data-cfm-mcq-style` | `questionTypes.MCQ.optionStyle` | MCQ card classes / CSS |
113
+ | `data-cfm-nps-button-style` | `questionTypes.NPS_SCALE.buttonStyle` | `RatingScale.tsx` |
114
+ | `data-cfm-dropzone-style` | `questionTypes.FILE_UPLOAD.dropzoneStyle` | `FileUploadScale.tsx` |
115
+
116
+ Vite preview harness also applies `previewState` via `PreviewConfigContext` for placeholders, NPS hints, rank variant, etc.
117
+
118
+ ---
119
+
120
+ ## Question card (Theme step)
121
+
122
+ Question outer wrapper **must** use theme vars:
123
+
124
+ ```tsx
125
+ <section
126
+ id={question.id}
127
+ className="cfm-question-card space-y-4"
128
+ style={{
129
+ background: 'var(--cfm-card-bg, #fff)',
130
+ borderColor: 'var(--cfm-card-border-color, #e5e7eb)',
131
+ padding: 'var(--cfm-card-padding, 24px)',
132
+ boxShadow: 'var(--cfm-card-shadow, 0 1px 3px rgba(0,0,0,0.1))',
133
+ borderRadius: 'var(--cfm-border-radius, 12px)',
134
+ borderWidth: 1,
135
+ borderStyle: 'solid',
136
+ }}
137
+ >
138
+ ```
139
+
140
+ Question title hooks:
141
+
142
+ ```tsx
143
+ <h2>
144
+ {question.questionNumber != null && (
145
+ <span data-cfm-question-number className="mr-1 font-semibold">
146
+ {question.questionNumber}.
147
+ </span>
148
+ )}
149
+ <span dangerouslySetInnerHTML={{ __html: displayQuestionText }} />
150
+ {question.required && (
151
+ <span data-cfm-required className="ml-1 text-red-500">*</span>
152
+ )}
153
+ </h2>
154
+ ```
155
+
156
+ TEXTFIELD placeholder live preview: `data-cfm-textfield` on input/textarea.
157
+
158
+ ---
159
+
160
+ ## SurveyPage wiring (content phase)
161
+
162
+ ```tsx
163
+ <div className="flex min-h-screen flex-col" style={{ background: 'var(--cfm-background)' }}>
164
+ <SurveyStickyChrome progressPercentage={state.progressPercentage} />
165
+ <main style={{ maxWidth: 'var(--cfm-max-width)' }}>...</main>
166
+ <Footer />
167
+ </div>
168
+ ```
169
+
170
+ Intro / end / paused: standalone `<Header />` (not sticky chrome). Footer still uses `cfm-footer`.
171
+
172
+ ---
173
+
174
+ ## Local wizard quit — preview garbage collection
175
+
176
+ When the client **quits** local wizard (`npx cfm-sdk customize`) without saving:
177
+
178
+ 1. Wizard SPA calls `POST /api/wizard-quit` on the customize server
179
+ 2. CLI deletes `out/previews/` on agent disk
180
+ 3. CLI exits; no orphan preview folders
181
+
182
+ When client **saves**: config written; previews remain until CLI exits (then server stops).
183
+
184
+ When CLI is **Ctrl+C**'d: customize server runs the same preview cleanup on shutdown.
185
+
186
+ EC2 `customize-ui`: quit calls `POST /wizard/session/quit` — disk session folder deleted (see [`wizard-config-handoff.md`](wizard-config-handoff.md)).
187
+
188
+ ---
189
+
190
+ ## Verification checklist (blocking before Phase 6b)
191
+
192
+ - [ ] Copied all templates from § Copy order
193
+ - [ ] `grep -r data-cfm-logo src/components/Header.tsx` — match
194
+ - [ ] `grep -r data-cfm-progress src/components/ProgressBar.tsx` — match
195
+ - [ ] `grep -r cfm-footer src/components/Footer.tsx` — match
196
+ - [ ] `grep -r data-cfm-question-number src/components/Question.tsx` — match
197
+ - [ ] `grep -rE 'var\\(--cfm-' src/components/` — no wizard colors as hardcoded hex only
198
+ - [ ] `bash templates/verify-agent-build.sh` exit 0
199
+ - [ ] `npx cfm-sdk export-previews` exit 0
200
+
201
+ ---
202
+
203
+ ## Related
204
+
205
+ | Doc | Role |
206
+ |-----|------|
207
+ | [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) | Full build guide |
208
+ | [`wizard-question-type-styling.md`](wizard-question-type-styling.md) | Per-type CSS vars |
209
+ | [`10-header-footer.md`](../01-components/10-header-footer.md) | Branding patterns |
210
+ | [`12-survey-chrome.md`](../03-ui-specs/12-survey-chrome.md) | Chrome layout spec |
@@ -53,6 +53,8 @@ When the client uploads a logo in the local wizard:
53
53
 
54
54
  The logo is **not** fetched from wizard memory on CloudFront — only from the deploy bundle. See [`aws-deploy.md`](aws-deploy.md) § Logos & local assets.
55
55
 
56
+ **Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/` on agent disk. Ctrl+C on customize CLI runs the same cleanup.
57
+
56
58
  ---
57
59
 
58
60
  ## Remote client flow — `customize-ui` (fallback only)
@@ -1,10 +1,13 @@
1
1
  # Wizard Live Preview — Client Build Guide
2
2
 
3
3
  > **Read during Phase 5 (BUILD) — mandatory.** Wizard **run** (Phase 6b) is optional; this contract is required for every agent build.
4
+ > **Authoritative hook list + copy commands:** [`wizard-chrome-contract.md`](wizard-chrome-contract.md) — fixes logo, layout toggles, question card, and per-type preview issues.
4
5
  > Pipeline: [`agent-execution-flow.md`](agent-execution-flow.md).
5
6
 
6
7
  Related docs:
7
8
 
9
+ - [`wizard-chrome-contract.md`](wizard-chrome-contract.md) — **copy-first templates** + symptom → fix table
10
+
8
11
  - [`ui-customization-wizard.md`](ui-customization-wizard.md) — wizard flow and CLI
9
12
  - [`apply-ui-config.md`](apply-ui-config.md) — applying final `survey-ui-config.json` after wizard
10
13
  - [`wizard-api.md`](wizard-api.md) — EC2 `/wizard/*` API
@@ -51,14 +54,25 @@ If components use **hardcoded** colors (`bg-[#e20074]`) with no `var(--cfm-*)` h
51
54
 
52
55
  ## Required project artifacts (Phase 5)
53
56
 
54
- Create these **before** the client opens the wizard:
57
+ **Copy wizard-ready templates first** (do not hand-write chrome from `apps/client`):
58
+
59
+ | Template (SDK) | Copy to |
60
+ |----------------|---------|
61
+ | `templates/docs/templates/Header.tsx` | `src/components/Header.tsx` |
62
+ | `templates/docs/templates/Footer.tsx` | `src/components/Footer.tsx` |
63
+ | `templates/docs/templates/ProgressBar.tsx` | `src/components/ProgressBar.tsx` |
64
+ | `templates/docs/templates/SurveyStickyChrome.tsx` | `src/components/SurveyStickyChrome.tsx` |
65
+ | `templates/docs/templates/LanguageSelector.tsx` | `src/components/LanguageSelector.tsx` |
66
+ | `templates/docs/templates/PreviewBridgeInit.tsx` | `src/components/PreviewBridgeInit.tsx` |
67
+ | `templates/previewBridge.ts` | `src/lib/previewBridge.ts` |
68
+ | `templates/survey-theme.css` | `src/styles/survey-theme.css` |
69
+ | `templates/docs/templates/Question.tsx` | `src/components/Question.tsx` (dispatcher base) |
70
+
71
+ Full symptom → fix table: [`wizard-chrome-contract.md`](wizard-chrome-contract.md).
55
72
 
56
73
  | File | Purpose |
57
74
  |------|---------|
58
- | `src/styles/survey-theme.css` | All `--cfm-*` CSS custom properties (defaults from mockup) |
59
75
  | `survey-ui-config.json` (draft) | Seeds wizard form fields from agent branding |
60
- | `src/lib/previewBridge.ts` | Live bridge in **local dev** (`npx cfm-sdk customize`) — copy from `templates/previewBridge.ts` |
61
- | `src/components/PreviewBridgeInit.tsx` | Mount bridge once in `layout.tsx` (local dev only) |
62
76
 
63
77
  Import theme in `globals.css`:
64
78
 
@@ -1,30 +1,28 @@
1
1
  # Survey Sticky Chrome
2
2
 
3
- > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
- > **Reference implementation:** `apps/client/src/components/SurveyStickyChrome.tsx`
3
+ > **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
4
+ > **Wizard contract:** [`wizard-chrome-contract.md`](../00-integration/wizard-chrome-contract.md) — header and progress are **separate** toggle targets
5
5
  > **Template:** [`templates/SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx)
6
- > **Component**: `SurveyStickyChrome`
7
6
 
8
7
  ## Purpose
9
8
 
10
- Wrap **Header + ProgressBar** in a single `sticky top-0` container so the progress bar stays visible when the respondent scrolls to answer questions lower on the page (especially the last question on a multi-question page).
9
+ Wrap **Header + ProgressBar** in a sticky container. The wizard toggles them **independently**:
11
10
 
12
- ## Props
11
+ | Toggle | Target |
12
+ |--------|--------|
13
+ | `showHeader` | `.cfm-header` on `<header>` only |
14
+ | `showProgressBar` | `[data-cfm-progress]` / `.cfm-progress` only |
13
15
 
14
- ```typescript
15
- type SurveyStickyChromeProps = {
16
- progressPercentage: number; // from state.progressPercentage
17
- };
18
- ```
16
+ Do **not** put class `cfm-header` on the sticky wrapper.
19
17
 
20
- ## Implementation
18
+ ## Implementation (wizard-ready)
21
19
 
22
20
  ```tsx
23
21
  export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyChromeProps) {
24
22
  return (
25
- <div className="sticky top-0 z-30 bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
23
+ <div className="cfm-sticky-chrome sticky top-0 z-30" style={{ background: 'var(--cfm-header-bg)' }}>
26
24
  <Header embedded />
27
- <div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
25
+ <div className="mx-auto w-full px-4" style={{ maxWidth: 'var(--cfm-max-width)' }}>
28
26
  <ProgressBar progressPercentage={progressPercentage} />
29
27
  </div>
30
28
  </div>
@@ -32,48 +30,11 @@ export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyC
32
30
  }
33
31
  ```
34
32
 
35
- ## Spacing (logo progress bar)
36
-
37
- Keep a visible gap between the logo block and the progress track — do **not** flush them together.
38
-
39
- | Token | Tailwind | Purpose |
40
- |-------|----------|---------|
41
- | Top inset | `pt-4` | 16px gap below logo / header row |
42
- | Bottom inset | `pb-5` | 20px gap above scrollable question content |
43
- | Horizontal | `px-4 sm:px-6 lg:px-8` | Align track with `main` content column (`max-w-4xl`) |
44
-
45
- ## Header `embedded` prop
46
-
47
- When `Header` is inside sticky chrome, pass `embedded` to avoid a double shadow:
48
-
49
- ```tsx
50
- <header className={`relative h-[120px] w-full bg-white ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}>
51
- ```
52
-
53
- Use the standalone `Header` (no `embedded`) on intro, end, loading, and terminal pages.
54
-
55
- ## SurveyPage wiring (content phase only)
56
-
57
- ```tsx
58
- <div className="flex min-h-screen flex-col bg-white">
59
- <SurveyStickyChrome progressPercentage={state.progressPercentage} />
60
-
61
- <main className="mx-auto w-full max-w-4xl flex-1 px-4 py-10 sm:px-6 lg:px-8">
62
- <LanguageSelector ... />
63
- {/* questions + nav */}
64
- </main>
65
-
66
- <Footer />
67
- </div>
68
- ```
69
-
70
- Do **not** render a separate `ProgressBar` inside `<main>` — it belongs in sticky chrome only.
33
+ Copy full file from [`templates/SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx).
71
34
 
72
35
  ## Agent checklist
73
36
 
74
- - [ ] `SurveyStickyChrome` (or equivalent sticky wrapper) on `phase.kind === 'content'`
75
- - [ ] `ProgressBar` bound to `state.progressPercentage`
76
- - [ ] `sticky top-0 z-30 bg-white` on wrapper; single shadow on wrapper, not Header
77
- - [ ] `pt-4` gap between logo and progress track; `pb-5` below track
78
- - [ ] Progress track aligned to same `max-w-4xl` as question content
79
- - [ ] Standalone `Header` on intro / end / loading / terminal branches
37
+ - [ ] `Header.tsx` has class `cfm-header` + `data-cfm-logo` + `data-cfm-company`
38
+ - [ ] `ProgressBar.tsx` has `data-cfm-progress` + class `cfm-progress`
39
+ - [ ] Sticky wrapper uses `cfm-sticky-chrome` not `cfm-header`
40
+ - [ ] `pt`/`pb` spacing via `--cfm-sticky-pt` / `--cfm-sticky-pb`
@@ -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)"
@@ -77,7 +77,7 @@ 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
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` |
@@ -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
 
@@ -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
+ }