@explorer02/cfm-survey-sdk 0.2.9 → 0.3.0

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 (40) hide show
  1. package/dist/cli/index.js +41 -38
  2. package/dist/cli/index.mjs +39 -36
  3. package/package.json +1 -1
  4. package/templates/docs/00-integration/agent-execution-flow.md +6 -6
  5. package/templates/docs/00-integration/agent-operating-contract.md +1 -2
  6. package/templates/docs/00-integration/component-checklist.md +0 -1
  7. package/templates/docs/00-integration/ui-customization-wizard.md +38 -29
  8. package/templates/docs/00-integration/wizard-chrome-contract.md +1 -11
  9. package/templates/docs/00-integration/wizard-config-handoff.md +19 -11
  10. package/templates/docs/00-integration/wizard-post-customize-build.md +0 -6
  11. package/templates/docs/00-integration/wizard-question-type-styling.md +3 -3
  12. package/templates/docs/00-integration/wizard-troubleshooting.md +4 -20
  13. package/templates/docs/index.md +1 -1
  14. package/templates/docs/templates/CsatMatrixScale.tsx +59 -53
  15. package/templates/docs/templates/CustomSliderTrack.tsx +8 -17
  16. package/templates/docs/templates/FileUploadScale.tsx +4 -12
  17. package/templates/docs/templates/Header.tsx +48 -32
  18. package/templates/docs/templates/HeatmapScale.tsx +2 -5
  19. package/templates/docs/templates/LikertMatrixScale.tsx +45 -57
  20. package/templates/docs/templates/MatrixDropdown.tsx +11 -12
  21. package/templates/docs/templates/Question.tsx +24 -4
  22. package/templates/docs/templates/RankOrderScale.tsx +9 -42
  23. package/templates/docs/templates/RatingScale.tsx +0 -38
  24. package/templates/docs/templates/SliderMatrixScale.tsx +3 -4
  25. package/templates/docs/templates/verify-agent-build.sh +0 -17
  26. package/templates/preview-harness/preview-bridge.inline.js +17 -14
  27. package/templates/preview-harness/previewPages.js +23 -15
  28. package/templates/preview-harness/previewPages.ts +7 -5
  29. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +22 -14
  30. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +13 -2
  31. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +6 -11
  32. package/templates/previewBridge.ts +5 -5
  33. package/templates/survey-theme.css +17 -17
  34. package/templates/wizard-dist/assets/{PreviewMock-CysRws9G.js → PreviewMock-Bax7oRAL.js} +1 -1
  35. package/templates/wizard-dist/assets/{TypePanel-DWX1LL3q.js → TypePanel-D2t4FPSd.js} +1 -1
  36. package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
  37. package/templates/wizard-dist/assets/index-c5lka74l.js +34 -0
  38. package/templates/wizard-dist/index.html +2 -2
  39. package/templates/wizard-dist/assets/index-DIuc7AU2.js +0 -34
  40. package/templates/wizard-dist/assets/index-pCM8n0FT.css +0 -1
@@ -2,6 +2,37 @@
2
2
 
3
3
  > Optional **run** after Phase 6 verify. **Wizard-ready build** is mandatory in Phase 5 — see [`agent-execution-flow.md`](agent-execution-flow.md).
4
4
 
5
+ ### Port selection (strict)
6
+
7
+ The CLI calls `findPort(4200)` — tries **4200**, then **4201**, **4202**, … until a port is free.
8
+
9
+ **Agent rules:**
10
+
11
+ 1. **Do not** hardcode `localhost:4200` if the CLI printed a different port.
12
+ 2. **Share only** the URL the CLI prints under `🔗 Client wizard link — share this URL only:`.
13
+ 3. **Do not** give the client a second link, a file path, or `out/previews/` paths.
14
+
15
+ Example CLI output:
16
+
17
+ ```
18
+ 🔗 Client wizard link — share this URL only:
19
+ http://localhost:4201
20
+ Port 4201 (auto-selected if 4200 was in use)
21
+ ```
22
+
23
+ ### Client quit without save
24
+
25
+ If the client clicks **Quit** (no Review & Build):
26
+
27
+ - CLI runs preview GC (`out/previews/` deleted)
28
+ - CLI exits without `survey-ui-config.json` update
29
+ - **Agent must NOT** re-run `npx cfm-sdk customize` or send another wizard link
30
+ - **Agent must** ask deploy confirmation (INPUT 4) using the **existing** build from Phase 6
31
+
32
+ See [`wizard-config-handoff.md`](wizard-config-handoff.md) § Quit without save.
33
+
34
+ ---
35
+
5
36
  ## Default: local wizard (`customize`)
6
37
 
7
38
  When the client says **yes** to customization in agent chat, **always run:**
@@ -12,8 +43,7 @@ npx cfm-sdk customize
12
43
 
13
44
  This is the **recommended default** because it:
14
45
 
15
- - Probes the first free port from **4200** upward and starts the wizard there
16
- - Prints a machine-parseable line: **`WIZARD_LINK=http://localhost:{port}`** — agent shares **only** this URL with the client (never hardcode `:4200`)
46
+ - Serves wizard + component previews from **one local server** (`localhost:4200`)
17
47
  - Auto-exports all **11** question-type previews from agent components
18
48
  - Writes `survey-ui-config.json` **directly** on save — no S3, no session polling
19
49
  - Cleans up local `out/previews/` when client quits without saving (`POST /api/wizard-quit`) or when CLI exits (Ctrl+C)
@@ -21,15 +51,9 @@ This is the **recommended default** because it:
21
51
 
22
52
  **Agent script after client says yes:**
23
53
 
24
- > *"I'll start the customization wizard. Use the link I share — complete all steps and click **Review & Build** when done."*
54
+ > *"I'll open the customization wizard in your browser. Complete all steps and click **Review & Build** when done — I'll apply your changes automatically."*
25
55
 
26
- **Strict link handoff:**
27
-
28
- 1. Run `npx cfm-sdk customize` and wait for `WIZARD_LINK=http://localhost:…` in CLI output
29
- 2. Share **only** that URL with the client — do not guess port 4200
30
- 3. Keep the terminal open until CLI exits
31
-
32
- **On successful save:**
56
+ The CLI **blocks** until the client saves. Wait for:
33
57
 
34
58
  ```
35
59
  ✅ UI configuration saved to survey-ui-config.json
@@ -37,8 +61,6 @@ This is the **recommended default** because it:
37
61
 
38
62
  Then apply per [`apply-ui-config.md`](apply-ui-config.md).
39
63
 
40
- **If client quits without saving:** CLI exits 0 after GC (`out/previews/` removed). **Do not** re-run the wizard. Skip Phase 6c. Ask deploy confirmation (Phase 7 / INPUT 4) with the existing Phase 6 build.
41
-
42
64
  ### Who can use it
43
65
 
44
66
  | User | Can use `customize`? |
@@ -46,7 +68,7 @@ Then apply per [`apply-ui-config.md`](apply-ui-config.md).
46
68
  | Client on **same machine** as agent (Cursor chat on dev laptop) | **Yes — default** |
47
69
  | Client on **another device** | **No** — use EC2 fallback below |
48
70
 
49
- The wizard URL is **`WIZARD_LINK` from CLI output** — only reachable on the machine running the CLI (port may be 4201+ if 4200 is busy).
71
+ `localhost:4200` is only reachable on the machine running the CLI.
50
72
 
51
73
  ---
52
74
 
@@ -60,7 +82,7 @@ npx cfm-sdk customize-ui
60
82
  ```
61
83
 
62
84
  - Uploads previews to EC2 disk (local-server model on remote host)
63
- - Prints **`WIZARD_LINK=…`** after previews upload — agent sends **only** that URL to client
85
+ - Prints EC2 wizard link — agent sends link to client
64
86
  - CLI **polls** until client submits → writes `.final.json` + `.diff.md`
65
87
  - Requires deploy-api on EC2 + `CFM_INSTANCE_ID`
66
88
 
@@ -87,7 +109,7 @@ After Phase 6 verify + dev server:
87
109
  ```mermaid
88
110
  sequenceDiagram
89
111
  participant Agent as Agent CLI
90
- participant Srv as localhost port from WIZARD_LINK
112
+ participant Srv as localhost:4200
91
113
  participant Wizard as Wizard SPA
92
114
  participant Prev as out/previews/
93
115
 
@@ -120,24 +142,11 @@ sequenceDiagram
120
142
 
121
143
  ---
122
144
 
123
- ## Port probe + link-only handoff (mandatory)
124
-
125
- | Step | Agent action |
126
- |------|----------------|
127
- | 1 | Run `npx cfm-sdk customize` (or `customize-ui` for remote) |
128
- | 2 | Read `WIZARD_LINK=…` from CLI stdout — **never** assume `localhost:4200` |
129
- | 3 | Share **only** that URL with the client |
130
- | 4 | Wait until CLI exits (save → exit 0, quit → exit 0 with GC message) |
131
-
132
- Optional: set `CFM_WIZARD_OPEN_BROWSER=1` to auto-open the wizard in the agent's browser.
133
-
134
- ---
135
-
136
145
  ## Agent checklist
137
146
 
138
147
  - [ ] Phase 5: wizard-preview artifacts wired
139
148
  - [ ] Client opted in (INPUT 3)
140
- - [ ] Parsed `WIZARD_LINK` from CLI and shared only that URL with client
149
+ - [ ] Ran **`npx cfm-sdk customize`** (default) and waited for CLI exit 0
141
150
  - [ ] Read `survey-ui-config.json` (or `.diff.md` if used EC2 fallback)
142
151
  - [ ] Phase 6c: applied per [`apply-ui-config.md`](apply-ui-config.md)
143
152
  - [ ] Re-ran verify + build
@@ -12,6 +12,7 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
12
12
  | Wizard control / symptom | Required fix |
13
13
  |--------------------------|--------------|
14
14
  | Logo not updating in Branding / Header preview | `data-cfm-logo` img + `data-cfm-logo-text` fallback in `Header.tsx` |
15
+ | Logo shows colored pad behind image | Logo `<img>` must use `background: transparent` — **never** `var(--cfm-primary)` on logo box |
15
16
  | Company name not updating | `data-cfm-company` span in `Header.tsx` |
16
17
  | 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
18
  | Show footer toggle does nothing | `footer` + class `cfm-footer` |
@@ -25,21 +26,10 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
25
26
  | Text placeholder color not updating | `data-cfm-textfield` + `--cfm-input-placeholder` in CSS / `::placeholder` rule |
26
27
  | Matrix 2nd row toggle | Harness adds row via `previewMultiStatement` — `LikertMatrixScale` must render all `statementRows` from props |
27
28
  | Blank page after deploy | **Never** reference `UI_CONFIG` without import — use `src/lib/uiConfig.ts` |
28
- | Logo pink/colored box behind image | Logo `background: var(--cfm-header-logo-bg, transparent)` — never `var(--cfm-primary)` on logo img |
29
- | Logo + company placement | Locked: logo left, company name after logo in `.cfm-header-brand` flex row — no grid placement templates |
30
- | Logo size | `--cfm-header-logo-width/height` from wizard max-dimension slider; `object-contain` |
31
29
  | Logo wrong on CloudFront | Never change `global.logo.fileName` manually — use wizard file in `public/` |
32
30
 
33
31
  ---
34
32
 
35
- ## Agent link handoff (strict)
36
-
37
- 1. Run `npx cfm-sdk customize` — read **`WIZARD_LINK=http://localhost:{port}`** from CLI (port may be 4201+)
38
- 2. Share **only** that URL with the client
39
- 3. On quit without save: CLI exits 0, GC `out/previews/` — **do not** re-run wizard; ask deploy (Phase 7)
40
-
41
- ---
42
-
43
33
  ## Copy order (agents — do this in Phase 5)
44
34
 
45
35
  ```bash
@@ -30,18 +30,16 @@ npx cfm-sdk customize
30
30
  The CLI will:
31
31
 
32
32
  1. Export all 11 component preview pages (`export-previews` if needed)
33
- 2. Probe first free port from 4200 and start wizard CLI prints **`WIZARD_LINK=http://localhost:{port}`**
34
- 3. **Block** until client clicks **Review & Build** or quits
35
- 4. On save: write `survey-ui-config.json` and exit 0
36
- 5. On quit: GC `out/previews/` and exit 0 — **do not re-run wizard**; ask deploy (Phase 7)
33
+ 2. Start wizard at `http://localhost:4200`
34
+ 3. Open browser on the **agent machine**
35
+ 4. **Block** until client clicks **Review & Build**
36
+ 5. Write `survey-ui-config.json` and exit 0
37
37
 
38
38
  **Agent must:**
39
39
 
40
- - [ ] Parse `WIZARD_LINK` from CLI and share **only** that URL with client (never hardcode `:4200`)
41
- - [ ] Client is on the **same machine** (can open the `WIZARD_LINK` host)
42
- - [ ] Keep terminal open until CLI exits
43
- - [ ] On save: apply per Phase 6c from `survey-ui-config.json`
44
- - [ ] On quit: skip Phase 6c; ask deploy confirmation with existing Phase 6 build
40
+ - [ ] Client is on the **same machine** (can see `localhost:4200`)
41
+ - [ ] Keep terminal open until `✅ UI configuration saved`
42
+ - [ ] Apply per Phase 6c from `survey-ui-config.json`
45
43
 
46
44
  **Artifact:** `survey-ui-config.json` — apply source for local flow.
47
45
 
@@ -56,10 +54,20 @@ When the client uploads a logo in the local wizard:
56
54
 
57
55
  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.
58
56
 
59
- **Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/` → exits 0. Agent **must not** re-run the wizard or share a new link — proceed to Phase 7 deploy ask.
57
+ **Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/`.
60
58
 
61
59
  **After save:** `POST /api/save-config` also deletes `out/previews/` before CLI exits (preview garbage collection).
62
60
 
61
+ **Quit without save (no second wizard run):**
62
+
63
+ | Event | Agent action |
64
+ |-------|----------------|
65
+ | Client clicks **Quit** in wizard | Wait for CLI exit; previews GC'd automatically |
66
+ | CLI exits without `✅ UI configuration saved` | **Do not** run `customize` again or share another wizard URL |
67
+ | Build still valid from Phase 6 | Ask deploy confirmation (INPUT 4) — use existing `out/` from prior `npm run build` |
68
+
69
+ The agent does **not** re-open the wizard after quit. Garbage collection removes preview artifacts only; the production build in `out/` from Phase 6 remains unless the client requests a rebuild.
70
+
63
71
  ---
64
72
 
65
73
  ## Remote client flow — `customize-ui` (fallback only)
@@ -278,7 +286,7 @@ npx cfm-sdk fetch-ui-config
278
286
  | Mistake | Fix |
279
287
  |---------|-----|
280
288
  | Killed CLI during poll | Re-run `customize-ui`; client re-submits |
281
- | Client quit wizard | CLI exits 0; GC done (`out/previews/` removed). **Do not** re-run wizard skip Phase 6c, ask deploy (INPUT 4) |
289
+ | Client quit wizard | CLI exits with abandon message; S3 GC done re-run `customize-ui` for new session |
282
290
  | Applied from `survey-ui-config.json` while still seed | Use `.final.json` or run `confirm-ui-config` after apply |
283
291
  | Only changed primary color, ignored diff | Read **entire** `diff.md` — clients often change 20+ paths |
284
292
  | Skipped `confirm-ui-config` | Active JSON stays seed; next wizard re-open is wrong |
@@ -121,12 +121,6 @@ Only after H passes → ask deploy (INPUT 4). See [`aws-deploy.md`](aws-deploy.m
121
121
 
122
122
  **Do not re-run** `npx cfm-sdk export-previews` after wizard save — previews were deleted on purpose (GC). Export-previews is for **component structure changes** during Phase 5, not for applying saved config.
123
123
 
124
- ### If client quit without saving
125
-
126
- CLI exits 0 with GC message. **Do not** re-run wizard or share a new link. Skip this runbook (no Phase 6c). Ask deploy (INPUT 4) with the Phase 6 build.
127
-
128
- **Wizard link:** parse `WIZARD_LINK=…` from CLI — never hardcode `localhost:4200`.
129
-
130
124
  ---
131
125
 
132
126
  ## Wizard steps → live preview keys
@@ -102,11 +102,11 @@ Single-select radio: outer border = focus ring, annulus = cell selected, inner d
102
102
  | Wizard section | Config path | CSS vars | Apply in |
103
103
  |----------------|-------------|----------|----------|
104
104
  | Button style | `questionTypes.NPS_SCALE.buttonStyle` | `data-cfm-nps-button-style` | `RatingScale.tsx` |
105
- | Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on min/max anchor row |
106
- | Number labels | `numberLabelMode`, per-index colors | `--cfm-number-label-mode`, `--cfm-number-mono-color`, `--cfm-number-color-{n}` | Badge row; `resolveNumberLabelColor()` for unselected |
105
+ | Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on anchor row in `Question.tsx` |
106
+ | Number labels | `numberLabelMode`, per-index colors | `--cfm-number-*` | `resolveNumberLabelColor()` / badge row in `RatingScale.tsx` |
107
107
  | Selection | Theme → Question card | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `scaleRadioRingStyle` + `scaleRadioDotStyle` |
108
108
 
109
- **Removed from wizard UI:** NPS selected fill, track bar, track highlight — use Theme → Question card tokens only.
109
+ **Removed from wizard UI:** selected fill, track bar, track highlight — NPS selection uses Theme → Question card tokens only.
110
110
 
111
111
  ### CFM_MATRIX (`CFM_matrix`, `CFM_dropdown`, `CFM_transpose`, `CFM_likert`, `CFM_bipolar`)
112
112
 
@@ -9,7 +9,7 @@
9
9
  | | `customize` **(default)** | `customize-ui` **(fallback)** |
10
10
  |--|---------------------------|--------------------------------|
11
11
  | **When** | Client on same machine as agent | Client on different device |
12
- | **URL** | `WIZARD_LINK` from CLI (dynamic port) | `WIZARD_LINK` from CLI (EC2) |
12
+ | **URL** | `localhost:4200` | EC2 link with `session` + `secret` |
13
13
  | **Previews** | `out/previews/` on agent disk | EC2 disk |
14
14
  | **Save** | `survey-ui-config.json` immediately | Poll → `.final.json` + `.diff.md` |
15
15
  | **GC** | CLI stops → no orphans | Session cleanup on EC2 |
@@ -34,7 +34,7 @@ Footer: **"Mock preview — run customize or customize-ui…"**
34
34
 
35
35
  ### Symptom
36
36
 
37
- Client cannot open the `WIZARD_LINK` URL (wrong port or wrong machine)
37
+ Client cannot open `http://localhost:4200`
38
38
 
39
39
  ### Fix
40
40
 
@@ -46,7 +46,7 @@ export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
46
46
  npx cfm-sdk customize-ui
47
47
  ```
48
48
 
49
- Send **only** the `WIZARD_LINK=…` line from CLI output.
49
+ Send **only the EC2 link** from CLI output.
50
50
 
51
51
  ---
52
52
 
@@ -72,25 +72,9 @@ Wire `data-cfm-*` hooks and `--cfm-*` CSS vars per [`wizard-preview-build-guide.
72
72
 
73
73
  ---
74
74
 
75
- ## Client quit without saving
76
-
77
- ### Symptom
78
-
79
- CLI prints quit message and exits 0; `out/previews/` removed.
80
-
81
- ### Agent action (mandatory)
82
-
83
- - **Do not** re-run `customize` or `customize-ui`
84
- - **Do not** share a new wizard link
85
- - Skip Phase 6c (no `survey-ui-config.json` update)
86
- - Ask deploy confirmation (Phase 7 / INPUT 4) with the existing Phase 6 build
87
-
88
- ---
89
-
90
75
  ## Checklist
91
76
 
92
77
  - [ ] Used **`customize`** as default when client said yes
93
- - [ ] Parsed **`WIZARD_LINK`** from CLI and shared only that URL
94
- - [ ] Waited for CLI exit 0 (save or quit)
78
+ - [ ] Waited for `✅ UI configuration saved`
95
79
  - [ ] Phase 5 preview bridge + `data-cfm-*` wired
96
80
  - [ ] EC2 only if client on remote device
@@ -131,7 +131,7 @@ Run: `bash templates/verify-agent-build.sh`
131
131
  Only after Phase 6 passes and client says **yes**:
132
132
 
133
133
  ```bash
134
- npx cfm-sdk customize # DEFAULT — prints WIZARD_LINK, WAITS until client saves or quits
134
+ npx cfm-sdk customize # DEFAULT — opens localhost:4200, WAITS until client saves
135
135
  ```
136
136
 
137
137
  **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`.
@@ -1,19 +1,21 @@
1
1
  import React, { useState } from 'react';
2
2
  import type { CsatQuestion, RatingMatrixQuestion, MatrixRowAnswers, ScaleColumn } from '@explorer02/cfm-survey-sdk';
3
- import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
3
+ import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
4
+ import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
5
+ import { CustomSliderTrack } from './CustomSliderTrack';
6
+ import MatrixDropdown from './MatrixDropdown';
4
7
  import {
5
- cellSelectedVar,
6
- focusRingVar,
7
- matrixRadioDotStyle,
8
8
  matrixRadioRingStyle,
9
+ matrixRadioDotStyle,
9
10
  scaleCellSelectedStyle,
10
- emojiSizeStyle,
11
11
  unselectedOpacityStyle,
12
+ emojiSizeStyle,
13
+ cellSelectedVar,
14
+ focusRingVar,
12
15
  } from '@/lib/surveyUi/selectionStyles';
13
- import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
14
- import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
15
- import { CustomSliderTrack } from './CustomSliderTrack';
16
- import MatrixDropdown from './MatrixDropdown';
16
+ import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
17
+
18
+ const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
17
19
 
18
20
  type CsatMatrixQuestion = CsatQuestion | RatingMatrixQuestion;
19
21
 
@@ -36,10 +38,10 @@ function GraphicsColumnLabelsRow({ scaleColumns }: { scaleColumns: ScaleColumn[]
36
38
  textAlign: 'center',
37
39
  fontSize: '12px',
38
40
  fontWeight: 500,
41
+ color: '#4b5563',
39
42
  lineHeight: 1.2,
40
43
  wordBreak: 'break-word',
41
44
  width: '64px',
42
- ...csatColumnLabelStyle(),
43
45
  }}
44
46
  >
45
47
  <span dangerouslySetInnerHTML={{ __html: col.label }} />
@@ -104,10 +106,10 @@ function CsatMatrixVerticalList({
104
106
  onClick={() => onCellSelect(row.id, submitValue)}
105
107
  style={{
106
108
  width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
107
- borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`,
109
+ borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`,
108
110
  padding: '14px 18px', cursor: 'pointer', transition: 'all 0.15s',
109
- backgroundColor: isSelected ? 'var(--cfm-mcq-selected-bg)' : '#fff',
110
- color: isSelected ? focusRingVar : '#374151',
111
+ backgroundColor: isSelected ? '#fdf2f8' : '#fff',
112
+ color: isSelected ? '#e20074' : '#374151',
111
113
  fontWeight: isSelected ? 600 : 400, fontSize: '15px', textAlign: 'left',
112
114
  }}
113
115
  >
@@ -126,9 +128,9 @@ function CsatMatrixVerticalList({
126
128
  key={col.id}
127
129
  style={{
128
130
  display: 'flex', alignItems: 'center', gap: '16px',
129
- borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#e5e5e5'}`,
131
+ borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#e5e5e5'}`,
130
132
  padding: '16px 20px', cursor: 'pointer', transition: 'all 0.15s',
131
- backgroundColor: isSelected ? 'var(--cfm-mcq-selected-bg)' : '#fff',
133
+ backgroundColor: isSelected ? '#fdf2f8' : '#fff',
132
134
  }}
133
135
  >
134
136
  <input type="radio" name={`${question.id}-${row.id}`}
@@ -136,8 +138,13 @@ function CsatMatrixVerticalList({
136
138
  onChange={() => onCellSelect(row.id, submitValue)}
137
139
  style={{ position: 'absolute', width: 0, height: 0, opacity: 0 }}
138
140
  />
139
- <div style={matrixRadioRingStyle(isSelected, '20px')}>
140
- {isSelected && <div style={matrixRadioDotStyle(isSelected, '10px')} />}
141
+ <div style={{
142
+ display: 'flex', width: '20px', height: '20px', flexShrink: 0,
143
+ alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
144
+ border: isSelected ? '2px solid #e20074' : '1.5px solid #9ca3af',
145
+ backgroundColor: '#fff',
146
+ }}>
147
+ {isSelected && <div style={{ width: '10px', height: '10px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
141
148
  </div>
142
149
  <span style={{ fontSize: '15px', fontWeight: 500, color: '#111827' }}
143
150
  dangerouslySetInnerHTML={{ __html: col.label }} />
@@ -151,7 +158,7 @@ function CsatMatrixVerticalList({
151
158
  <label style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer', userSelect: 'none' }}>
152
159
  <input type="checkbox" checked={selectedValue[row.id] === null}
153
160
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
154
- style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
161
+ style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
155
162
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>Not Applicable</span>
156
163
  </label>
157
164
  )}
@@ -195,7 +202,7 @@ function CsatMatrixGrid({
195
202
  {/* Labels row */}
196
203
  {showLabels && m > 0 && (
197
204
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
198
- <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
205
+ <div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
199
206
  <div style={{ flex: 1, display: 'flex' }}>
200
207
  <div style={{ flex: 1, position: 'relative', height: '24px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
201
208
  {labels!.map((label, i) => {
@@ -209,8 +216,7 @@ function CsatMatrixGrid({
209
216
  left: `${percent}%`,
210
217
  transform: 'translateX(-50%)',
211
218
  textAlign: 'center', fontSize: '13px', fontWeight: 600,
212
- lineHeight: 1.3, whiteSpace: 'nowrap',
213
- ...csatColumnLabelStyle(),
219
+ color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
214
220
  }}>
215
221
  {label}
216
222
  </div>
@@ -225,7 +231,7 @@ function CsatMatrixGrid({
225
231
  {/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
226
232
  {isGraphics && m > 0 && (
227
233
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
228
- <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
234
+ <div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
229
235
  <div style={{ flex: 1, display: 'flex' }}>
230
236
  <GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
231
237
  {hasNotApplicable && (
@@ -238,7 +244,7 @@ function CsatMatrixGrid({
238
244
  {/* Column Headers for non-graphics */}
239
245
  {!isGraphics && !showLabels && m > 0 && (
240
246
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
241
- <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
247
+ <div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
242
248
  <div style={{ flex: 1, display: 'flex' }}>
243
249
  <div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${scaleColumns.length}, 1fr)` }}>
244
250
  {scaleColumns.map(col => (
@@ -264,9 +270,9 @@ function CsatMatrixGrid({
264
270
  {statementRows.map((row, rowIdx) => (
265
271
  <div key={row.id} style={{
266
272
  display: 'flex', alignItems: 'center', borderRadius: '8px',
267
- padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'var(--cfm-zebra-row)' : '#fff',
273
+ padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
268
274
  }}>
269
- <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0, padding: '0 16px' }}>
275
+ <div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, padding: '0 16px' }}>
270
276
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4 }}
271
277
  dangerouslySetInnerHTML={{ __html: row.statementText }} />
272
278
  </div>
@@ -305,7 +311,7 @@ function CsatMatrixGrid({
305
311
 
306
312
  const RadioNode = (
307
313
  <div style={matrixRadioRingStyle(isSelected)}>
308
- {isSelected && <div style={matrixRadioDotStyle(isSelected)} />}
314
+ <div style={matrixRadioDotStyle(isSelected)} />
309
315
  </div>
310
316
  );
311
317
 
@@ -328,23 +334,22 @@ function CsatMatrixGrid({
328
334
  ...emojiSizeStyle(),
329
335
  transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
330
336
  ...(isSelected ? {
331
- backgroundColor: 'var(--cfm-mcq-selected-bg)',
332
- boxShadow: `0 0 0 2px ${focusRingVar}`,
337
+ outline: `2px solid ${focusRingVar}`,
338
+ backgroundColor: cellSelectedVar,
333
339
  borderRadius: '50%',
334
- } : {}),
340
+ } : unselectedOpacityStyle()),
335
341
  } : isStar ? {
336
342
  padding: '4px',
337
343
  transform: isHovered ? 'scale(1.1)' : 'scale(1)',
344
+ color: isSelected ? cellSelectedVar : focusRingVar,
338
345
  ...(isSelected ? {} : unselectedOpacityStyle()),
339
- opacity: isSelected ? 1 : isHovered ? 1 : undefined,
340
346
  } : isNumbered ? {
341
- width: 'var(--cfm-csat-cell-size, 40px)', height: 'var(--cfm-csat-cell-size, 40px)', borderRadius: '8px',
347
+ width: '40px', height: '40px', borderRadius: '8px',
342
348
  fontSize: '14px', fontWeight: 500,
343
- border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`,
344
- backgroundColor: isSelected ? cellSelectedVar : '#fff',
345
- color: isSelected ? '#fff' : '#111827',
349
+ ...(isSelected
350
+ ? scaleCellSelectedStyle(true)
351
+ : { border: '1px solid #d1d5db', backgroundColor: '#fff', color: '#111827' }),
346
352
  transition: 'all 0.15s',
347
- ...(isSelected ? scaleCellSelectedStyle(true) : {}),
348
353
  } : { padding: '8px' }),
349
354
  }}
350
355
  >
@@ -382,7 +387,7 @@ function CsatMatrixGrid({
382
387
  <label style={{ cursor: 'pointer' }}>
383
388
  <input type="checkbox" checked={selectedValue[row.id] === null}
384
389
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
385
- style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
390
+ style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
386
391
  </label>
387
392
  </div>
388
393
  )}
@@ -441,8 +446,7 @@ function CsatMatrixCarousel({
441
446
  <div key={i} style={{
442
447
  position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
443
448
  textAlign: 'center', fontSize: '13px', fontWeight: 600,
444
- lineHeight: 1.3, whiteSpace: 'nowrap',
445
- ...csatColumnLabelStyle(),
449
+ color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
446
450
  }}>
447
451
  {label}
448
452
  </div>
@@ -486,8 +490,13 @@ function CsatMatrixCarousel({
486
490
  const EmojiNode = isEmoji ? getEmojiForIndex(displayIdx, scaleColumns.length) : null;
487
491
  const StarNode = isStar ? (isSelected ? (CsatStarIcons.filled as any) : (CsatStarIcons.empty as any)) : null;
488
492
  const RadioNode = (
489
- <div style={matrixRadioRingStyle(isSelected)}>
490
- {isSelected && <div style={matrixRadioDotStyle(isSelected)} />}
493
+ <div style={{
494
+ display: 'flex', width: '18px', height: '18px',
495
+ alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
496
+ margin: '0 auto', border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
497
+ backgroundColor: '#fff',
498
+ }}>
499
+ {isSelected && <div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
491
500
  </div>
492
501
  );
493
502
 
@@ -503,23 +512,19 @@ function CsatMatrixCarousel({
503
512
  transition: 'all 0.15s', border: 'none', backgroundColor: 'transparent',
504
513
  cursor: 'pointer', borderRadius: '6px', pointerEvents: 'none',
505
514
  ...(isEmoji ? {
506
- padding: '6px', ...emojiSizeStyle(),
507
- transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
508
- ...(isSelected ? { backgroundColor: 'var(--cfm-mcq-selected-bg)', boxShadow: `0 0 0 2px ${focusRingVar}`, borderRadius: '50%' } : {}),
515
+ padding: '6px', transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
516
+ ...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } : {}),
509
517
  } : isStar ? {
510
- padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)',
511
- ...(isSelected ? {} : unselectedOpacityStyle()),
512
- opacity: isSelected ? 1 : isHovered ? 1 : undefined,
518
+ padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)', opacity: isSelected ? 1 : isHovered ? 1 : 0.5,
513
519
  } : isNumbered ? {
514
- width: 'var(--cfm-csat-cell-size, 40px)', height: 'var(--cfm-csat-cell-size, 40px)', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
515
- border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`, backgroundColor: isSelected ? cellSelectedVar : '#fff', color: isSelected ? '#fff' : '#111827',
516
- ...(isSelected ? scaleCellSelectedStyle(true) : {}),
520
+ width: '40px', height: '40px', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
521
+ border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`, backgroundColor: isSelected ? '#e20074' : '#fff', color: isSelected ? '#fff' : '#111827',
517
522
  } : { padding: '8px' }),
518
523
  }}
519
524
  >
520
525
  {(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
521
526
  </button>
522
- <span style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2, ...csatColumnLabelStyle() }}
527
+ <span style={{ fontSize: '13px', fontWeight: 500, color: '#4b5563', textAlign: 'center', lineHeight: 1.2 }}
523
528
  dangerouslySetInnerHTML={{ __html: col.label }} />
524
529
  </div>
525
530
  );
@@ -532,7 +537,7 @@ function CsatMatrixCarousel({
532
537
  <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
533
538
  <input type="checkbox" checked={selectedValue[row.id] === null}
534
539
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
535
- style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
540
+ style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
536
541
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>N/A</span>
537
542
  </label>
538
543
  </div>
@@ -554,7 +559,7 @@ function CsatMatrixCarousel({
554
559
  <button key={i} type="button" onClick={() => setActiveCarouselIndex(i)}
555
560
  style={{
556
561
  width: i === activeCarouselIndex ? '20px' : '8px', height: '8px', borderRadius: '4px',
557
- backgroundColor: i === activeCarouselIndex ? cellSelectedVar : '#d1d5db', border: 'none', padding: 0,
562
+ backgroundColor: i === activeCarouselIndex ? '#e20074' : '#d1d5db', border: 'none', padding: 0,
558
563
  cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
559
564
  }} />
560
565
  ))}
@@ -594,6 +599,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
594
599
  const isStar = displayStyle === 'star';
595
600
  const isNumbered = displayStyle === 'numbered';
596
601
  const isGraphics = displayStyle === 'graphics';
602
+ const isStandardRadio = displayStyle === 'standard';
597
603
 
598
604
  const scaleColumns = nonNaScaleColumns(question.scaleColumns);
599
605
 
@@ -612,7 +618,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
612
618
  );
613
619
  }
614
620
 
615
- if (isDropdown || isSelectBox) {
621
+ if (isDropdown || isSelectBox || (isStandardRadio && question.type !== 'RATING_MATRIX')) {
616
622
  return (
617
623
  <CsatMatrixVerticalList
618
624
  question={question}