freemium-survey-components 2.0.483 → 2.0.485

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.
package/adrs/.gitkeep ADDED
File without changes
@@ -0,0 +1,35 @@
1
+ # Prism (Backstage) software catalog descriptor.
2
+ # Registers this repo as the shared survey-rendering component library of the
3
+ # SurveyServ system. The `platforms/surveyserv` System entity itself is
4
+ # declared in the freshdesk/surveyserv repo (see PR #5448) — not redeclared
5
+ # here to avoid a duplicate-entity conflict in the catalog.
6
+ # Schema: `get_catalog_schema` in the Prism MCP server is the source of truth.
7
+ apiVersion: backstage.io/v1alpha1
8
+ kind: Component
9
+ metadata:
10
+ name: freemium-survey-components
11
+ namespace: platforms
12
+ title: Freemium Survey Components
13
+ description: >-
14
+ Shared React UI component library that renders the SurveyServ survey
15
+ collection form from a survey definition object. Published as an npm
16
+ package and consumed by SurveyServ frontends (admin, public widget,
17
+ public page).
18
+ annotations:
19
+ github.com/project-slug: freshdesk/freemium-survey-components
20
+ backstage.io/techdocs-ref: dir:.
21
+ tags:
22
+ - survey
23
+ - react
24
+ - library
25
+ - typescript
26
+ - ui-components
27
+ labels:
28
+ exposure: internal
29
+ language: TypeScript
30
+ orgVersion: v1alpha1
31
+ spec:
32
+ type: library
33
+ lifecycle: production
34
+ owner: platforms/plat-maise-surveyserv
35
+ system: platforms/surveyserv
@@ -0,0 +1,21 @@
1
+ # Architecture Decision Records
2
+
3
+ No ADRs are recorded for this repo today — there is no `adrs/`-sourced decision record, and no
4
+ dedicated architecture-decision page was found in Confluence space `SP` beyond general
5
+ architecture/roadmap docs (e.g. "Frontend Roadmap — Surveyserv (Backlog 2026)",
6
+ "freemium-survey-components — Engineering Improvement Brief") which describe planned work rather
7
+ than ratified decisions.
8
+
9
+ Notable architectural choices that exist in code but are not backed by a written ADR (call these
10
+ out as "decision, not yet documented as an ADR" if you're relying on them):
11
+
12
+ - Two branching mechanisms coexist (`dependent_blocks` and `display_logic` — see
13
+ [Deep Dive → Idiomatic vernacular](../architecture/deep-dive.md#4-idiomatic-vernacular)) rather
14
+ than one being fully migrated/removed.
15
+ - The library maintains three parallel layout implementations (`card`, `standard`, `widget`) with
16
+ duplicated `SurveyEndCard`/`PageNavigator`/`QuestionFooter` components per layout rather than a
17
+ shared abstraction (see [Deep Dive → Architecture & control flow](../architecture/deep-dive.md#1-architecture--control-flow)).
18
+ - Peer-dependency-only React (no bundling) via Rollup's `external` config (`rollup.config.ts`).
19
+
20
+ When a real ADR is written for this repo, add source files under `adrs/` at the repo root and
21
+ list/link them from this page.
@@ -0,0 +1,118 @@
1
+ # Code Structure
2
+
3
+ This is the "where do I find X" map of `src/`, grouped by responsibility. Every file is real —
4
+ paths are exact as of this writing (see [Deep Dive → Staleness triggers](deep-dive.md#8-staleness-triggers)
5
+ for what invalidates this map).
6
+
7
+ ## Entry points
8
+
9
+ | File | Description |
10
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `src/index.ts` | The npm package entry point. Re-exports `src/components`, `src/survey`, `src/survey/meta-channel-preview`, `src/types`, and a few constants (`ALL_FONTS`, `DEFAULT_FONT`, `FONT_SIZE_OPTIONS`). Everything exported transitively from here is the library's public surface. |
12
+ | `src/index.d.ts` | Hand-written extra ambient type re-exports (`components/text-input/types`, `components/pickers/types`) that aren't otherwise picked up by the Rollup TS declaration build. |
13
+
14
+ ## Survey rendering shell — `src/survey/`
15
+
16
+ The layout/orchestration layer: turns a survey + answers into a navigable flow. Each layout
17
+ (`card`, `standard`, `widget`) has its own React component plus a shared hook from `src/survey/utils/`.
18
+
19
+ | File | Description |
20
+ | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `src/survey/index.tsx` | Top-level exported components: `Survey` (dispatches to `CardSurvey`/`StandardSurvey` based on `surveyStyle`), `WebInAppSurvey` (widget entry, shows a loader until `survey.id` exists), `QuestionPreview` (single-question, stateless preview used by the survey builder). |
22
+ | `src/survey/question.tsx` | `Question` — the single dispatcher that maps `question.type_info.question_type` to the concrete answer widget from `src/components/*`. This is the file to touch when adding a new question type. |
23
+ | `src/survey/card/index.tsx` | `CardSurvey` + `SurveyEndCard` — one-question-per-screen layout with animated transitions (used standalone and as the base for the widget). |
24
+ | `src/survey/standard/index.tsx` | `StandardSurvey` + `SurveyEndCard` — all-questions-on-one-page (classic form) layout, with paging support. |
25
+ | `src/survey/widget/index.tsx` | `Widget` — the floating/embedded chat-style widget shell (header, minimize/maximize, backdrop, unsubscribe footer) that wraps the widget-specific `Survey`. |
26
+ | `src/survey/widget/survey.tsx` | Widget-flavored `Survey` + `SurveyEndCard` (built on `useCardSurvey`) — adds widget-only concerns: dynamic height animation, progress bar, minimized-header text. |
27
+ | `src/survey/Links/index.tsx` | Renders the thank-you-page review/social/custom-button links block (`show_review_links`, `show_social_links`, `show_custom_button`). |
28
+ | `src/survey/end-card/*.tsx` | Thank-you visual variants: `CardThankYou`, `StandardThankYou`, `WidgetThankYou` (selected by `src/utils.tsx#thankYouComponent`). |
29
+ | `src/survey/survey.scss`, `src/survey/card/index.scss`, `src/survey/standard/index.scss`, `src/survey/widget/index.scss` | Layout-specific styles; consumed via Rollup's `postcss` plugin into `lib/bundle.css`. |
30
+ | `src/survey/*.stories.tsx`, `src/survey/SurveyStory.tsx`, `src/survey/WebInAppSurveyStory` equivalents | Storybook stories/fixtures for the top-level layouts. |
31
+
32
+ ### Survey state & branching logic — `src/survey/utils/`
33
+
34
+ | File | Description |
35
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36
+ | `src/survey/utils/use-survey.ts` | `useSurvey(survey, answers)` — normalizes a raw `SurveyType` into an `AugmentedSurveyType` (injects `question_names` into every block, deep-clones, filters out soft-deleted questions and their orphaned answers). Runs once per `survey.updated_at` change. Used by every layout entry point. |
37
+ | `src/survey/utils/use-card-survey.ts` | `useCardSurvey(props)` — all state/behavior for the card & widget layouts: rendered-block stack, pivot question/answer, per-statement matrix index, navigation, validation, submit. |
38
+ | `src/survey/utils/use-standard-survey.ts` | `useStandardSurvey(props)` — the standard-layout equivalent; additionally handles pages, "sets" (branch-separate-page groups), and pre-rendering multiple blocks at once (all questions visible, not just the active one). |
39
+ | `src/survey/utils/logics.ts` | ~2,800-line pure-function core: display logic evaluation (`evaluateConditions`, `getIsDisplay`), branching/skip navigation (`getNextLogicalBlock`, `fetchNextBlockIndex`, `getNextBlockIndex`), pivot-question resolution (`getPivotQuestion`, `getQuestionForBlock`), button enable/disable rules (`canDisplayButton`, `canDisableButton`), and field validators (`dateValidator`, `formValidator`, `textboxValidator`, `contactformValidator`, `fileUploadQnValidator`). See [Deep Dive → Architecture & control flow](deep-dive.md#1-architecture--control-flow). |
40
+ | `src/survey/utils/index.ts` | Barrel: re-exports `use-survey`, `use-card-survey`, and `logics` (note: `use-standard-survey` is **not** re-exported here — it's imported directly by `src/survey/standard/index.tsx`). |
41
+
42
+ ### Chat-channel previews — `src/survey/meta-channel-preview/`
43
+
44
+ Renders the survey as it would appear inside WhatsApp / Instagram / Facebook Messenger / a generic
45
+ web-chat preview, for the collector-builder UI. Exported via `src/survey/meta-channel-preview` →
46
+ re-exported from `src/index.ts`.
47
+
48
+ | File | Description |
49
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `src/survey/meta-channel-preview/index.tsx` | `ChannelPreview` (dispatches to chat/facebook/whatsapp/instagram renderers) and `ChannelLinkPreview` (renders just the outbound message/link-preview card for a channel). |
51
+ | `src/survey/meta-channel-preview/question.tsx` | Channel-specific question rendering (large file, ~1,300 lines — presentation + branching combined). |
52
+ | `src/survey/meta-channel-preview/answer.tsx` | Renders a previously-given answer bubble in the chat transcript; one of only two files in `src` that runs `DOMPurify.sanitize` before `dangerouslySetInnerHTML`. |
53
+ | `src/survey/meta-channel-preview/facebook/index.tsx`, `web-chat.tsx`, `preview-channel.tsx`, `meta-components.tsx`, `utils.tsx`, `RatingTypeIcon.tsx` | Per-channel chrome (Messenger frame, WhatsApp/Instagram frame, shared header/bubble components, rating icon mapping). |
54
+ | `*.scss`, `*.svg` | Channel-specific chat chrome styling and static art (`thankyou.svg`, `whatsapp-chat-bg.svg`). |
55
+
56
+ ## Question-type & shared UI components — `src/components/`
57
+
58
+ Each subfolder is one question-type widget or shared primitive. `src/components/index.tsx` is the
59
+ barrel that decides what's re-exported to `src/index.ts` (and therefore public).
60
+
61
+ | File/folder | Description |
62
+ | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `src/components/index.tsx` | Barrel — re-exports `binary`, `Button`, `Checkbox`, `Dropdown`, `footer`, `Loader`, `PhoneNumberInput`, `DatePicker`, `DateTimePicker`, `progressbar`, `RadioGroup`, `range`, `rank-order`, `slider`, `survey-progress`, `text-input`. Anything **not** listed here (e.g. `matrix`, `matrix-widget`, `form-field`, `contact-form`, `file-qn`, `consent`, `number`, `textbox`, `questionComment`, `page-container`, `prompt`, `link-preview`, `icons`) is imported directly by path from `src/survey/question.tsx` and is **not** part of the npm package's public surface. |
64
+ | `src/components/binary/index.tsx` | `Binary` — Yes/No / thumbs-style boolean question (also used for `RESOLUTION` type). |
65
+ | `src/components/matrix/index.tsx`, `matrix-widget/index.tsx` | `Matrix` (standard/card layout) and `MatrixWidget` (mobile/widget layout, one statement at a time) for `MATRIX_RATING_SCALE`. |
66
+ | `src/components/range/index.tsx`, `range/utils.tsx` | `Range` — NPS/CSAT/CES point-scale question (emoji, star, number, text variants); exported directly from README's documented public API. |
67
+ | `src/components/rank-order/index.tsx` | `RankOrder` — drag-to-reorder question, built on `@dnd-kit`. |
68
+ | `src/components/slider/index.tsx` | `Slider` — numeric slider question, built on `@radix-ui/react-slider`. |
69
+ | `src/components/checkbox/*`, `radio-button/*`, `dropdown/index.tsx` | `CHECKBOX`/`MULTI_SELECT`, `RADIO`/`DROPDOWN` choice-question widgets, including "Others" free-text handling. |
70
+ | `src/components/text-input/*`, `textbox/index.tsx`, `number/index.tsx` | `TEXT`, `PARAGRAPH`, `NUMBER` question inputs. |
71
+ | `src/components/pickers/*` | `DatePicker`, `DateTimePicker` (`DATE`, `DATE_TIME` questions), built on `@blueprintjs/datetime2`; `constants.ts` holds `TIME_ZONE_SEPERATOR`. |
72
+ | `src/components/form-field/index.tsx`, `contact-form/index.tsx` | `FORM_FIELD` and `CONTACT_FORM` multi-field composite questions, each with its own per-field validators. |
73
+ | `src/components/file-qn/index.tsx` | `FILE` upload question, built on `filepond` + `@pqina/pintura` (image editor). |
74
+ | `src/components/consent/index.tsx` | `CONSENT` question (checkbox or button display format). |
75
+ | `src/components/questionComment/index.tsx` | The optional "add a comment" box attached to range/checkbox/radio/slider/boolean questions. |
76
+ | `src/components/matrix-widget`, `page-container`, `progressbar`, `survey-progress`, `prompt`, `link-preview`, `footer` | Layout/chrome primitives: paged-block accordion, progress bar/percentage, opt-in "prompt" screen shown before the survey starts, outbound link preview card, survey footer (branding/language dropdown). |
77
+ | `src/components/icons/index.tsx` | ~2,650-line SVG icon registry (`Icon` component + named icon components e.g. `WidgetClose`, `WidgetMaximize`, `WidgetMinimize`). |
78
+ | `src/components/button/index.tsx`, `Loader/index.tsx` | Shared `Button` (primary/secondary variants) and `Loader` spinner. |
79
+ | `*/style.scss` next to each component | Component-scoped styles, all rolled into the single `lib/bundle.css`. |
80
+ | `*.stories.tsx` next to relevant components | Component-level Storybook stories (`Button.stories.tsx`, `Checkbox.stories.tsx`, `Dropdown.stories.tsx`, `Radio.stories.tsx`, `RadioGroup.stories.tsx`, `DatePicker.stories.tsx`, `DateTimePicker.stories.tsx`, `Loader.stories.tsx`). |
81
+
82
+ ## Types, constants, utils
83
+
84
+ | File | Description |
85
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
86
+ | `src/types.ts` | ~2,100-line single source of truth for every domain type: `SurveyType`, `SurveyQuestionType`, `CollectorType`, all the component prop types (`SurveyProps`, `WidgetProps`, `QuestionFooterProps`, …). See [Deep Dive → Idiomatic vernacular](deep-dive.md#4-idiomatic-vernacular) for the domain glossary. |
87
+ | `src/constants.ts` | Cross-cutting constants: `AUTO_COMMIT_FIELD_TYPES`, `SKIP_DISABLED_QUESTIONS`, animation durations (`CARD_ANIMATION_DURATION`, `TRANSITION_DURATION`), widget sizing tables (`WIDGET_MAX_HEIGHT`, `WIDGET_CONTAINER_WIDTH`), `EMOJI_PICK_HELPER`/`SCALE_TO_EMOJI` rating-to-icon maps, `REVIEW_PAGE_OPTIONS` (review-site logos for the thank-you page), and a full IANA `timeZones` lookup table. |
88
+ | `src/utils.tsx` | Cross-cutting pure helpers: `isNil`/`isEmpty`/`isRTL`, animation direction helpers (`initialAnimation`/`exitAnimation`), `resolvePlaceholders` (merges `{{...}}` placeholders + NPS/CSAT/CES rating tokens into question/thank-you text), `setCSSVariablesIfPresent` (writes theme colors as CSS custom properties on `document.documentElement`), `transformSurvey`, `getRangeToRender`, `appLogger`/`getFreshSurveyDebug` (debug logging gated by `localStorage`/`sessionStorage` flags), `setOrderForRenderedBlocks`. |
89
+ | `src/palette.ts` | Static brand color palette (hex values) used as CSS fallback defaults (e.g. `palette.S50` as a footer background fallback). |
90
+ | `src/global.d.ts`, `src/react-app-env.d.ts` | Ambient module declarations (SVG/SCSS imports, CRA env types). |
91
+
92
+ ## Mocks & Storybook fixtures
93
+
94
+ | File | Description |
95
+ | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `src/mocks/index.ts` | `MOCK_SURVEY` and related fixture exports — a full realistic `SurveyType` payload used across Storybook stories and manual dev. Not part of the npm package's runtime purpose; dev/test-only. |
97
+ | `src/mocks/gallery.ts`, `src/mocks/fchatgallery.ts` | Large fixture data (`gallery.ts` alone is ~30,600 lines) for gallery/file-question and chat-channel stories. |
98
+ | `src/stories/*.stories.tsx` + matching `*Story.tsx` | One pair per question type/scenario (Boolean, Consent, DateTime, DateTimeZone, File, FormFields, Gallery, LinkPreview, LongTextAnswer, Matrix{Single,Multiple}, Mcq{Single,Multiple}, Message, Number, QuestionPreview, Range, RankOrder, Slider, Textbox) plus `Channel.stories.tsx` / `ChannelLinkPreview.stories.tsx` for the chat previews. `*Story.tsx` holds the reusable render body; `*.stories.tsx` holds the Storybook `Meta`/`StoryObj` wiring. |
99
+ | `src/stories/utils.ts`, `src/stories/style.scss` | Shared story helpers/styles. |
100
+ | `src/survey/*.stories.tsx` (`Survey.stories.tsx`, `WebInAppSurvey.stories.tsx`, `QuestionPreview.stories.tsx`) | Top-level layout stories. |
101
+ | `src/test/__snapshots__/*.snap` | Committed HTML snapshots asserted by the Storybook test-runner (see [Getting Started](../getting-started.md)). |
102
+ | `src/test/snapshot-resolver.js`, `src/test/snapshot-serializer.js` | Custom Jest snapshot resolver/serializer wiring referenced from `test-runner-jest.config.js`. |
103
+
104
+ ## Build & tooling config
105
+
106
+ | File | Description |
107
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `rollup.config.ts` | The npm build: entry `src/index.ts` → `lib/index.cjs.js` (CJS) + `lib/index.esm.js` (ESM), both with `inlineDynamicImports: true` (single-chunk output); externalizes `react`/`react-dom`; extracts styles to `lib/bundle.css` via `rollup-plugin-postcss`; minifies via `rollup-plugin-terser`; type declarations via `rollup-plugin-typescript2`. |
109
+ | `tsconfig.json` | `strict: true` but `noImplicitAny` is not enabled — see [Deep Dive → Failure modes](deep-dive.md#6-failure-modes--sharp-edges). |
110
+ | `.babelrc`, `postcss.config.js` | Babel preset config (used by `react-scripts`/Storybook webpack path) and PostCSS preset-env config for the Rollup CSS extraction. |
111
+ | `scripts/buildUtils.js`, `scripts/frankBuild.js` | Supporting build scripts invoked around the Rollup build. |
112
+ | `.storybook/main.ts` | Storybook 8 config (webpack5 + CRA preset, addons: essentials, interactions, a11y, styling-webpack, Chromatic). |
113
+ | `.storybook/preview.ts`, `.storybook/manager.ts` | Storybook preview/manager UI customization. |
114
+ | `.storybook/test-runner.ts` | Configures `@storybook/test-runner`'s `postVisit` hook to snapshot each story's rendered `innerHTML` after animations settle (`TRANSITION_DURATION` / `WIDGET_MINIMIZE_ANIMATION_DURATION`). This is the repo's only automated test coverage — see [Deep Dive → Failure modes](deep-dive.md#6-failure-modes--sharp-edges). |
115
+ | `test-runner-jest.config.js` | Jest config layered on top of `@storybook/test-runner`'s default config; wires the custom snapshot resolver/serializer from `src/test/`. |
116
+ | `chromatic.config.json` | Chromatic (visual regression / Storybook publish) project config, used by `npm run chromatic`. |
117
+ | `.eslintignore`, `.prettierignore`, `.prettierrc` | Lint/format scope and formatting rules (`@trivago/prettier-plugin-sort-imports` enforces import ordering — do not hand-reorder imports). |
118
+ | `.husky/pre-commit`, `.husky/commit-msg` | Git hooks: pre-commit runs lint + format + install + full Rollup build (see [Deep Dive → Failure modes](deep-dive.md#6-failure-modes--sharp-edges) for why this is slow/bypassable). |
@@ -0,0 +1,401 @@
1
+ # Architecture Deep Dive
2
+
3
+ ## 1. Architecture & control flow
4
+
5
+ **Exported entry points** (all re-exported from `src/index.ts`):
6
+
7
+ - `Survey` (`src/survey/index.tsx`) — full survey form, `surveyStyle: 'card' | 'standard'`.
8
+ - `WebInAppSurvey` (`src/survey/index.tsx`) — the floating/embedded widget entry (wraps `Widget` from `src/survey/widget`).
9
+ - `QuestionPreview` (`src/survey/index.tsx`) — stateless single-question preview (used by the survey builder to preview one question without running the full navigation engine).
10
+ - `ChannelPreview`, `ChannelLinkPreview` (`src/survey/meta-channel-preview/index.tsx`) — chat-channel (WhatsApp/Instagram/Facebook/generic web-chat) previews of a survey.
11
+
12
+ **How a survey object flows in and a response payload flows out** (using `Survey` as the
13
+ canonical path; `WebInAppSurvey`/`Widget` and `ChannelPreview` follow the same shape):
14
+
15
+ 1. Host renders `<Survey survey={surveyObject} answers={priorAnswers} onSubmit={fn} .../>`.
16
+ 2. `useSurvey(props.survey, props.answers)` (`src/survey/utils/use-survey.ts`) normalizes the raw
17
+ `SurveyType` into an `AugmentedSurveyType`: deep-clones it, injects a `question_names` array into
18
+ every block, filters out soft-deleted questions, and drops any prior answers that belonged to a
19
+ now-deleted question. This only re-runs when `survey.updated_at` changes.
20
+ 3. `Survey` dispatches to `CardSurvey` or `StandardSurvey` based on `props.surveyStyle`. Each of
21
+ these calls its own hook — `useCardSurvey` or `useStandardSurvey` (`src/survey/utils/`) — which
22
+ owns **all** interaction state: `blocks`, `renderedBlocks` (the stack of blocks shown so far),
23
+ `activeIndexOfRenderedBlocks`, `answers` (`AnswersType`), per-field error state, the "pivot"
24
+ question/answer (the first rating question that downstream branching keys off), and submitting
25
+ state.
26
+ 4. The active block's question is handed to `Question` (`src/survey/question.tsx`), a single
27
+ `switch (question.type_info.question_type)` dispatcher that renders the matching widget from
28
+ `src/components/*` (e.g. `RANGE` → `Range`, `MATRIX_RATING_SCALE` → `Matrix`/`MatrixWidget`,
29
+ `FILE` → `FileUpload`). Each widget calls `onChangeHandler(value, commitValue?)` on interaction.
30
+ 5. `onChangeHandler` is wired (by the layout component, not by `Question`) to
31
+ `saveFormValues(block, value, commitDirtyValue)`, which updates `answers.formValues` in the hook's
32
+ state. `commitDirtyValue`/`commitValue` distinguishes "value changed" (e.g. typing) from
33
+ "value finalized, safe to evaluate navigation" (e.g. blur, click Next, or an
34
+ `AUTO_COMMIT_FIELD_TYPES` question like `RANGE`/`NPS`/`BOOLEAN`/`MESSAGE` that commits immediately —
35
+ see `src/constants.ts`).
36
+ 6. An effect keyed on `[answers, blocks]` in the hook calls `canMoveToNextBlock` (`logics.ts`); if
37
+ true, it calls `fetchNextBlockIndex` → `getNextBlockIndex` → (for branching/multi-dependent-block
38
+ questions) `getNextLogicalBlock`, all in `src/survey/utils/logics.ts`. These pure functions
39
+ evaluate `dependent_blocks` / `display_logic` against the current answers to decide the next
40
+ block index, or `Infinity` if the survey should end (routes to the resolved thank-you block via
41
+ `getAppliedTYBlock`).
42
+ 7. The resolved next block + its matching question (`getQuestionForBlock`, which also resolves
43
+ rating-group/category branching using the pivot answer) is appended to `renderedBlocks`, and
44
+ `activeIndexOfRenderedBlocks` advances. `onSubmitHandler('PARTIAL')` fires on every step (unless
45
+ at the end), so the host receives a live partial payload on every commit.
46
+ 8. When there is no next block, `onSubmitHandler('COMPLETE')` runs `getCompletedAnswers` (drops
47
+ answers for blocks that were never actually rendered/reached) and calls
48
+ `props.onSubmit(data, callback, status, action)` — `data` is a `SurveyResponseType`
49
+ (`{ [questionName]: answer, others_meta: {...} }`), `status` is `'PARTIAL' | 'COMPLETE' |
50
+ 'PARTIAL_COMPLETE'`, `action` is `'CREATE' | 'EDIT'` (edit when `canResubmit` navigated the user
51
+ back to redo the survey). The host **must** invoke the passed `callback()` once its own save
52
+ completes — the library uses it to flip `isSurveyCompleted`/`isSubmitting` and show the thank-you
53
+ card.
54
+
55
+ ```mermaid
56
+ sequenceDiagram
57
+ participant Host as Host app (e.g. survey-public-widget)
58
+ participant Survey as Survey / CardSurvey / StandardSurvey
59
+ participant Hook as useCardSurvey / useStandardSurvey
60
+ participant Logics as logics.ts (pure functions)
61
+ participant Question as Question dispatcher
62
+ participant Widget as Question-type widget (Range, Matrix, ...)
63
+
64
+ Host->>Survey: <Survey survey answers onSubmit ... />
65
+ Survey->>Hook: useSurvey() normalize survey+answers
66
+ Hook->>Question: render active block's question
67
+ Question->>Widget: render matching widget
68
+ Widget->>Hook: onChangeHandler(value, commit)
69
+ Hook->>Hook: saveFormValues -> setAnswers
70
+ Hook->>Logics: canMoveToNextBlock / fetchNextBlockIndex
71
+ Logics-->>Hook: next block index (or Infinity)
72
+ Hook->>Host: onSubmit(data, callback, 'PARTIAL', action)
73
+ Host-->>Hook: callback() after host persists
74
+ Hook->>Question: render next block's question
75
+ Note over Hook,Logics: repeats until next block index is Infinity
76
+ Hook->>Host: onSubmit(completedData, callback, 'COMPLETE', action)
77
+ Host-->>Hook: callback() -> isSurveyCompleted = true
78
+ Hook->>Survey: render SurveyEndCard (thank-you)
79
+ ```
80
+
81
+ `ChannelPreview`/`ChannelLinkPreview` follow the same `useSurvey` normalization but render a
82
+ read-only chat transcript instead of running the navigation engine — they call `onAnswer` per
83
+ question (`QuestionPreview`) rather than driving `onSubmit`.
84
+
85
+ ## 2. Contracts & interfaces
86
+
87
+ Everything exported (directly or transitively) from `src/index.ts` is **PUBLIC** — consumers'
88
+ builds break if you change its shape. Everything else, including sibling files inside the same
89
+ folder that aren't re-exported, is **INTERNAL** and safe to refactor as long as the public surface's
90
+ behavior is preserved.
91
+
92
+ ### Top-level components (`PUBLIC`)
93
+
94
+ | Export | Source | Props type | Inputs | Outputs / side effects | Error modes |
95
+ | -------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
96
+ | `Survey` | `src/survey/index.tsx` | `SurveyProps` (`src/types.ts`) | `survey: SurveyType`, `answers: SurveyResponseType \| null`, `surveyStyle?: 'standard'\|'card'`, `onSubmit`, `placeholders?`, `preview?`, `isMobile?`, `onAnsweringPrompt`, `initialPivotAnswer?`, `initialQuestionAnswer?`, `onBlockChange?`, `getSurveyProgress?`, `footerProps?` | Calls `onSubmit(data, callback, status, action)` on every commit and on completion; calls `onBlockChange`/`getSurveyProgress` as navigation happens; writes CSS custom properties onto `document.documentElement` for theme colors and the WhatsApp chat background asset URL | Renders `null` if the active block/question can't be resolved (e.g. malformed survey with no valid first block) — no thrown exception; logs `console.error` for unknown `question_type` via `Question` |
97
+ | `WebInAppSurvey` | `src/survey/index.tsx` | `WidgetProps` (`src/types.ts`) | `survey`, `answers`, `onSubmit`, `onDismiss`, `onClose?`, `surveyType: 'default'\|'compact'\|'cozy'`, `placeholders`, `isLoading?`, `loadSurveyCollapsed?`, `unsubscribeUrl?`, `isSurveyCompleted`, `hideMinimize?` | Same `onSubmit` contract as `Survey`; calls `onDismiss`/`onClose` on user dismiss/unmount | Shows a `Loader` while `isLoading`/no `survey.id`; renders `null` if there's no `survey.id` and not loading |
98
+ | `QuestionPreview` | `src/survey/index.tsx` | `QuestionPreviewProps` | `question: SurveyQuestionType`, `block: SurveyBlockType`, `surveyStyle`, `onAnswer?`, `theme?`, `language?` | Calls `onAnswer({ value, block, question })` per interaction; **does not** call `onSubmit` — it is not survey-navigation aware | None known — pure render of one question |
99
+ | `ChannelPreview` | `src/survey/meta-channel-preview/index.tsx` | `SurveyProps & { channel: PreviewChannelType; previewConfig?: PreviewConfigType }` | `channel: 'chat'\|'facebook'\|'whatsapp'\|'instagram'` (others unsupported) | Renders a chat transcript; no `onSubmit`-style callback contract | `console.error` + renders `null` for unsupported `channel` values |
100
+ | `ChannelLinkPreview` | `src/survey/meta-channel-preview/index.tsx` | `LinkPreviewProps` | `channel: 'whatsapp'\|'instagram'\|'facebook'`, `meta_image?`, `meta_title?`, `short_url?`, `message?` | Pure render | `console.error` + renders `null` for unsupported `channel` |
101
+
102
+ ### Standalone components re-exported via `src/components` (`PUBLIC`)
103
+
104
+ | Export | Source | Notes |
105
+ | ------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
106
+ | `Range` | `components/range/index.tsx` | Props: `{ question: SurveyQuestionType; onChangeHandler; value?; isWidget?; isMobile?; widgetStyle?; initialPivotAnswer?; isInitialTransition? }`. `> [UNVERIFIED] The README documents a different, older prop shape (a bare type_info object instead of question: SurveyQuestionType) — the current source is authoritative; the README example will not type-check against the current code.` |
107
+ | `Binary` | `components/binary/index.tsx` | NPS/CSAT boolean-style question widget; also used for `RESOLUTION` question type from `Question`. |
108
+ | `Button` | `components/button/index.tsx` | Shared primary/secondary button. |
109
+ | `Checkbox` | `components/checkbox/index.tsx` | Single checkbox primitive (not the `CHECKBOX` question group — that's `CheckboxGroup`, internal). |
110
+ | `Dropdown` | `components/dropdown/index.tsx` | Generic dropdown primitive. |
111
+ | `SurveyFooter` | `components/footer/index.tsx` | Survey/widget footer (branding, language switcher, footer content). |
112
+ | `Loader` | `components/Loader/index.tsx` | Spinner shown while `WebInAppSurvey` loads. |
113
+ | `PhoneNumberInput` | `components/phone-number-input/index.tsx` | Wraps `react-phone-number-input`. |
114
+ | `DatePicker`, `DateTimePicker` | `components/pickers/` | `DATE` / `DATE_TIME` question inputs; also usable standalone. |
115
+ | `ProgressBar` | `components/progressbar/index.tsx` | Generic step progress bar. |
116
+ | `RadioGroup` | `components/radio-button/radio-group.tsx` | `RADIO`/`DROPDOWN`-format choice group. |
117
+ | `RankOrder` | `components/rank-order/index.tsx` | Drag-to-reorder question. |
118
+ | `Slider` | `components/slider/index.tsx` | Numeric slider question. |
119
+ | `SurveyProgress` | `components/survey-progress/index.tsx` | Progress indicator variant used by standard/card layouts. |
120
+ | `Input`, `TextArea` | `components/text-input/index.tsx` | Generic text input/textarea primitives. |
121
+
122
+ ### Types (`PUBLIC` — all of `src/types.ts` is re-exported)
123
+
124
+ The type surface is large (~140 exported types/interfaces). The load-bearing ones a consumer or
125
+ contributor actually touches:
126
+
127
+ | Type | Role |
128
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | `SurveyType` | The full survey definition (questions, blocks, pages, theme, meta) — the primary input to `Survey`/`WebInAppSurvey`. |
130
+ | `SurveyResponseType` | The answer payload shape, both as input (`answers` prop) and output (`onSubmit`'s first argument). |
131
+ | `SurveyQuestionType`, `SurveyQuestionTypeInfo`, `QuestionType` | A single question and its type-specific config; `QuestionType` is the discriminant `Question` (`src/survey/question.tsx`) switches on. |
132
+ | `SurveyBlockType`, `DependentBlockType`, `DisplayLogicType` | Navigation graph: one block per question (or per rating-branch category), its `dependent_blocks` (old-style branching) and/or `display_logic` (new-style show/hide rules) — see [Section 5](#5-state-data--invariants). |
133
+ | `OnSubmitType` | `(data: SurveyResponseType, callback: Function, answerType: AnswerStatus, action: ComponentActions) => void` — the exact `onSubmit` contract every host must implement. |
134
+ | `AnswerStatus` | `'PARTIAL' \| 'COMPLETE' \| 'PARTIAL_COMPLETE'` — status flag hosts branch on to decide whether to persist as final. |
135
+ | `SurveyProps`, `WidgetProps`, `QuestionPreviewProps` | Prop contracts for the three top-level components. |
136
+ | `CollectorType` and its nested `*InfoType`s | Read-only context the widget consumes (e.g. `web_in_app_channel_info.backdrop_color`) — this library never mutates or creates collectors. |
137
+
138
+ ### Internal-only building blocks (`INTERNAL`)
139
+
140
+ `useCardSurvey`, `useStandardSurvey`, everything in `src/survey/utils/logics.ts`
141
+ (`getNextLogicalBlock`, `fetchNextBlockIndex`, `canDisplayButton`, `canDisableButton`, the
142
+ validators, etc.), `Question` (`src/survey/question.tsx`), and every component under
143
+ `src/components/*` **not** listed in `src/components/index.tsx` (e.g. `Matrix`, `MatrixWidget`,
144
+ `FormField`, `ContactForm`, `FileUpload`, `ConsentQuestion`, `NumberQuestion`, `Textbox`,
145
+ `CommentBox`, `Icon`) are internal implementation detail. They can be freely refactored (renamed,
146
+ resplit, re-signatured) as long as the public components above keep behaving the same way.
147
+
148
+ ## 3. Dependencies & coupling
149
+
150
+ **Prism entity:** [`platforms/component/freemium-survey-components`](https://prism.freshtools.ai/catalog/platforms/component/freemium-survey-components)
151
+
152
+ ### Depends on
153
+
154
+ | Dependency | Kind | Why |
155
+ | --------------------------------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
156
+ | `react`, `react-dom` | Peer dependency (`package.json`: `>=16.0.2`) | Host must supply these — the library is externalized in the Rollup build (`external = /^react\|react-dom/`), never bundled. |
157
+ | `@blueprintjs/core`, `@blueprintjs/datetime2` | Direct dependency | Date/time picker primitives. |
158
+ | `@dnd-kit/*` | Direct dependency | Drag-and-drop for `RankOrder`. |
159
+ | `@radix-ui/react-*` | Direct dependency | Accessible primitives for checkbox/radio/select/slider. |
160
+ | `@pqina/*`, `filepond*`, `react-filepond` | Direct dependency | File-upload question UI + in-browser image editing. |
161
+ | `framer-motion` | Direct dependency | All block/question transition animations. |
162
+ | `moment-timezone` | Direct dependency | Date/time-zone parsing and comparison in `logics.ts` display-logic evaluation and the date pickers. |
163
+ | `dompurify` | Direct dependency | HTML sanitization — but only wired in 2 of the 20 `dangerouslySetInnerHTML` sites in `src` (see [Section 6](#6-failure-modes--sharp-edges)). |
164
+ | `react-phone-number-input`, `react-time-picker`, `react-transition-group`, `lodash.clonedeep` | Direct dependency | Phone input, time picker, transition groups, and the deep-clone used by `useSurvey`. |
165
+ | `lodash` | **Undeclared transitive dependency** | `import { isNil } from 'lodash'` in `src/components/binary/index.tsx`, `src/components/pickers/DateTimePicker.tsx`, `src/survey/meta-channel-preview/answer.tsx` — works today only because something else in the dependency tree pulls `lodash` in; not listed in `package.json`. |
166
+
167
+ ### Depended on by
168
+
169
+ All four are real, `package.json`-evidenced npm consumers in the same Prism system
170
+ (`platforms/surveyserv`) — each npm-installs `freemium-survey-components` and renders the survey
171
+ form for a different surface:
172
+
173
+ | Consumer | Prism entity | Reason |
174
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
175
+ | `survey-public-widget` | [`platforms/component/survey-public-widget`](https://prism.freshtools.ai/catalog/platforms/component/survey-public-widget) | Renders the survey collection form for the public web-in-app widget (`freemium-survey-components@^2.0.483`). |
176
+ | `survey-public-page` | [`platforms/component/survey-public-page`](https://prism.freshtools.ai/catalog/platforms/component/survey-public-page) | Renders the survey collection form for the standalone public survey page (`^2.0.480`). |
177
+ | `freshsurvey-web-admin-app` | [`platforms/component/freshsurvey-web-admin-app`](https://prism.freshtools.ai/catalog/platforms/component/freshsurvey-web-admin-app) | Renders survey/question previews inside the survey builder/admin UI (`^2.0.126`). |
178
+ | `survey-admin-microfrontend` | [`platforms/component/survey-admin-microfrontend`](https://prism.freshtools.ai/catalog/platforms/component/survey-admin-microfrontend) | Renders survey/question previews inside the admin micro-frontend (`^2.0.484`). |
179
+
180
+ Because every consumer pins a `^`-range version independently, **this repo cannot assume all
181
+ consumers are on the latest published version** — a breaking change to a `PUBLIC` export
182
+ (Section 2) can land weeks or months before every consumer upgrades. `freshsurvey-web-admin-app`
183
+ in particular is pinned three major-ish versions behind (`^2.0.126` vs. current `2.0.484`).
184
+
185
+ ### Hidden coupling
186
+
187
+ - **CSS custom properties as an implicit contract.** `setCSSVariablesIfPresent` (`src/utils.tsx`)
188
+ writes theme colors (from `survey.theme`) onto `document.documentElement` as `--fsc-*` CSS
189
+ variables (e.g. `--fsc-whatsapp-chat-background`, `--fsc-max-range-question-width`,
190
+ `--fsc-question-max-width`). Consumers that render this library alongside their own CSS must not
191
+ collide with the `--fsc-*` namespace, and anything that reads survey theming outside this library
192
+ (e.g. a host wrapper) is implicitly coupled to these exact variable names.
193
+ - **Global DOM IDs.** The rendered tree always uses fixed IDs: `#freemium-survey`,
194
+ `#freshworks-survey-widget`, `#surveyserv-widget-container`, `#surveyserv-widget-header`. Only one
195
+ instance of `Survey`/`WebInAppSurvey` can exist per page without ID collisions — this is an
196
+ unwritten single-instance-per-page assumption.
197
+ - **`localStorage`/`sessionStorage` as an implicit side channel.** `removeUploadedFile(localStorage)`
198
+ (called from `Survey`'s mount effect and from `resetSurvey`) clears file-upload keys from
199
+ `localStorage`; `getFreshSurveyDebug`/`appLogger` read debug flags from `localStorage`/
200
+ `sessionStorage` (e.g. `fsc:debug` — see `src/utils.tsx`). A host embedding multiple SurveyServ
201
+ widgets on one page shares this storage.
202
+ - **Peer-dependency version assumptions.** `package.json` declares `react`/`react-dom: >=16.0.2`,
203
+ but `package-lock.json` resolves and tests against React **17.0.2**, while `@types/react`/
204
+ `@types/react-dom` are pinned to **18.x**. The library has never been tested against React 16 or
205
+ 19 in CI (there is no CI test job at all — see [Section 6](#6-failure-modes--sharp-edges)).
206
+ `> [UNVERIFIED] Whether the library actually works correctly on React 18/19 concurrent features (automatic batching, etc.) has not been verified by any automated test — it is inferred only from the fact that no consumer has reported breakage.`
207
+ - **`window`/`document`/`matchMedia` guards are inconsistent.** Some effects guard for SSR
208
+ (`typeof window === 'undefined'`), others (e.g. `scrollToNextBlock` in `use-standard-survey.ts`)
209
+ assume `document` exists unconditionally. Consumers that server-render must render this library
210
+ client-only.
211
+
212
+ ## 4. Idiomatic vernacular
213
+
214
+ **Naming conventions, with real examples:**
215
+
216
+ - **`use*Survey` hooks own all state for one layout.** `useSurvey` (normalization, always run),
217
+ `useCardSurvey` (card + widget layouts), `useStandardSurvey` (standard layout). Copy
218
+ `src/survey/utils/use-card-survey.ts` as the template for "a hook that fully owns navigation state
219
+ for a layout."
220
+ - **`*Type` suffix for every domain type**, e.g. `SurveyType`, `CollectorType`, `AnswersType`,
221
+ `QuestionAnswerType` (`src/types.ts`). Prop-bag types for a component use `*Props`
222
+ (`SurveyProps`, `WidgetProps`, `CardQuestionFooterProps`).
223
+ - **`get*`/`is*`/`can*` prefixes in `logics.ts` signal pure, side-effect-free functions** that take
224
+ named-object args and return a value: `getNextLogicalBlock`, `getPivotQuestion`, `isBlockFinal`,
225
+ `isThankYouBlock`, `canDisplayButton`, `canDisableButton`, `canMoveToNextBlock`,
226
+ `canCommitDirtyValue`. When adding new branching logic, follow this pattern — a same-named `set*`
227
+ or component-coupled function in this file would be out of place.
228
+ - **`SurveyEndCard`, `PageNavigator`, `QuestionFooter` are re-implemented per layout**, not shared
229
+ components — `src/survey/card/index.tsx`, `src/survey/standard/index.tsx`, and
230
+ `src/survey/widget/survey.tsx` each define their own local versions with the same names but
231
+ different JSX/behavior. Do not assume "the same name across layout files" means "the same
232
+ component" — check the file.
233
+ - **`ss-*` / `svs-*` / `fsc-*` CSS class and variable prefixes** mark class names/CSS variables that
234
+ are part of the informal styling contract with hosts (e.g. `ss-question-container`,
235
+ `svs-icon`, `--fsc-question-max-width`). `freemium-survey-*` and `surveyserv-*` prefix the
236
+ top-level wrapper classes (`freemium-survey-components`, `surveyserv-widget-container`).
237
+
238
+ **Domain glossary** (as this codebase specifically uses the term — check `src/types.ts` and
239
+ `src/survey/utils/logics.ts` for the authoritative shape):
240
+
241
+ | Term | Meaning here |
242
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
243
+ | **Block** | One navigable step in the survey graph (`SurveyBlockType`). Usually maps 1:1 to a question (`block.question_name`), but a _rating-branch_ block instead has `category: SurveyBlockCategoryType[]` — one entry per pivot-answer range, each pointing at a different `question_name`. `BlockWithQuestionType` is a block with its resolved `question` attached; `renderedBlocks` is the ordered list of blocks actually shown to the user so far (as opposed to `blocks`, the full survey graph). |
244
+ | **Pivot question** | The rating question (NPS/CSAT/CES `RANGE`, or `BOOLEAN`) whose answer determines which _category_ branch of a downstream block is shown. Resolved by `getPivotQuestion`: first tries a question flagged `type_info.pivot_question`, then the first block's question, then the first `RANGE` question, then falls back to `questions[0]`. |
245
+ | **Dependent block / display logic** | Two overlapping branching mechanisms on `SurveyBlockType`/`SurveyQuestionType`: `dependent_blocks` (older, per-question `next_block` rules keyed by `choice_id`/`condition_type`/`operator`) and `display_logic` (newer, per-block/per-page `SHOW`/`HIDE` rule sets combined with `AND`/`OR`). Both are evaluated by the same `evaluateConditions` comparator in `logics.ts`. New branching features should extend `display_logic` rather than `dependent_blocks` unless the older behavior is specifically needed. |
246
+ | **Set** (`orderOfSet`, `branch_separate_page`) | A sub-grouping of blocks _within_ a page, used only in the standard layout when `survey.meta.branch_separate_page` is true — lets one page reveal blocks in waves (e.g. a rating question, then its follow-up, on the same page but staged). Tracked via `setOrderForRenderedBlocks` (`src/utils.tsx`) and `activeIndexOfSet`. |
247
+ | **Channel** | In `meta-channel-preview`, a chat delivery surface (`'whatsapp' \| 'instagram' \| 'facebook' \| 'chat'`) being previewed — unrelated to `CollectorChannelType` (`EMAIL`, `WEB_APP`, `SLACK`, …), which is the backend's delivery-channel enum for a `CollectorType`. Do not conflate the two "channel" concepts. |
248
+ | **Collector** | _Not_ rendered or created by this library — `CollectorType` is read-only context passed in (e.g. widget backdrop styling from `collector.channel_info.web_in_app_channel_info`). The collector lifecycle (scheduling, throttling, audience) lives in the backend. |
249
+ | **Commit / commitDirtyValue** | Whether an answer change should be treated as "final for this interaction" and trigger navigation evaluation now, vs. just updating local state (e.g. still typing). Threaded through `saveFormValues(block, value, commitDirtyValue)` and `canCommitDirtyValue` (auto-true for `AUTO_COMMIT_FIELD_TYPES` unless a comment box is attached). |
250
+ | **Widget** vs **Survey** vs **Channel preview** | Three distinct rendering _modes_ for the same question set: `Survey` (full-page/card form), `Widget`/`WebInAppSurvey` (floating embedded chat-style box), `ChannelPreview` (read-only chat transcript mockup). They share `Question` and most `components/*` widgets but have separate layout/state code. |
251
+
252
+ ## 5. State, data & invariants
253
+
254
+ **What's mutated, and who owns it:**
255
+
256
+ | State | Owner | Notes |
257
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
258
+ | `answers: AnswersType` (`{ formValues, others, commitDirtyValue?, moveToNextPage? }`) | `useCardSurvey` / `useStandardSurvey` (one instance per mounted `Survey`) | The single source of truth for in-progress answers. Mutated only via `setAnswers` inside the owning hook — never directly by a component. `formValues` maps `question.name -> QuestionAnswerType`; `others` maps `question.name -> "Others" free-text value`. |
259
+ | `renderedBlocks: BlockWithQuestionType[]` | Same hook | The navigation stack: which blocks have been shown, in order. Card/widget layouts show only the last entry (`activeIndexOfRenderedBlocks`); standard layout renders all of them (paged by `activePage`). |
260
+ | `pivot` (`{ question, answer }`, a `useRef`) | Same hook | Deliberately a ref, not state — read by branching logic without forcing a re-render on every pivot update. |
261
+ | Validation errors (`ValidatingQuestions`: `formQuestionErrors`, `dateQuestionErrors`, `textboxQuestionErrors`, `fileUploadErrors`, `contactFormErrors`) | Same hook, via `setErrors` passed down to `Question` | Populated by the `*Validator` functions in `logics.ts`, invoked from the hook's `isValidQuestion`, and also directly by `DatePicker`/`FileUpload` calling `setErrors` on blur/upload-error. |
262
+ | CSS custom properties on `document.documentElement` | `setCSSVariablesIfPresent` (`src/utils.tsx`), called from layout `useEffect`s keyed on `survey.theme` | Global, page-wide mutation — see [Section 3 → Hidden coupling](#3-dependencies--coupling). |
263
+ | `localStorage` file-upload keys | `removeUploadedFile` (`src/survey/utils/index.ts` re-export), called on mount and on `resetSurvey` | Shared across any survey instance on the same origin. |
264
+
265
+ **Invariants** (violate these and expect silent wrong-navigation or a blank render, not a thrown error):
266
+
267
+ - `blocks` passed into the navigation engine always excludes the block named `'TY'` and any
268
+ `is_deleted` block (`use-card-survey.ts`/`use-standard-survey.ts` filter this on every `setBlocks`
269
+ call) — the thank-you block is resolved separately via `getAppliedTYBlock`, never addressed by
270
+ index like normal blocks.
271
+ - Every block in `blocks` must resolve to a `question` via `getQuestionForBlock`; if it can't
272
+ (e.g. `pivotQn` undefined and the block is a rating-branch block), the calling component returns
273
+ `null` rather than throwing — `Survey`/`CardSurvey`/`StandardSurvey`/`Survey` (widget) all early-return
274
+ `null` when `!activeBlock || !activeBlock.question`.
275
+ - `answers.formValues[questionName] === null` means **explicitly skipped**, not "unanswered" —
276
+ `undefined` means unanswered. `isCurrentBlockSkipped` and the deleted-question filter in
277
+ `useSurvey` both depend on this distinction; do not conflate `null` and `undefined` when writing
278
+ new code that touches `formValues`.
279
+ - `BOOLEAN` and `RESOLUTION` question answers are always coerced to `Boolean(value)` before being
280
+ stored (`saveFormValues` in both hooks) — a falsy-but-defined value (e.g. `0`) is normalized to
281
+ `false`, not stored as-is.
282
+ - `props.survey`/`props.answers` are effectively **read once per identity change**: the hooks cache
283
+ derived state keyed on `survey.updated_at` (and re-derive `blocks`/`pages` only when it changes).
284
+ The README's documented "Rules" section states props are cached on mount and the component must be
285
+ remounted to pick up a new survey — in the current code this is more precisely "cached until
286
+ `survey.updated_at` changes," which is a materially different (weaker) guarantee than "remount
287
+ required." Treat the README's blanket "must remount" statement as historical/imprecise;
288
+ `> [UNVERIFIED] whether every prop (not just survey/answers) is safe to update without remounting has not been exhaustively checked.`
289
+
290
+ ## 6. Failure modes & sharp edges
291
+
292
+ - **Zero automated unit/behavioral test coverage.** `find src -iname '*.test.*' -o -iname '*.spec.*'`
293
+ returns 0 files, and `npm test` (`react-scripts test`) has nothing to run. The only automated
294
+ check is the Storybook test-runner's `innerHTML` snapshot per story (`.storybook/test-runner.ts` +
295
+ `src/test/__snapshots__/*.snap`) — it catches unintended markup/DOM changes, but **not** logic
296
+ regressions in `logics.ts` branching, validators, or anything not exercised by an existing story.
297
+ A change to `getNextLogicalBlock`/`evaluateConditions` can silently break real survey branching
298
+ and every snapshot will still pass. **Do not treat green Storybook snapshots as proof that
299
+ navigation/branching logic still works.**
300
+ - **No CI gate on pull requests.** `.github/workflows/` only has `security_verification.yml`
301
+ (Codepot scan), `pr-template-validation.yml`, `stale.yml`, `enhanced-pr-labeler.yml`, and
302
+ `automated-version-bump.yml` — none run `npm run lint`, `npm run build`, or `npm test` on a PR.
303
+ The only local enforcement is `.husky/pre-commit`, which runs lint + format + `npm install` + a
304
+ full `npm run build` on every commit and is bypassable with `git commit --no-verify`. A broken
305
+ Rollup build can be merged to `main` undetected until the next manual `npm run build` or publish.
306
+ - **`lodash` is an undeclared dependency.** `src/components/binary/index.tsx`,
307
+ `src/components/pickers/DateTimePicker.tsx`, and `src/survey/meta-channel-preview/answer.tsx`
308
+ `import { isNil } from 'lodash'`, but `lodash` is not in `package.json` — it currently resolves
309
+ only because something else in the dependency tree happens to pull it in. A future dependency bump
310
+ that drops that transitive `lodash` will break the build with no local signal until then.
311
+ - **Inconsistent HTML sanitization.** 20 `dangerouslySetInnerHTML` sites exist in `src`; only 2
312
+ (`src/components/link-preview/index.tsx`, `src/survey/meta-channel-preview/answer.tsx`) run
313
+ `DOMPurify.sanitize` first. Others — including `question.type_info.footer_text` and
314
+ `survey.footer_content` rendering in `src/survey/question.tsx` / `src/components/footer/index.tsx`
315
+ — render platform-supplied HTML unsanitized. `dompurify` is already a direct dependency; **don't
316
+ add a new `dangerouslySetInnerHTML` site without sanitizing it**, and treat existing unsanitized
317
+ sites as trusted only because the backend is assumed to control that content today.
318
+ - **`README.md`'s documented contracts have drifted from the code.** Two concrete, verified
319
+ mismatches: (1) the README's `Range` usage example passes a bare `type_info` object, but the
320
+ current `RangeProps` requires `question: SurveyQuestionType` (see [Section 2](#2-contracts--interfaces));
321
+ (2) the README states peer deps are `react`/`react-dom >= 17.0.2`, but `package.json` declares
322
+ `>=16.0.2`. **When README and code disagree, the code (`src/`, `package.json`) is authoritative** —
323
+ update the README rather than trusting it for a new integration.
324
+ - **Peer-dependency version drift is untested.** `package-lock.json` resolves React to `17.0.2` for
325
+ local dev/build, `@types/react`/`@types/react-dom` are pinned to `18.x`, and the declared peer
326
+ range is `>=16.0.2` with no upper bound — yet there is no CI test matrix across React versions.
327
+ A consumer on React 19 or a consumer still on React 16 is relying on behavior nobody has verified
328
+ automatically here.
329
+ - **`src/survey/utils/logics.ts` (2,793 lines) is the highest-risk file to change.** It mixes pure
330
+ branching/navigation rules with per-question-type validators in one module with no test coverage.
331
+ Small changes to `evaluateConditions`, `getNextBlockIndex`, or `canDisplayButton` can silently
332
+ change which button is shown/enabled or which block comes next for _every_ question type and
333
+ layout, because all three layouts (`card`, `standard`, `widget`) call into the same functions.
334
+ **Don't touch `logics.ts` without manually exercising all three layouts against a survey that
335
+ exercises both `dependent_blocks` and `display_logic` branching**, since there is no test suite to
336
+ catch a regression.
337
+ - **`src/components/icons/index.tsx` (2,653 lines) and `src/types.ts` (2,107 lines) are large,
338
+ frequently-touched files** — expect merge conflicts when multiple PRs add icons or types
339
+ concurrently; there's no per-domain split today.
340
+ - **Rollup output is a single inlined chunk per format** (`inlineDynamicImports: true`), currently
341
+ ~839 KB gzipped JS + ~42 KB gzipped CSS. There is no bundle-size regression check in CI — a large
342
+ new dependency (e.g. another date library, another icon set) silently increases every consumer's
343
+ bundle.
344
+ - **`src/mocks/gallery.ts` is ~30,600 lines of fixture data**, committed to the repo and shipped in
345
+ git history (though excluded from the npm package via `.npmignore`'s `src`/`mock` entries) — be
346
+ aware of this when doing repo-wide search/refactor tooling; it will dominate line-count-based
347
+ tooling output.
348
+ - **Single-instance-per-page DOM ID assumptions** (`#freemium-survey`, `#freshworks-survey-widget`,
349
+ etc. — see [Section 3](#3-dependencies--coupling)) mean two `Survey`/`WebInAppSurvey` instances
350
+ mounted simultaneously on the same page will collide on IDs; this has not been a supported
351
+ configuration.
352
+
353
+ ## 7. Change guidance
354
+
355
+ - **Adding a new question type** requires changes in lockstep across: `src/types.ts`
356
+ (`QuestionType` union + any new `type_info` fields), `src/survey/question.tsx` (new `switch` case
357
+ dispatching to a new/existing widget), a new component under `src/components/<name>/` (following
358
+ the `index.tsx` + `style.scss` + optional `types.ts`/`utils.ts` pattern — e.g. copy
359
+ `src/components/slider/` as a template for a simple typed-value widget), `src/constants.ts` if the
360
+ type needs to join `AUTO_COMMIT_FIELD_TYPES`/`SKIP_DISABLED_QUESTIONS`/`STRING_BASED_QUESTIONS`,
361
+ and `src/survey/utils/logics.ts` if the type needs bespoke validation (`isValidQuestion` switch in
362
+ both `use-card-survey.ts` and `use-standard-survey.ts`) or participates in `evaluateConditions`'s
363
+ `BETWEEN`/date-specific branches. Add a Storybook story under `src/stories/` (a `*Story.tsx` +
364
+ `*.stories.tsx` pair) so the new type gets snapshot coverage.
365
+ - **Changing branching/navigation behavior** (`logics.ts`) must be verified by hand across all three
366
+ layouts (`CardSurvey`, `StandardSurvey`, the widget `Survey` in `survey/widget/survey.tsx`) and
367
+ both branching mechanisms (`dependent_blocks` and `display_logic`) — there is no automated test to
368
+ catch a regression (see [Section 6](#6-failure-modes--sharp-edges)).
369
+ - **Changing a `PUBLIC` export's prop shape** (Section 2) is a breaking change for whichever of the
370
+ four dependent repos haven't upgraded yet (Section 3) — bump `minor`/`major` accordingly via the
371
+ version-bump workflow (see [Getting Started](../getting-started.md)) and call it out explicitly in
372
+ the PR's "Breaking Changes" section.
373
+ - **Running the existing coverage before/after a change:**
374
+ - Storybook visually + interactively: `npm run storybook` (build: `npm run build-storybook`).
375
+ - Snapshot tests: `npm run test-storybook` (uses `test-runner-jest.config.js`, drives
376
+ `.storybook/test-runner.ts`'s `postVisit` hook against every story in `src/**/*.stories.tsx`,
377
+ diffs against `src/test/__snapshots__/*.snap`). Requires Storybook to be built/served first per
378
+ `@storybook/test-runner`'s normal flow.
379
+ - Type/build check: `npm run build` (Rollup + `rollup-plugin-typescript2`) — the closest thing to
380
+ a compile-time regression check; run it before opening a PR since CI does not run it for you.
381
+ - `npm run lint` / `npm run format` — enforced locally via `.husky/pre-commit`, not in CI.
382
+
383
+ ## 8. Staleness triggers
384
+
385
+ This document should be treated as stale and re-verified against source if any of the following change:
386
+
387
+ - `src/index.ts`'s export list, or `src/components/index.tsx`'s re-export list (changes the `PUBLIC`
388
+ surface in [Section 2](#2-contracts--interfaces)).
389
+ - The `QuestionType` union in `src/types.ts`, or the `switch` in `src/survey/question.tsx`.
390
+ - The signatures of `getNextLogicalBlock`, `fetchNextBlockIndex`, `getNextBlockIndex`,
391
+ `evaluateConditions`, `canDisplayButton`, `canDisableButton` in `src/survey/utils/logics.ts`.
392
+ - `AUTO_COMMIT_FIELD_TYPES`, `SKIP_DISABLED_QUESTIONS`, `STRING_BASED_QUESTIONS` in `src/constants.ts`.
393
+ - `package.json`'s `peerDependencies`, `dependencies` (especially any `lodash` declaration change),
394
+ or the Rollup `external`/output config in `rollup.config.ts`.
395
+ - Any of the four dependent repos' `package.json` `freemium-survey-components` version pin
396
+ (changes the "Depended on by" version-skew note in [Section 3](#3-dependencies--coupling)) —
397
+ re-check with `grep freemium-survey-components package.json` in each sibling repo.
398
+ - Addition of any test job to `.github/workflows/` or any `*.test.*`/`*.spec.*` file under `src`
399
+ (invalidates the "zero test coverage" claims in [Section 6](#6-failure-modes--sharp-edges)).
400
+ - `.github/workflows/automated-version-bump.yml`'s trigger/branch/publish logic (see
401
+ [Getting Started](../getting-started.md)).
@@ -0,0 +1,110 @@
1
+ # Getting Started
2
+
3
+ ## Prerequisites
4
+
5
+ - Node.js 18 (matches `actions/setup-node@v4` `node-version: '18'` used in
6
+ `.github/workflows/automated-version-bump.yml`).
7
+ - npm (repo uses `package-lock.json`, not yarn/pnpm).
8
+ - Peer dependencies your consuming app must already provide: `react` and `react-dom`
9
+ (`package.json` declares `>=16.0.2`; the lockfile resolves/tests against `17.0.2` locally — see
10
+ [Deep Dive → Failure modes](architecture/deep-dive.md#6-failure-modes--sharp-edges) for the
11
+ version-drift caveat).
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install
17
+ ```
18
+
19
+ ## Local development (Storybook)
20
+
21
+ Storybook is the primary local dev loop for this component library — there is no standalone
22
+ "run the app" entry point.
23
+
24
+ ```sh
25
+ npm run storybook # starts Storybook dev server on port 6006
26
+ npm run build-storybook # produces a static Storybook build (storybook-static/)
27
+ ```
28
+
29
+ Stories live under `src/stories/**/*.stories.tsx` and `src/survey/**/*.stories.tsx`; see
30
+ [Architecture → Code Structure](architecture/code-structure.md) for the full map. Mock survey
31
+ data used across stories comes from `src/mocks/index.ts` (`MOCK_SURVEY`) and `src/mocks/gallery.ts`.
32
+
33
+ `npm start` (`react-scripts start`) also exists in `package.json` but this repo has no
34
+ CRA `public/index.html`-driven app to serve beyond Storybook's own tooling — Storybook is the
35
+ supported way to see components render.
36
+
37
+ ## Build (the npm package artifact)
38
+
39
+ ```sh
40
+ npm run build
41
+ ```
42
+
43
+ Runs `rollup -c rollup.config.ts`, producing `lib/index.cjs.js`, `lib/index.esm.js`,
44
+ `lib/bundle.css`, and `lib/types/` (see `rollup.config.ts` for the exact plugin chain). This is
45
+ also what `prepublishOnly` runs automatically before `npm publish`.
46
+
47
+ ## Test
48
+
49
+ ```sh
50
+ npm test # react-scripts test — currently has no *.test.*/*.spec.* files to run
51
+ npm run test-storybook # Storybook test-runner: snapshots each story's rendered innerHTML
52
+ ```
53
+
54
+ `npm run test-storybook` requires Storybook to be running/built first (standard
55
+ `@storybook/test-runner` flow: start `npm run storybook` or `npm run build-storybook` +
56
+ `http-server`, then run the test-runner against it). Snapshots are stored under
57
+ `src/test/__snapshots__/*.snap` with a custom resolver/serializer
58
+ (`src/test/snapshot-resolver.js`, `src/test/snapshot-serializer.js`) wired via
59
+ `test-runner-jest.config.js`. See
60
+ [Deep Dive → Failure modes](architecture/deep-dive.md#6-failure-modes--sharp-edges) for what this
61
+ test suite does and does not catch.
62
+
63
+ ## Lint & format
64
+
65
+ ```sh
66
+ npm run lint # eslint . --fix --quiet
67
+ npm run check # prettier --check .
68
+ npm run format # prettier --write .
69
+ ```
70
+
71
+ `.husky/pre-commit` runs lint + format + `npm install` + a full `npm run build` on every commit —
72
+ expect commits to take noticeably longer than the lint/format step alone; see
73
+ [Deep Dive → Failure modes](architecture/deep-dive.md#6-failure-modes--sharp-edges).
74
+
75
+ ## Chromatic (visual regression)
76
+
77
+ ```sh
78
+ npm run chromatic # publishes Storybook build to Chromatic
79
+ npm run chromatic-dry # dry run, no publish
80
+ ```
81
+
82
+ Configured via `chromatic.config.json`.
83
+
84
+ ## Versioning & npm publish flow
85
+
86
+ This library ships no running service — its "release" is an npm package publish, automated by
87
+ `.github/workflows/automated-version-bump.yml`:
88
+
89
+ 1. **Trigger a version bump** — manually via `workflow_dispatch` on that workflow, choosing
90
+ `version_type: patch | minor | major` (default `patch`).
91
+ 2. The workflow checks out `main`, runs `npm version <type> --no-git-tag-version` to bump
92
+ `package.json`/`package-lock.json`, commits that bump on a new `version-bump-<timestamp>`
93
+ branch, and opens a PR titled `chore(release): bump version to <version>` back into `main`,
94
+ auto-populated with the commit log since the last tag.
95
+ 3. **Merging that PR into `main`** triggers the workflow's `publish-after-merge` job (on
96
+ `push` to `main`): it detects the version-bump commit/branch pattern, runs `npm ci` +
97
+ `npm run build`, then `npm publish` (using `NPM_TOKEN`), creates and pushes a `v<version>` git
98
+ tag, deletes the now-merged `version-bump-*` branch, generates release notes from the commit
99
+ log, and creates a GitHub Release. It also posts a Slack notification if `SLACK_WEBHOOK_URL` is
100
+ configured (best-effort, `continue-on-error: true`).
101
+ 4. There is also a manual escape hatch in `package.json`: `npm run version-beta` (
102
+ `npm version prerelease --preid=beta`) + `npm run publish-beta` (`npm publish --tag beta`) for
103
+ publishing an unlisted beta build without going through the automated flow.
104
+
105
+ Every consumer pins a `^`-range on `freemium-survey-components` in its own `package.json` — a
106
+ version bump does not immediately propagate to `survey-public-widget`, `survey-public-page`,
107
+ `freshsurvey-web-admin-app`, or `survey-admin-microfrontend` until each of those repos bumps its
108
+ own dependency. See [Deep Dive → Dependencies & coupling](architecture/deep-dive.md#3-dependencies--coupling)
109
+ for the current version skew across consumers, and [Deep Dive → Change guidance](architecture/deep-dive.md#7-change-guidance)
110
+ for what warrants a `minor`/`major` bump vs. `patch`.
package/docs/index.md ADDED
@@ -0,0 +1,57 @@
1
+ # Freemium Survey Components
2
+
3
+ `freemium-survey-components` is the shared React/TypeScript UI component library that renders the
4
+ SurveyServ survey collection form from a survey definition object (`SurveyType`). It is published to
5
+ npm as `freemium-survey-components` and consumed in-process (not over the network) by every SurveyServ
6
+ frontend that needs to show a survey to an end user or preview it in an admin/builder context.
7
+
8
+ ## Purpose & boundary
9
+
10
+ This repo owns the **rendering** of a survey: turning a `SurveyType` + prior `SurveyResponseType`
11
+ into a navigable, animated question flow (`Survey`, `WebInAppSurvey`, `QuestionPreview`,
12
+ `ChannelPreview`), collecting per-question answers, running client-side display/skip logic
13
+ (`src/survey/utils/logics.ts`), running field-level validation, and calling back to the host
14
+ application via `onSubmit` with the accumulated answers. It ships every question-type widget
15
+ (text, matrix, range/NPS, file upload, contact form, date/time, slider, rank order, consent, etc.)
16
+ and the card/standard/widget/channel-preview layout shells around them.
17
+
18
+ **Not responsible for:**
19
+
20
+ - **Persisting responses or surveys.** The library never calls a network API; it only invokes the
21
+ `onSubmit` / `onFileUpload` callbacks the host passes in. Persistence, auth, and the survey CRUD
22
+ API live in the SurveyServ backend (`surveyserv` repo), not here.
23
+ - **Deciding which survey/collector to show, when, or to whom.** Targeting, scheduling, throttling
24
+ and channel delivery are collector/backend concerns (`CollectorType`, `ScheduleInfoType`,
25
+ `ThrottleInfoType` in `src/types.ts` are consumed as read-only data, never computed here).
26
+ `src/mocks/` only exists for Storybook and this repo's own manual dev loop.
27
+ - **Hosting/serving the survey to an end user.** There is no routing, no server, no page shell.
28
+ That belongs to the four consumer apps — see [Deep Dive → Dependencies & coupling](architecture/deep-dive.md#3-dependencies--coupling).
29
+ - **A network API of its own.** This is a UI component library; its only "contract" is its exported
30
+ components/props (see [Deep Dive → Contracts & interfaces](architecture/deep-dive.md#2-contracts--interfaces)).
31
+ - **Independent deployment.** There is no running service or environment for this repo — it is
32
+ built with Rollup and published to npm; see [Getting Started](getting-started.md) for the
33
+ publish/version-bump flow.
34
+
35
+ ## Tech & runtime
36
+
37
+ | Aspect | Value |
38
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | Language(s) | TypeScript (`^4.9.5`), SCSS |
40
+ | Framework(s) | React (component library), Rollup (bundler), Storybook 8 (dev/docs/visual testing), `@storybook/test-runner` + Jest (snapshot testing) |
41
+ | Runtime / version constraints | Peer deps: `react` / `react-dom` `>=16.0.2` per `package.json` (README states `>=17.0.2` — treat `package.json` as authoritative; see [Deep Dive → Failure modes](architecture/deep-dive.md#6-failure-modes--sharp-edges)). Node 18 is used in CI/publish workflows. Ships as CJS (`lib/index.cjs.js`) and ESM (`lib/index.esm.js`) bundles plus `lib/bundle.css`, with type declarations in `lib/types`. |
42
+
43
+ ## What's here
44
+
45
+ | Section | What you'll find |
46
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
47
+ | [Getting Started](getting-started.md) | Local install/build/run/test/Storybook workflow, peer dependencies, and the npm publish/version-bump flow |
48
+ | [Architecture → Code Structure](architecture/code-structure.md) | File-by-file map of `src/` grouped by responsibility |
49
+ | [Architecture → Deep Dive](architecture/deep-dive.md) | Control flow, the exported "API" surface, dependency/coupling graph, naming conventions & glossary, state/invariants, known sharp edges, and change guidance |
50
+ | [Operations](operations/monitoring.md) | Monitoring, alerting, SOP, support and debugging guidance for a library that has no runtime of its own |
51
+ | [ADRs](adrs/index.md) | Architecture decision records (currently none recorded) |
52
+
53
+ ## Ownership
54
+
55
+ Team: platforms/plat-maise-surveyserv
56
+
57
+ System: platforms/surveyserv
@@ -0,0 +1,19 @@
1
+ # Alerts Configuration
2
+
3
+ No alerting is configured for this repo, and none is expected to be — it has no runtime to alert
4
+ on (see [Monitoring → Overview](monitoring.md)).
5
+
6
+ The only automated notification wired up in this repo is a best-effort Slack post on successful
7
+ npm publish, defined in `.github/workflows/automated-version-bump.yml` (`Notify Slack channel`
8
+ step): it posts to `SLACK_WEBHOOK_URL` (if configured as a repo secret) announcing the package
9
+ name, version, and release URL after a version-bump PR is merged and published. This step runs
10
+ with `continue-on-error: true` — a missing webhook or a failed post does not fail the workflow and
11
+ nobody is alerted if it silently fails.
12
+
13
+ If GitHub Actions failure notifications matter to this team (e.g. a failed `publish-after-merge`
14
+ job), they come from GitHub's own workflow-failure notification settings for the repo/org, not from
15
+ anything configured inside this codebase.
16
+
17
+ > [UNVERIFIED] Whether org-level GitHub Actions failure notifications (email/Slack) are configured
18
+ > for this repo outside of the workflow file itself was not checked — that would live in GitHub
19
+ > org/repo notification settings, not in source control.
@@ -0,0 +1,73 @@
1
+ # How to Debug
2
+
3
+ ## 1. Enable the library's built-in debug logging
4
+
5
+ `src/utils.tsx`'s `appLogger` gates every internal `console.log` call behind a `localStorage` flag
6
+ read by `getFreshSurveyDebug` (`src/constants.ts`: `FRESH_SURVEY_DEBUG = 'fsc:debug'`). In the
7
+ browser console, on the page rendering the survey:
8
+
9
+ ```js
10
+ localStorage.setItem('fsc:debug', JSON.stringify({ log: true }));
11
+ // then reload the page
12
+ ```
13
+
14
+ This turns on `appLogger('Survey', {...})`, `appLogger('Card', {...})`,
15
+ `appLogger('Standard', {...})`, `appLogger('Widget', {...})`, and per-`useEffect` logging inside
16
+ `useCardSurvey`/`useStandardSurvey` — each call logs the component name and the full current
17
+ props/state (survey, answers, active block/index, rendered blocks). This is the fastest way to see
18
+ exactly what the navigation engine believes the current state is.
19
+
20
+ `src/types.ts`'s `FreshSurveyDebugType` defines sibling debug flags used by other SurveyServ
21
+ frontends under the same `localStorage` mechanism (`fwa:debug` for the admin app, `mfe:debug` for
22
+ the microfrontend, `spw:debug` for the public widget, `spp:debug` for the public page, `fui:debug`
23
+ for `freemium-ui`) — if you're debugging an issue that spans this library and its host, check
24
+ whether the host has its own equivalent flag.
25
+
26
+ > **Security note:** this logs the *full* current props/state, including `answers.formValues` —
27
+ > real survey respondent data (which may include names, emails, free-text answers, or other PII).
28
+ > Only enable `fsc:debug` in non-production environments. If you must reproduce against production
29
+ > data, redact identifying fields before sharing the console output, and clear the `fsc:debug` key
30
+ > from `localStorage` afterward (it persists across page reloads and sessions until removed).
31
+
32
+ ## 2. Reproduce in Storybook first
33
+
34
+ Before debugging inside a consumer app, try to reproduce with the same survey shape in Storybook
35
+ (`npm run storybook`) using a story under `src/stories/` or `src/survey/*.stories.tsx`, adapting
36
+ `src/mocks/index.ts`'s `MOCK_SURVEY` to match the reported shape. If it reproduces in Storybook,
37
+ the bug is in this repo; if it doesn't, the bug is likely in the consumer's integration (see
38
+ [Support](support.md)).
39
+
40
+ ## 3. Isolate: navigation/branching vs. rendering vs. validation
41
+
42
+ - **Wrong question shown next / survey ends too early or too late** → the bug is almost certainly
43
+ in `src/survey/utils/logics.ts` (`getNextLogicalBlock`, `fetchNextBlockIndex`,
44
+ `getNextBlockIndex`, `evaluateConditions`). Log the survey's `blocks`/`dependent_blocks`/
45
+ `display_logic` and the current `answers.formValues` (via `fsc:debug`) and trace which branch of
46
+ `evaluateConditions` should fire for the given operator/answer.
47
+ - **A question renders with the wrong widget or missing data** → check the `switch` in
48
+ `src/survey/question.tsx` for that `question_type`, and the corresponding component under
49
+ `src/components/`.
50
+ - **A "Next"/"Submit"/"Skip" button is missing, disabled, or in the wrong state** → check
51
+ `canDisplayButton`/`canDisableButton` (`logics.ts`) for that `ActionButtonTypes` case — these
52
+ functions are shared across all three layouts, so also check whether `surveyStyle`/`isWidget`/
53
+ `isMobile` branches inside the specific case are gating on the wrong condition for your layout.
54
+ - **A field won't let the user proceed / shows a validation error incorrectly** → check the
55
+ relevant `*Validator` function in `logics.ts` (`dateValidator`, `formValidator`,
56
+ `textboxValidator`, `contactformValidator`, `fileUploadQnValidator`) and the corresponding
57
+ `ValidatingQuestions` error bucket.
58
+
59
+ ## 4. Check for the known sharp edges first
60
+
61
+ Before deep-diving, rule out the documented gotchas in
62
+ [Deep Dive → Failure modes & sharp edges](../architecture/deep-dive.md#6-failure-modes--sharp-edges) —
63
+ in particular: `null` vs `undefined` in `answers.formValues` (skip vs. unanswered), the
64
+ `survey.updated_at`-keyed caching in `useSurvey`, and README examples that no longer match the
65
+ current prop shapes.
66
+
67
+ ## 5. There is no server-side log to check
68
+
69
+ Because this library never makes a network call itself, there is no backend log, trace, or APM
70
+ entry to correlate against — everything observable happens in the consumer's browser. If the
71
+ report includes a network-layer symptom (e.g. `onFileUpload` failing), the failure is inside the
72
+ callback the **host** implemented, not inside this library's call site — confirm the host's
73
+ implementation of `onFileUpload`/`onSubmit` before assuming a library bug.
@@ -0,0 +1,26 @@
1
+ # Monitoring — Overview
2
+
3
+ `freemium-survey-components` is a build-time-consumed UI component library, not a running
4
+ service. There is no deployed process, endpoint, container, or pod for this repo to monitor — it
5
+ has no uptime, no request rate, no dashboards of its own.
6
+
7
+ What actually needs watching lives elsewhere:
8
+
9
+ - **Runtime errors and rendering issues** surface inside the four consuming applications
10
+ (`survey-public-widget`, `survey-public-page`, `freshsurvey-web-admin-app`,
11
+ `survey-admin-microfrontend`) — their own frontend error monitoring / RUM tooling is where a bug
12
+ in this library would actually be observed in production. See
13
+ [How to Debug](how-to-debug.md) for how to correlate an issue back to this repo.
14
+ - **Build/publish health** is observable via GitHub Actions runs on
15
+ `.github/workflows/automated-version-bump.yml` (version-bump PR creation and the
16
+ publish-after-merge job) — a failed `npm publish` or `npm run build` step there is the closest
17
+ thing this repo has to an "incident."
18
+ - **Bundle size** (`lib/index.esm.js` / `lib/index.cjs.js` ~839 KB gzipped JS, `lib/bundle.css`
19
+ ~42 KB gzipped as of this writing) is not tracked automatically in CI today — see
20
+ [Deep Dive → Failure modes](../architecture/deep-dive.md#6-failure-modes--sharp-edges). If this
21
+ is added in the future, this page should be updated to describe it.
22
+ - **Storybook/Chromatic** (`npm run chromatic`) provides visual-regression signal at PR time, not
23
+ runtime monitoring — see [Getting Started](../getting-started.md).
24
+
25
+ There is no APM, log aggregation, or metrics pipeline configured in this repo, and none is
26
+ expected for a library with this shape.
@@ -0,0 +1,22 @@
1
+ # Recent Issues
2
+
3
+ This page tracks recent notable fixes merged to this repo, as a quick "have we seen this before"
4
+ reference. Source: `git log` on `main`. Update this list as new notable fixes land — do not let it
5
+ grow unbounded; keep roughly the last 10–15 entries and drop the oldest.
6
+
7
+ | Ticket | Summary |
8
+ | ------------------------ | --------------------------------------------------------------------------------------------------- |
9
+ | FSURVEY-8673 | Added `enableAnalytics` flag to `ProductConfigType`. |
10
+ | FSURVEY-8628 | Renamed the thank-you header CSS class to `fsc-thankyou-footer`. |
11
+ | FSURVEY-8568 | Fixed the `RESOLUTION` question and thank-you card rendering together and ignoring other questions. |
12
+ | FSURVEY-8425 | Fixed Facebook-channel-preview button alignment and a NUMBER-variant issue with a minimum of 0. |
13
+ | FSURVEY-8425 (follow-up) | Fixed MCQ rendering issues in the survey. |
14
+ | FSURVEY-8546 | Hid the widget footer when it would render no visible content. |
15
+ | MISC | Onboarded this repo to the Prism software catalog (`catalog-info.yaml`, `techdocs-ref: dir:.`). |
16
+
17
+ For anything not covered here, search closed PRs/issues on `freshdesk/freemium-survey-components`
18
+ by `FSURVEY-` ticket prefix, or search Confluence space `SP` for
19
+ "freemium-survey-components" (engineering briefs, roadmap docs) — see
20
+ [Deep Dive → Failure modes & sharp edges](../architecture/deep-dive.md#6-failure-modes--sharp-edges)
21
+ for the known, still-open structural issues (no test coverage, no PR CI gate, undeclared `lodash`
22
+ dependency, inconsistent HTML sanitization) that are more "known risk" than "recent issue."
@@ -0,0 +1,44 @@
1
+ # SOP (Standard Operating Procedures)
2
+
3
+ There is no running service to operate, restart, scale, or fail over — see
4
+ [Monitoring → Overview](monitoring.md). The procedures below cover the two situations that
5
+ actually recur for a published component library: cutting a release, and reacting to a broken
6
+ release.
7
+
8
+ ## Cutting a release
9
+
10
+ Follow [Getting Started → Versioning & npm publish flow](../getting-started.md#versioning--npm-publish-flow):
11
+ trigger `automated-version-bump.yml` via `workflow_dispatch`, choose the version bump type, review
12
+ and merge the generated PR, then confirm the `publish-after-merge` job succeeded (npm publish + git
13
+ tag + GitHub Release).
14
+
15
+ Before triggering a release:
16
+
17
+ - Run `npm run build` locally and confirm it succeeds (CI does not run this for you on the
18
+ version-bump PR — see [Deep Dive → Failure modes](../architecture/deep-dive.md#6-failure-modes--sharp-edges)).
19
+ - Run `npm run test-storybook` against a running/built Storybook and confirm no unexpected
20
+ snapshot diffs.
21
+ - If the change affects a `PUBLIC` export's shape (see
22
+ [Deep Dive → Contracts & interfaces](../architecture/deep-dive.md#2-contracts--interfaces)),
23
+ choose `minor` or `major` accordingly, not `patch`, and note the breaking change in the PR.
24
+
25
+ ## Reacting to a broken release (bad version published to npm)
26
+
27
+ 1. **Do not attempt to unpublish** — npm restricts unpublishing versions with existing downloads;
28
+ treat forward-fixing as the default path.
29
+ 2. Trigger `automated-version-bump.yml` again with `version_type: patch` for a fix once the
30
+ regression is corrected on `main`.
31
+ 3. Notify the four consuming repos (`survey-public-widget`, `survey-public-page`,
32
+ `freshsurvey-web-admin-app`, `survey-admin-microfrontend`) if any of them auto-upgrade or are
33
+ about to release against the bad version — see
34
+ [Deep Dive → Dependencies & coupling](../architecture/deep-dive.md#3-dependencies--coupling)
35
+ for why version skew across consumers means impact isn't uniform or immediate.
36
+ 4. If the break is severe enough that consumers must pin away from it, communicate the last-known-good
37
+ version explicitly (e.g. in the incident thread / PR) since there is no automated deprecation
38
+ tooling wired into this repo's publish workflow.
39
+
40
+ ## Rolling back a merged-but-bad PR on `main`
41
+
42
+ Standard git revert-and-release: revert the offending commit(s) on `main`, then follow "Cutting a
43
+ release" above for a `patch` release containing the revert. There is no separate rollback
44
+ mechanism for a published npm package beyond publishing a new (higher) version.
@@ -0,0 +1,39 @@
1
+ # Support — Overview
2
+
3
+ **Owning team:** `platforms/plat-maise-surveyserv` (system: `platforms/surveyserv`).
4
+
5
+ Because this library has no runtime of its own, "support" for it means one of two things:
6
+
7
+ 1. **A consumer app hits a bug that traces back to this library** (rendering, navigation/branching,
8
+ validation, or a question-type widget behaving incorrectly). Start with
9
+ [How to Debug](how-to-debug.md) to confirm the bug is actually in this repo and not in how a
10
+ consumer is calling `Survey`/`WebInAppSurvey`/`ChannelPreview`, then file/fix it here.
11
+ 2. **A consumer needs a change to the public contract** (new question type, new prop, new exported
12
+ component) — see [Deep Dive → Change guidance](../architecture/deep-dive.md#7-change-guidance)
13
+ for what has to change together, and [Deep Dive → Contracts & interfaces](../architecture/deep-dive.md#2-contracts--interfaces)
14
+ for what's safe to change without a version bump vs. what's a breaking change for the four
15
+ dependent repos.
16
+
17
+ ## Where to raise things
18
+
19
+ - **Bugs / feature requests against this repo:** GitHub issues on
20
+ `freshdesk/freemium-survey-components` (see `bugs.url` in `package.json`), or a PR directly using
21
+ this repo's `.github/pull_request_template.md`.
22
+ - **"Is this library or my integration?"** — reproduce against a Storybook story first
23
+ (`npm run storybook`); if the same survey/question renders correctly in Storybook with the same
24
+ props but not in your app, the issue is very likely in how the consumer is calling the component
25
+ (prop shape, missing `onSubmit`/`callback()` invocation, CSS variable collision — see
26
+ [Deep Dive → Dependencies & coupling → Hidden coupling](../architecture/deep-dive.md#3-dependencies--coupling)),
27
+ not in this library.
28
+ - **Freshrelease tracking:** ticket IDs referenced in commit/PR titles in this repo follow the
29
+ `FSURVEY-xxxx` pattern (see recent `git log` history and `.github/pull_request_template.md`'s
30
+ Ticket Reference section).
31
+
32
+ ## What good bug reports include
33
+
34
+ - The exact `SurveyType` (or a minimal reproducing subset) and `answers` payload passed in.
35
+ - Which top-level component was used (`Survey` vs `WebInAppSurvey` vs `ChannelPreview`) and
36
+ `surveyStyle`/`surveyType`.
37
+ - Console output with `fsc:debug` logging enabled — see [How to Debug](how-to-debug.md).
38
+ - The installed `freemium-survey-components` version (`npm ls freemium-survey-components` in the
39
+ consumer repo).
package/lib/bundle.css CHANGED
@@ -3283,7 +3283,7 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
3283
3283
  .surveyserv-widget-container .question-container.thankyou .text,
3284
3284
  .freemium-survey-components .question-container.thankyou .text {
3285
3285
  text-align: center;
3286
- white-space: pre-wrap;
3286
+ white-space: normal;
3287
3287
  font-weight: 500;
3288
3288
  font-size: var(--message-font-size, var(--fsc-font-size));
3289
3289
  word-break: break-word;
@@ -1494,6 +1494,7 @@ export type ProductConfigType = {
1494
1494
  sfsQuestionTypes?: QuestionType[];
1495
1495
  disableShowLabelInBooleanQuestion?: boolean;
1496
1496
  viewVersioningAtNavBar?: boolean;
1497
+ enableAnalytics?: boolean;
1497
1498
  };
1498
1499
  export type TriggerSurveyResponseType = {
1499
1500
  trigger_id?: string | null;
package/mkdocs.yml ADDED
@@ -0,0 +1,38 @@
1
+ site_name: 'Freemium Survey Components'
2
+ site_description: 'Functionality and Architecture Documentation for Freemium Survey Components'
3
+
4
+ nav:
5
+ - Home: index.md
6
+ - Getting Started: getting-started.md
7
+ - Architecture:
8
+ - Code Structure: architecture/code-structure.md
9
+ - Deep Dive: architecture/deep-dive.md
10
+ - Operations:
11
+ - Monitoring:
12
+ - Overview: operations/monitoring.md
13
+ - Alerts configuration: operations/alerts-configuration.md
14
+ - SOP: operations/sop.md
15
+ - Support:
16
+ - Overview: operations/support.md
17
+ - How to Debug: operations/how-to-debug.md
18
+ - Recent Issues: operations/recent-issues.md
19
+ - ADRs:
20
+ - Overview: adrs/index.md
21
+
22
+ plugins:
23
+ - techdocs-core
24
+ - mermaid2
25
+
26
+ markdown_extensions:
27
+ - admonition
28
+ - pymdownx.details
29
+ - pymdownx.superfences:
30
+ custom_fences:
31
+ - name: mermaid
32
+ class: mermaid
33
+ format: !!python/name:pymdownx.superfences.fence_code_format
34
+ - toc:
35
+ permalink: true
36
+
37
+ extra:
38
+ version: v1alpha1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freemium-survey-components",
3
- "version": "2.0.483",
3
+ "version": "2.0.485",
4
4
  "description": "React Survey Ui Components",
5
5
  "main": "lib/index.cjs.js",
6
6
  "module": "lib/index.esm.js",