@explorer02/cfm-survey-sdk
v1.0.3
Published
Headless React SDK for custom survey UIs. Matches CFM respondent-view behavior (navigation, logic, validation, progress, submission); you own all presentation.
Readme
@explorer02/cfm-survey-sdk
Headless React SDK for custom survey UIs. Matches CFM respondent-view behavior (navigation, logic, validation, progress, submission); you own all presentation.
For AI agents
Do not implement from memory. Read the operating contract before writing code:
templates/AGENT.md— monorepo:packages/sdk/templates/AGENT.md; npm:node_modules/@explorer02/cfm-survey-sdk/templates/AGENT.md- Every doc linked from that file
Full index: templates/docs/index.md — monorepo: templates/docs/index.md; npm: node_modules/@explorer02/cfm-survey-sdk/templates/docs/index.md
Per-type routing: templates/docs/MANIFEST.routing.json
Import from the package name in package.json only — never from /src/.
Requirements
This package relies on the following peer dependencies:
| Peer dependency | Version |
| ----------------------- | ------- |
| react | >= 17 |
| @tanstack/react-query | ^5 |
Documentation
Install, scaffold, and integration live in the agent docs:
| Context | Start here |
| ----------------- | ----------------------------------------------------------------- |
| Docs | templates/docs/index.md |
| Installed package | node_modules/@explorer02/cfm-survey-sdk/templates/docs/index.md |
Key reads:
- Mental model:
templates/docs/00-integration/sdk-overview.md - Install:
templates/docs/00-integration/setup/install.md - Hook contract:
templates/docs/00-integration/useSurveySDK.md - Spine / all 11 types:
templates/docs/00-integration/client-component-spine-guide.md
API
useSurveySDK(props)
The single entry point. Pass an options object and receive the survey state, request results, and an onAction dispatcher.
Options
| Option | Type | Description |
| --------------------- | --------------------------- | ------------------------------------------------ |
| instanceId | string | Identifier of the survey instance to load. |
| language | string | Language code to request the survey in. |
| placeholders | Record<string, string> | Runtime values injected into placeholder text. |
| customFieldValues | CustomFieldValueMap | Contextual values used by display / skip logic. |
| savePartialResponse | SavePartialResponseOption | Enable and tune saving of in-progress responses. |
| debug | boolean | Enable verbose SDK logging. |
Returns
| Field | Description |
| --------------------- | ------------------------------------------------------------------------- |
| state | Current phase, page, questions, answers, validation errors, and progress. |
| onAction | Dispatch survey actions (see below). |
| surveyQueryResults | { data, error, isLoading } for the survey load request. |
| submitSurveyResults | { data, error, isLoading } for the final submission. |
| partialSaveResults | { data, error, isLoading } for in-progress saves, when enabled. |
Actions
All interactions go through onAction via the exported ACTIONS constant — see useSurveySDK.md § Action Dispatching for payloads and behavior.
Supported question types
Exported as QUESTION_TYPE — full matrix in question-type-sdk-matrix.md.
License
See the package's license for terms of use.
AI agent integration
For AI coding agents building a full survey UI, see templates/docs/ and templates/AGENT.md.
Maintainer — publish
npm publish --access public (and node publish.js --access public) automatically runs prepublishOnly → npm test. Publish aborts if any test fails.
For a fuller release gate (build, coverage, lint, agent docs):
npm run prepublish:checkDoc health only:
bash packages/sdk/scripts/lint-agent-docs.shStale docs cause agents to hallucinate removed APIs — fix docs before shipping.
Agent doc regression (scenario suite)
After doc changes, spot-check agents against these scenarios. When an agent fails, add a targeted cookbook snippet or boundary row — do not expand entry prose.
| # | Scenario | Pass criteria |
|---|----------|---------------|
| 1 | CSAT matrix, 2 rows, emoji mode | Anchor/header aligned over cells; no label text inside cells |
| 1b | CSAT single-row | Full-width cells under anchor percents; no stmt column |
| 1c | CSAT star + anchors + N/A (Q7) | All m stars visible (outline) before click; label i centered over star i; n === m grid anchors; NA below row; not NPS flex strip |
| 1d | CSAT emoji clustered (Q17) | Cell centering; header/cell same m; Δ(center) < 4px |
| 1e | CSAT numbered + reverse + N/A (Q18) | Badges = displayIdx+1; NA below row; no col.label in cells |
| 2 | CFM bipolar (fixture page 7) | Left stmt | scale | right oppositeStatementText; anchors only over scale zone; BipolarAnchorHeaderRow flex regime — not likert grid header |
| 2c | CFM carousel misaligned (Q13) | gridLayout=carousel branch; header/cell same m; card + nav + position footer (NEAR-MISS if missing nav) |
| 2b | CFM bipolar NEAR-MISS audit | grep 0 0 25% + oppositeStatementText; CLI Matrix bipolar layout if stacked left |
| 3 | MCQ single-select | Stores option.id; selected state from state.answers[id] |
| 3b | MCQ multi + maxSelections | Toggle string[]; block add at max |
| 3c | MCQ Other postal | Searchable country dropdown + local input; stores ISO2\|local |
| 3d | MCQ Other date | Calendar picker; placeholder matches dateFormat |
| 4 | RATING matrix, graphics | CustomSliderTrack, not cell grid; one common label row for all statements |
| 4b | RATING graphics multi-statement (page 11) | One common label row; row loop = stmt + track only; emoji thumb may be styled freely; no label/thumb overlap |
| 5 | SLIDER matrix N/A | Stores 'N/A' string |
| 10 | SLIDER matrix single-row (page 2) | Colored 0–10 tick header above full-width track; no stmt column |
| 11 | CSAT/CFM matrix dropdown (last row) | Open panel overlaps next question; fully selectable; onPanelOpenChange wired |
| 12 | RANK_ORDER dragAndDrop | Drag persists; Up/Down reorder; RANK_ORDER_REORDER not CHANGE; when §3c-r textAndImage, each row has <img> + label |
| 6 | Client says "deploy" before JWT | C1 checkpoint; JWT ask first |
| 7 | verify-build FAIL: Intro Start | Routes to intro-start-gate, fixes, re-runs verify |
| 8 | TEXTFIELD long, limit 500 | Visible counter from maxCharacterCount; updates on type |
| 9 | CFM transpose + checkboxes (Q16 / page 10) | Statement headers = Statement Items; radios centered under headers; Δ(center) < 4px |
| 9b | CFM standard likert (Q17) | Scale Item headers over radio centers; justify-items-center or per-cell justify-center |
| 13 | Agent batches 3 types then verifies once | Blocked at HOOK 2 — must run HOOK 1 per type |
| 14 | CSAT numbered uses col.label in cells | HOOK 3 grep → Broken; fix to displayIdx + 1 |
| 15 | RATING graphics reads numeric index from selectedValue | HOOK 3 code read → Broken P0; use column id + matrixColumnStoredValue |
| 16 | MCQ Other date = plain text input | HOOK 3 grep McqOtherInput → Broken P0; picker imports required |
| 17 | TEXTFIELD no counter in JSX | HOOK 3 grep → Broken P1 |
| 18 | Agent skips HOOK 3 because verify-build passed | sync-from-fetch prints BLOCKED footer; verify-layout --from-fetch fails until layout-audit.md Broken = 0 |
| 19 | Agent uses MCP walk instead of code audit | Non-compliant — audit report must cite file:line grep evidence |
| 20 | HEATMAP max clicks | Handler guards spots.length < question.maxClicksAllowed; footer shows limit; grep passes |
| 21 | CFM bipolar + Label 1/2/3 (page 7) | Anchor header row with flex:'0 0 25%' spacers; Label i Δ(center) < 4px over radio i (Q12) |
| 22 | CFM bipolar slider vs radio | Bipolar slider uses CustomSliderTrack in center zone — not radio grid; anchors use columnPercent(i, n) not m (Q13) |
| 23 | RATING graphics tooltip (Q22 / page 11) | 2 statements, 7 cols; tooltip "Option 4" over column 4 label + thumb; columnPercent grep passes |
| 24 | CFM carousel (page 13) | LikertMatrixCarouselLayout: statement once; col.label + radio per column; chevrons + dots |
| 25 | SLIDER dual header (Q11) | Tick badge row + anchor row — no "Label 5" over "9/10" overlap |
| 33 | Slider thumb geometry (Q10/Q11/Q22) | CustomSliderTrack lane top: 50%; graphics getEmojiForIndex in thumb — verify-interactions passes |
| 26 | SLIDER pointerLeave kills drag | setPointerCapture on range input; verify-interactions passes |
| 27 | SLIDER per-row tick badges (Q11 bad) | Common header once; trackOnly rows; scan fails per-row ticks= |
| 28 | RATING/SLIDER mode mix | tickPercent on SLIDER; columnPercent on graphics — scaleValues.ts grep |
| 29 | Matrix tooltip in track | Parent header overlay + hideTooltip; scanMatrixTrackTooltipDom passes |
| 30 | CSAT star / CFM likert / RATING numbered duplicate headers | showColumnHeaders, no scaleAnchorLabels → exactly one visible header row (no duplicate Poor/Average/Good or 1–10 bands); MatrixHeaderRow / if-else grep |
| 31 | INPUT 1 LeetCode-style prompt (IDE/terminal motifs) | Post-JWT Phase 5a fills §5a surface map from §0 + prompt; Phase 5b applies IDE cards, terminal header, segmented progress — not generic Sprinklr kit only |
| 32 | Flex-in-one-grid-cell / invisible borders on dark theme | Per-column MatrixGridRow children (not gridColumn + justify-between); borderWidth + borderColor in cell helper — verify-layout + verify-interactions |
