Skip to content

feat: start surveys with the identified contact and close all-skipped surveys cleanly - #170

Merged
boehlerlukas merged 2 commits into
masterfrom
feat/survey-skip-known
Oct 8, 2026
Merged

boehlerlukas merged 2 commits into
masterfrom
feat/survey-skip-known

Conversation

@boehlerlukas

@boehlerlukas boehlerlukas commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Surveys 2.0: skip contact questions when the user is known (JS SDK)

Part of the "skip contact questions when the user is known" feature:

Deploy order: Server first, then Messenger-App and Frontend (see Server#2079). This PR doesn't depend on that order, because it only changes how the SDK hosts a survey. It needs a JS SDK release to take effect. The feature itself works without this PR, but without it the two problems below remain. No version bump or build output here: build/ and published/ are left for the release.

What the messenger needs from the host

  • Identity: no change needed. session-update sends the whole session (GleapSession.getSession()), so userId, email, name and phone from the server's getPublicSessionData reach the messenger. It is sent again after every session answer (validateSession → notifySessionReady → sendSessionUpdate), so after identify and updateContact too. After clearIdentity the SDK sends session-cleared and then the new guest session.
  • Timing (fixed): the messenger reads the contact once, when the survey starts. Calling Gleap.identify(...) and then Gleap.showSurvey(...) straight away, or an outbound trigger firing while identify is in flight, started the survey as the guest. The guest was asked for their email, and then the identified session-update (new gleapId) reset the survey in the messenger while it was open. Now showSurvey waits until no identify / updateContact is in flight, at most 5 s. Every path goes through it: manual calls, outbound triggers, gleap_survey links, banners, modals and survey pages. With nothing in flight it still starts synchronously, as before.
  • All questions skipped (fixed): the messenger sends survey-closed (reason: "nothing_to_ask") and then close-widget. On a cold frame this can arrive before the SDK's "open 300 ms after the first ping" timer fires. The timer then opened the frame anyway, leaving an empty card stuck open (isOpened() === true). The timer is now cancelled by hideWidget. A frame that was never shown hides without the close animation and without a close event, because no open event fired either.

Changes

  • GleapSession: trackContactUpdate counts identify / partial-update requests until they settle. onContactSettled(cb, 5000) runs cb once none is pending.
  • Gleap.showSurvey: starts through onContactSettled.
  • GleapFrameManager: the ping-open timer is kept in openAfterPingTimeout and cleared in hideWidget / destroy. hideWidget doesn't animate or fire close when the widget never opened.

Small side effect: Gleap.close() on an already closed widget no longer fires a close event.

Verification

  • npm test: 521 passed. npm run build: compiles (the existing bundle-size warnings only).
  • End to end in headless Chrome (Playwright), using this SDK build plus the Messenger PR's production build, with every network call mocked (no real hosts):
Scenario Before (master) After
Identified contact, contact-only card survey, cold frame, fast definition survey-closed, close, then open 300 ms later: an empty white card stays on screen, isOpened() true survey-closed, frame never visible, no open/close, isOpened() false
Same, full screen, cold frame (same timer path) never visible
Warm frame (widget opened and closed before), card n/a stays in the invisible measuring state, open → survey-closed → close, hidden
Warm frame, full screen n/a full-screen loading view for the definition fetch (~400 ms mocked), then the close animation, hidden. This is inherent: the host can't know before the messenger has the definition
identify() + showSurvey() at once (identify answer delayed 1.2 s) start-survey goes out with userId=null, the email question is shown, then the identified session-update resets it start-survey goes out after session-update userId=user_42, nothing_to_ask, closed
Contact + NPS survey, identified n/a first step viewed is the NPS (c_nps), the email and name are skipped
clearIdentity() + showSurvey() n/a guest session, the email question is asked
Inline survey page (format: 'page'), contact-only n/a shows the ending ("Thanks!"), no response saved

Follow-up: a reused frame waits for the new survey's height (9a66bd9)

Messenger-App#240 69a4f8b holds every in-app survey back: nothing is rendered and no height-update is sent until the definition is in and something will be asked. In a frame that had already measured an earlier survey card, setAppMode skipped the wait (surveyAwaitingHeight was false), so the next card was shown at once with the old height. An all-skipped survey then flashed an empty card before closing. Every new card or page survey start now resets frameHeight and waits for its own first height-update, or the existing 1.2 s fallback, the same as a frame that has never shown a survey.

Verified with the same harness against a build of Messenger #240 at 69a4f8b:

Scenario Before After
Normal card, then Gleap.close(), then an all-skipped card in the same frame visible for about 400 ms, then the close animation stays --measuring (invisible), then hidden
All-skipped card never visible never visible
All-skipped card, frame reused after a widget open never visible never visible
identify + showSurvey race n/a nothing_to_ask, never visible
Normal card / full screen (contact + NPS) n/a shown, first step is the NPS
Slow definition (2.5 s), card n/a revealed at the 1.2 s fallback with the messenger's skeleton (1.0 s), content at about 3.5 s
All-skipped full screen n/a nothing is painted, because the messenger renders nothing in the transparent full-screen container. The container is still open for about 100 ms and catches clicks

npm test: 521 pass. npm run build: compiles, and its output was reverted.

🤖 Generated with Claude Code

boehlerlukas and others added 2 commits October 8, 2026 14:29
… surveys cleanly

The messenger now skips survey contact questions an identified contact already
answered (Messenger-App#240) and, when nothing is left to ask, closes the
survey (survey-closed nothing_to_ask + close-widget) before it is shown.

- showSurvey (manual, outbound triggers, links, survey pages) waits for an
  identify / updateContact in flight (at most 5 s), so the messenger reads the
  new contact when the survey starts.
- A close-widget before the first-ping open no longer gets reopened 300 ms
  later as an empty, stuck "open" frame. A frame that was never shown hides
  without the close animation and without a close event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A card or page survey started in a frame that already measured an earlier
survey card was revealed at once with the old height, so a survey with
nothing to ask (the messenger now reports no height for it) flashed an empty
card before it closed. Each new survey start now resets the height and waits
for the survey's first height-update, or the 1.2 s fallback, like a cold
frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boehlerlukas
boehlerlukas merged commit 45c0f95 into master Oct 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant