feat: add PostHog custom survey modal on second app launch#4604
feat: add PostHog custom survey modal on second app launch#4604devin-ai-integration[bot] wants to merge 7 commits intomainfrom
Conversation
- Add AppOpenCount and SurveyDismissed store keys in Rust backend - Add increment_app_open_count, get/set_survey_dismissed Tauri commands - Create SurveyModal component with 4 select/multi-select questions - Show survey on second app open, capture 'survey sent' PostHog event - Questions: discovery source, why Char, role, current note-taking Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Deploy Preview for hyprnote canceled.
|
✅ Deploy Preview for hyprnote-storybook canceled.
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
✅ Deploy Preview for char-cli-web canceled.
|
…ommands) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| if (hasChecked.current || !isTauri()) { | ||
| return; | ||
| } | ||
| hasChecked.current = true; |
There was a problem hiding this comment.
🔴 SurveyModal missing window label guard causes count inflation and survey in wrong window
The SurveyModal is rendered by EventListeners (apps/desktop/src/services/event-listeners.tsx:149), which is mounted at the root level for ALL Tauri windows (apps/desktop/src/main.tsx:111), including the tiny 120×36px control window. The useEffect at line 162 only checks !isTauri() but does not check getCurrentWebviewWindowLabel() === "main", unlike the sibling hooks useUpdaterEvents (apps/desktop/src/services/event-listeners.tsx:23) and useNotificationEvents (apps/desktop/src/services/event-listeners.tsx:75) which both guard against non-main windows. This means when the control window opens, incrementAppOpenCount is called again (inflating the counter and triggering the survey earlier than intended), and the survey dialog could render inside the control window's tiny viewport.
| if (hasChecked.current || !isTauri()) { | |
| return; | |
| } | |
| hasChecked.current = true; | |
| if (hasChecked.current || !isTauri() || getCurrentWebviewWindowLabel() !== "main") { | |
| return; | |
| } | |
| hasChecked.current = true; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| SURVEY_QUESTIONS.forEach((q, i) => { | ||
| const answer = responses[q.id] ?? []; | ||
| const key = i === 0 ? "$survey_response" : `$survey_response_${i}`; | ||
| payload[key] = q.multiSelect ? answer : (answer[0] ?? ""); | ||
| }); |
There was a problem hiding this comment.
🚩 Survey response keys follow PostHog convention but multiSelect sends arrays
The analytics payload construction at apps/desktop/src/survey/survey-modal.tsx:214-218 uses $survey_response for the first question and $survey_response_N for subsequent ones, matching PostHog's survey response format. For multi-select questions, the value is an array of strings rather than a single string. Verify that the analytics backend (PostHog or equivalent) correctly handles array values for $survey_response keys, as some integrations expect only scalar values.
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Summary
Adds a 4-question onboarding survey that appears as a modal dialog on the user's second app launch (after a full quit). Responses are captured as PostHog custom survey events via the existing analytics plugin.
Backend (Rust): New persisted store keys (
AppOpenCount,SurveyDismissed) with Tauri commands to get/set/increment the open count and track whether the survey was dismissed/submitted.Frontend (React):
SurveyModalcomponent rendered insideEventListeners. On mount, it increments the app open count and shows the dialog when count is ≥ 2 and the survey hasn't been dismissed. The modal steps through 4 questions (select/multi-select), then fires a"survey sent"event with PostHog's$survey_id/$survey_responseproperty convention. A window label guard ensures the survey only runs in the"main"window (not the control window).Questions:
Devtools: New
SurveyCardin the devtools sidebar with buttons to inspect current state (open count, dismissed flag), reset both values, and force-open the survey modal for testing. TheSurveyModalaccepts optionalforceOpenandonCloseprops to support this.Updates since last revision
main— kept both survey commands andchar_v1p1_previewcommandsgetCurrentWebviewWindowLabel() !== "main"guard to prevent the survey from triggering (and inflating the open count) in the control windowReview & Testing Checklist for Human
SURVEY_IDis set to"onboarding_survey_v1"— you'll need to create a matching API-mode survey in the PostHog dashboard (or replace with the actual UUID) for responses to appear correctly in the PostHog survey UI.tauri.gen.ts). Runcargo test export_typesinapps/desktop/src-taurito regenerate and confirm the manual additions match specta's output.$survey_responsekeys correctly.analyticsCommands.eventpayload cast: The PostHog event payload is built asRecord<string, unknown>then cast viaas. Verify the analytics plugin actually accepts arbitrary properties, or the extra$survey_response_*keys may be silently dropped."survey sent"event appears in PostHog. Relaunch a third time and confirm the survey does not reappear. Also test dismissing (clicking X / closing) and confirm"survey dismissed"fires and the modal does not return.Notes
$survey_responsefor Q1 and$survey_response_Nfor subsequent questions. Multi-select answers are sent as arrays; single-select as strings.>= 2(not strict equality) combined with theSurveyDismissedflag, so if something goes wrong on the second launch the user will still see the survey on subsequent launches.Link to Devin session: https://app.devin.ai/sessions/3f4f1d83f2dd40a5aa6d39fd8b0bbe9f
Requested by: @ComputelessComputer