Skip to content

feat(analytics): implement Google Analytics#1533

Draft
graycyrus wants to merge 4 commits into
tinyhumansai:mainfrom
graycyrus:feat/google-analytics
Draft

feat(analytics): implement Google Analytics#1533
graycyrus wants to merge 4 commits into
tinyhumansai:mainfrom
graycyrus:feat/google-analytics

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented May 12, 2026

Summary

  • Add GA4 via react-ga4 with triple-gate: IS_DEV, GA_MEASUREMENT_ID env var, and analyticsEnabled user consent toggle
  • Track route changes automatically via AppShell useLocation() effect
  • Fire privacy-safe events: app_open, onboarding lifecycle (onboarding_start, onboarding_step_complete, onboarding_complete), account_connect_start/success, chat_message_sent, skill_install/skill_uninstall
  • All events validated against an allowlist — no PII, message content, credentials, or sensitive data sent to GA
  • Update privacy disclosure text in PrivacyPanel and whatLeavesItems to accurately reflect analytics scope
  • Update capability catalog in about_app/catalog.rs
  • 11 new unit tests covering init, consent gating, pageview, events, and allowlist enforcement

Configuration

Set VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX in app/.env.local to enable. Leave blank or omit to disable. GA is always disabled in dev mode (IS_DEV=true).

Privacy

  • Consent gated by existing analytics toggle in Settings > Privacy & Security
  • Event names validated against a strict allowlist; unrecognized names are dropped with a warning
  • No user IDs, message content, transcripts, credentials, or PII ever sent
  • Ad personalization signals disabled unconditionally
  • Page views use sanitized pathname only (no query strings, no local file paths)

Test plan

  • pnpm typecheck — clean
  • pnpm lint — 0 errors (39 pre-existing warnings)
  • pnpm format:check — clean
  • pnpm build — success
  • pnpm test:unit — 2075 passed, 0 failed
  • cargo check (core + Tauri shell) — clean
  • Manual: set measurement ID, launch app, verify events in GA DebugView (post-merge)
  • Manual: toggle analytics off in Settings, verify events stop (post-merge)

Closes #1479

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated Google Analytics 4 for anonymous usage tracking, capturing page views and feature engagement metrics.
    • Added analytics tracking for key user interactions: onboarding completion, skill installation/uninstallment, account connections, and message sending.
    • Made analytics configurable via environment variable with built-in opt-out support and automatic disabling in development.
  • Documentation

    • Updated privacy documentation to clarify analytics collection behavior and confirm no personal data is included.

Review Change Stack

graycyrus added 3 commits May 12, 2026 15:27
…tracking

Add GA4 via react-ga4 with triple-gate (IS_DEV, GA_MEASUREMENT_ID, analyticsEnabled consent).
Track route changes via AppShell useLocation effect, plus events for app_open, onboarding
lifecycle, account connect, chat send, and skill install/uninstall. All events validated
against an allowlist — no PII, message content, or credentials leave the app.

Update privacy disclosure text and capability catalog to reflect the new analytics scope.

Closes tinyhumansai#1479
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 18aaf11f-6f49-4ca7-8f2f-6c92147c07cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements Google Analytics 4 tracking across the OpenHuman app with privacy gating, consent management, and event allowlisting. Configuration is sourced from environment variables, initialization is wired into app startup, route changes and feature actions emit anonymous events, and user-facing documentation is updated to reflect the new tracking behavior.

Changes

Google Analytics 4 Integration with Privacy Gating

Layer / File(s) Summary
Analytics Service Core & Tests
app/src/services/analytics.ts, app/src/services/__tests__/analytics.test.ts, app/src/utils/config.ts
Service exports initGA(), trackPageView(path), trackEvent(name, params?), and GA_ALLOWED_EVENTS allowlist. Module-level gaInitialized and gaEnabled flags gate all GA operations. syncAnalyticsConsent(enabled) updates GA consent state and toggles ad-personalization signals. Tests mock react-ga4 and config, validate initialization gates (dev, missing measurement ID), event allowlisting with console warnings, and consent-driven suppression/re-enablement of GA signals.
Configuration & Startup Wiring
app/.env.example, app/package.json, app/src/main.tsx
VITE_GA_MEASUREMENT_ID environment variable and GA_MEASUREMENT_ID config constant allow deployment-specific GA setup. react-ga4 dependency added. App startup initializes GA and emits app_open event with version before React renders.
Route Page-View Tracking
app/src/App.tsx
AppShell emits anonymous page-view events via trackPageView(pathname) in a useEffect dependency on location.pathname.
Feature & Engagement Event Tracking
app/src/pages/onboarding/*, app/src/pages/Accounts.tsx, app/src/pages/Conversations.tsx, app/src/components/skills/*
Onboarding pages emit onboarding_start, onboarding_step_complete (with step name), and onboarding_complete. Accounts page tracks account_connect_start on provider selection. Conversations page tracks chat_message_sent after successful send. Skill install/uninstall dialogs emit skill_install and skill_uninstall with skill IDs.
Account Connection Success Tracking
app/src/services/webviewAccountService.ts
Webview account load handler emits account_connect_success with provider ID after setting account status to open, in both reveal and no-reveal code paths.
User-Facing Documentation
app/src/components/settings/panels/PrivacyPanel.tsx, app/src/features/privacy/whatLeavesItems.ts, src/openhuman/about_app/catalog.rs, .claude/memory.md, .gitignore
Privacy panel, whatLeavesItems, and capability catalog descriptions are updated to explain GA4 tracking (anonymous page views and feature engagement), affirm no personal data/messages/credentials are sent, and document the privacy settings toggle. Memory notes document the GA4 architecture and gating rules. .gitignore adds sentry_bugs entry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The PR is substantial, spanning service core, tests, configuration, startup, and distributed feature tracking across multiple UI layers. The analytics service introduces new consent/initialization state and event allowlisting logic requiring close review. Comprehensive test coverage validates GA behavior, but the distributed event tracking across many components increases cognitive load and surface-area verification.

Possibly related PRs

  • tinyhumansai/openhuman#1405: Both PRs modify app/src/services/analytics.ts—one adds GA4 init/track APIs and consent gating, the other restores Sentry httpContextIntegration behavior.
  • tinyhumansai/openhuman#727: Both PRs modify app/src/services/webviewAccountService.ts—the main PR adds analytics tracking calls, the retrieved PR adds notification handling.
  • tinyhumansai/openhuman#1086: Both PRs modify app/src/services/analytics.ts consent gating and Sentry behavior.

Suggested reviewers

  • senamakel

Poem

🐰 Analytics flow so clean and bright,
GA4 tracks with consent in sight,
No secrets sent, just user delight,
Onboarding steps and chats take flight,
Privacy guarded left and right! 📊✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(analytics): implement Google Analytics' clearly and concisely describes the main change: adding Google Analytics functionality to the application.
Linked Issues check ✅ Passed The PR comprehensively implements all core acceptance criteria from issue #1479: GA4 initialization via config, page/route tracking, key event taxonomy (app_open, onboarding, accounts, chat, skills), privacy-safe payload enforcement with allowlisting, environment gating (dev mode, measurement ID checks, consent toggle), and 11 unit tests covering initialization, gating, and allowlist validation.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #1479 objectives: analytics initialization, event tracking instrumentation, privacy documentation updates, and comprehensive test coverage. No unrelated refactoring or feature creep is present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@graycyrus
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
app/src/services/analytics.ts (1)

198-210: 💤 Low value

Consider removing the redundant ReactGA.set call in syncAnalyticsConsent.

Lines 206-208 call ReactGA.set({ allow_ad_personalization_signals: false }) every time consent is toggled, but this value is already set to false in initGA() (line 244) and should never change. Since the actual consent enforcement happens via the gaEnabled flag that gates trackPageView and trackEvent, this re-set is redundant.

♻️ Proposed simplification
 export function syncAnalyticsConsent(enabled: boolean): void {
   const client = Sentry.getClient();
   if (client && !enabled) {
     void Sentry.flush(2000);
   }

   // Update the GA consent shadow and toggle ad-personalization signals.
   gaEnabled = enabled;
   if (gaInitialized) {
-    ReactGA.set({ allow_ad_personalization_signals: false });
     console.debug(`[analytics] GA consent updated: enabled=${enabled}`);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/analytics.ts` around lines 198 - 210, The
syncAnalyticsConsent function contains a redundant call to ReactGA.set({
allow_ad_personalization_signals: false }) which is already established in
initGA and never changes; remove that ReactGA.set call from
syncAnalyticsConsent, keep updating the gaEnabled shadow and the console.debug
line (adjust the debug message if desired) and rely on initGA to set
allow_ad_personalization_signals once; references: function
syncAnalyticsConsent, initGA, gaEnabled, gaInitialized, and ReactGA.set.
app/src/services/__tests__/analytics.test.ts (1)

409-417: ⚡ Quick win

Ensure console.warn spy is always restored

Line 410 creates a global spy, but restore on Line 416 won’t run if an assertion throws first. Wrap the body in try/finally to prevent cross-test leakage.

Suggested patch
   test('drops events not in the allowlist and logs a warning', async () => {
     const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
-    const { initGA, trackEvent } = await freshAnalytics();
-    initGA();
-    trackEvent('internal_debug_event');
-    expect(hoisted.gaEvent).not.toHaveBeenCalled();
-    expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('internal_debug_event'));
-    warnSpy.mockRestore();
+    try {
+      const { initGA, trackEvent } = await freshAnalytics();
+      initGA();
+      trackEvent('internal_debug_event');
+      expect(hoisted.gaEvent).not.toHaveBeenCalled();
+      expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('internal_debug_event'));
+    } finally {
+      warnSpy.mockRestore();
+    }
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/__tests__/analytics.test.ts` around lines 409 - 417, The
test creates a global console.warn spy (warnSpy) but only restores it after
assertions, risking leaks if an assertion throws; wrap the body of the test that
calls freshAnalytics(), initGA(), trackEvent('internal_debug_event'), and the
assertions in a try/finally so warnSpy.mockRestore() is always executed in the
finally block to guarantee restoration even on failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main.tsx`:
- Around line 54-57: The trackEvent('app_open', { version: APP_VERSION }) call
is firing for overlay/mascot windows as well; wrap it so it only runs for the
main/non-standalone window. Add or call a single predicate (e.g. isMainWindow(),
isMainAppWindow(), or check window.type/window.name/IPC-provided flag) before
invoking trackEvent in main.tsx, leaving initSentry() and initGA()
unchanged—only call trackEvent when that predicate returns true so
overlays/mascots do not emit app_open.

In `@app/src/pages/onboarding/pages/ContextPage.tsx`:
- Around line 16-19: The onNext handler currently calls completeAndExit()
without handling rejection; update the onNext callback that wraps trackEvent and
completeAndExit to await or chain a .catch on completeAndExit (the function name
completeAndExit in this component) and handle errors explicitly—e.g., log the
error via the existing logger or trackEvent an error event and surface user
feedback (toast/modal) as appropriate—so any failure in completeAndExit is not
an unhandled rejection and the failure path is observable.

In `@app/src/services/webviewAccountService.ts`:
- Around line 312-329: The code currently fires
trackEvent('account_connect_success', connectSuccessParams) unconditionally,
which counts warm re-opens (payload.state === 'reused') as new connects; update
the logic in the webview reveal path and the else branch so that trackEvent is
only called when payload.state !== 'reused' (i.e., a real new connect).
Specifically, inside the invoke(...).finally() and the fallback else, check
payload.state and only call trackEvent('account_connect_success',
connectSuccessParams) when payload.state !== 'reused' while still always
dispatching setAccountStatus({ accountId, status: 'open' }) as before.

---

Nitpick comments:
In `@app/src/services/__tests__/analytics.test.ts`:
- Around line 409-417: The test creates a global console.warn spy (warnSpy) but
only restores it after assertions, risking leaks if an assertion throws; wrap
the body of the test that calls freshAnalytics(), initGA(),
trackEvent('internal_debug_event'), and the assertions in a try/finally so
warnSpy.mockRestore() is always executed in the finally block to guarantee
restoration even on failure.

In `@app/src/services/analytics.ts`:
- Around line 198-210: The syncAnalyticsConsent function contains a redundant
call to ReactGA.set({ allow_ad_personalization_signals: false }) which is
already established in initGA and never changes; remove that ReactGA.set call
from syncAnalyticsConsent, keep updating the gaEnabled shadow and the
console.debug line (adjust the debug message if desired) and rely on initGA to
set allow_ad_personalization_signals once; references: function
syncAnalyticsConsent, initGA, gaEnabled, gaInitialized, and ReactGA.set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b180a648-aa89-4a26-89df-5fd57aca37b7

📥 Commits

Reviewing files that changed from the base of the PR and between 78d1f3d and 7c359df.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • .claude/memory.md
  • .gitignore
  • app/.env.example
  • app/package.json
  • app/src/App.tsx
  • app/src/components/settings/panels/PrivacyPanel.tsx
  • app/src/components/skills/InstallSkillDialog.tsx
  • app/src/components/skills/UninstallSkillConfirmDialog.tsx
  • app/src/features/privacy/whatLeavesItems.ts
  • app/src/main.tsx
  • app/src/pages/Accounts.tsx
  • app/src/pages/Conversations.tsx
  • app/src/pages/onboarding/OnboardingLayout.tsx
  • app/src/pages/onboarding/pages/ContextPage.tsx
  • app/src/pages/onboarding/pages/SkillsPage.tsx
  • app/src/pages/onboarding/pages/WelcomePage.tsx
  • app/src/services/__tests__/analytics.test.ts
  • app/src/services/analytics.ts
  • app/src/services/webviewAccountService.ts
  • app/src/utils/config.ts
  • src/openhuman/about_app/catalog.rs

Comment thread app/src/main.tsx Outdated
Comment on lines +54 to +57
// Initialize Sentry and GA early (before React renders)
initSentry();
initGA();
trackEvent('app_open', { version: APP_VERSION });
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Emit app_open only for the main window.

Line 57 currently fires for overlay/mascot windows too, which can overcount app-open analytics. Gate this event to non-standalone/main context.

Suggested patch
 initSentry();
 initGA();
-trackEvent('app_open', { version: APP_VERSION });
+if (!isStandaloneWindow) {
+  trackEvent('app_open', { version: APP_VERSION });
+}
 document.documentElement.dataset.window = currentWindowLabel;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Initialize Sentry and GA early (before React renders)
initSentry();
initGA();
trackEvent('app_open', { version: APP_VERSION });
// Initialize Sentry and GA early (before React renders)
initSentry();
initGA();
if (!isStandaloneWindow) {
trackEvent('app_open', { version: APP_VERSION });
}
document.documentElement.dataset.window = currentWindowLabel;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main.tsx` around lines 54 - 57, The trackEvent('app_open', { version:
APP_VERSION }) call is firing for overlay/mascot windows as well; wrap it so it
only runs for the main/non-standalone window. Add or call a single predicate
(e.g. isMainWindow(), isMainAppWindow(), or check
window.type/window.name/IPC-provided flag) before invoking trackEvent in
main.tsx, leaving initSentry() and initGA() unchanged—only call trackEvent when
that predicate returns true so overlays/mascots do not emit app_open.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d98ec80 — gated app_open with if (!isStandaloneWindow) so overlay/mascot windows don't fire it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graycyrus, confirmed — the current code in app/src/main.tsx already reflects the fix with if (!isStandaloneWindow) guarding the trackEvent('app_open', ...) call. Overlay and mascot windows will no longer inflate app-open counts. Thanks for the quick turnaround!


🧠 Learnings used
Learnt from: graycyrus
Repo: tinyhumansai/openhuman PR: 167
File: app/src/components/OnboardingOverlay.tsx:30-39
Timestamp: 2026-04-01T13:52:17.936Z
Learning: In React components, do not perform synchronous `setState` (or other state-updating calls) directly inside `useEffect` bodies. The lint rule `react-hooks/set-state-in-effect` (enforced in this codebase) disallows patterns such as resetting state flags at the top of an effect before returning. Refactor to avoid calling state setters in the effect; prefer deriving state from props/render, using refs, or restructuring logic so state updates occur outside `useEffect`.

Comment on lines +16 to +19
onNext={() => {
trackEvent('onboarding_step_complete', { step_name: 'context' });
void completeAndExit();
}}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle completeAndExit() rejection explicitly

Line 18 intentionally drops the returned promise. If onboarding completion fails, this can become an unhandled rejection and the failure path is silent.

Suggested patch
       onNext={() => {
         trackEvent('onboarding_step_complete', { step_name: 'context' });
-        void completeAndExit();
+        void completeAndExit().catch(error => {
+          console.error('[onboarding:context-page] completeAndExit failed', error);
+        });
       }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onNext={() => {
trackEvent('onboarding_step_complete', { step_name: 'context' });
void completeAndExit();
}}
onNext={() => {
trackEvent('onboarding_step_complete', { step_name: 'context' });
void completeAndExit().catch(error => {
console.error('[onboarding:context-page] completeAndExit failed', error);
});
}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/onboarding/pages/ContextPage.tsx` around lines 16 - 19, The
onNext handler currently calls completeAndExit() without handling rejection;
update the onNext callback that wraps trackEvent and completeAndExit to await or
chain a .catch on completeAndExit (the function name completeAndExit in this
component) and handle errors explicitly—e.g., log the error via the existing
logger or trackEvent an error event and surface user feedback (toast/modal) as
appropriate—so any failure in completeAndExit is not an unhandled rejection and
the failure path is observable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d98ec80 — added .catch() to completeAndExit() with a diagnostic log.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graycyrus Thanks for the quick fix! The updated file confirms the .catch() chain is in place with the diagnostic console.error log — that exactly matches the suggested patch and ensures any completeAndExit() rejection is now observable rather than silently swallowed. ✅


🧠 Learnings used
Learnt from: graycyrus
Repo: tinyhumansai/openhuman PR: 167
File: app/src/components/OnboardingOverlay.tsx:30-39
Timestamp: 2026-04-01T13:52:17.936Z
Learning: In React components, do not perform synchronous `setState` (or other state-updating calls) directly inside `useEffect` bodies. The lint rule `react-hooks/set-state-in-effect` (enforced in this codebase) disallows patterns such as resetting state flags at the top of an effect before returning. Refactor to avoid calling state setters in the effect; prefer deriving state from props/render, using refs, or restructuring logic so state updates occur outside `useEffect`.

Comment on lines 312 to 329
const trigger = payload.trigger === 'watchdog' ? 'watchdog' : 'load';

const provider = store.getState().accounts.accounts[accountId]?.provider;
const connectSuccessParams = provider ? { provider } : undefined;

if (bounds) {
invoke('webview_account_reveal', { args: { account_id: accountId, bounds, trigger } })
.catch(err => {
errLog('webview_account_reveal failed account=%s: %o', accountId, err);
})
.finally(() => {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
trackEvent('account_connect_success', connectSuccessParams);
});
} else {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
trackEvent('account_connect_success', connectSuccessParams);
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid counting warm re-opens as account_connect_success.

This event is emitted even when payload.state === 'reused', which represents reopening an already-loaded account, not a new successful connect.

Suggested patch
 const trigger = payload.trigger === 'watchdog' ? 'watchdog' : 'load';

 const provider = store.getState().accounts.accounts[accountId]?.provider;
 const connectSuccessParams = provider ? { provider } : undefined;
+const shouldTrackConnectSuccess = payload.state !== 'reused';

 if (bounds) {
   invoke('webview_account_reveal', { args: { account_id: accountId, bounds, trigger } })
@@
       .finally(() => {
         store.dispatch(setAccountStatus({ accountId, status: 'open' }));
-        trackEvent('account_connect_success', connectSuccessParams);
+        if (shouldTrackConnectSuccess) {
+          trackEvent('account_connect_success', connectSuccessParams);
+        }
       });
 } else {
   store.dispatch(setAccountStatus({ accountId, status: 'open' }));
-  trackEvent('account_connect_success', connectSuccessParams);
+  if (shouldTrackConnectSuccess) {
+    trackEvent('account_connect_success', connectSuccessParams);
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const trigger = payload.trigger === 'watchdog' ? 'watchdog' : 'load';
const provider = store.getState().accounts.accounts[accountId]?.provider;
const connectSuccessParams = provider ? { provider } : undefined;
if (bounds) {
invoke('webview_account_reveal', { args: { account_id: accountId, bounds, trigger } })
.catch(err => {
errLog('webview_account_reveal failed account=%s: %o', accountId, err);
})
.finally(() => {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
trackEvent('account_connect_success', connectSuccessParams);
});
} else {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
trackEvent('account_connect_success', connectSuccessParams);
}
const trigger = payload.trigger === 'watchdog' ? 'watchdog' : 'load';
const provider = store.getState().accounts.accounts[accountId]?.provider;
const connectSuccessParams = provider ? { provider } : undefined;
const shouldTrackConnectSuccess = payload.state !== 'reused';
if (bounds) {
invoke('webview_account_reveal', { args: { account_id: accountId, bounds, trigger } })
.catch(err => {
errLog('webview_account_reveal failed account=%s: %o', accountId, err);
})
.finally(() => {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
if (shouldTrackConnectSuccess) {
trackEvent('account_connect_success', connectSuccessParams);
}
});
} else {
store.dispatch(setAccountStatus({ accountId, status: 'open' }));
if (shouldTrackConnectSuccess) {
trackEvent('account_connect_success', connectSuccessParams);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/webviewAccountService.ts` around lines 312 - 329, The code
currently fires trackEvent('account_connect_success', connectSuccessParams)
unconditionally, which counts warm re-opens (payload.state === 'reused') as new
connects; update the logic in the webview reveal path and the else branch so
that trackEvent is only called when payload.state !== 'reused' (i.e., a real new
connect). Specifically, inside the invoke(...).finally() and the fallback else,
check payload.state and only call trackEvent('account_connect_success',
connectSuccessParams) when payload.state !== 'reused' while still always
dispatching setAccountStatus({ accountId, status: 'open' }) as before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d98ec80 — added shouldTrackConnectSuccess = payload.state !== 'reused' guard so warm re-opens don't count as new connections.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graycyrus, confirmed — the fix is exactly in line with the suggestion. shouldTrackConnectSuccess = payload.state !== 'reused' is correctly guarding both the .finally() path and the else branch, so warm re-opens no longer inflate account_connect_success counts. Thanks for the quick turnaround!

…ection, skip reused accounts

- Gate app_open event to main window only (skip overlay/mascot windows)
- Add .catch() to completeAndExit() in ContextPage to prevent unhandled rejection
- Skip account_connect_success for reused/warm-reopened accounts
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.

Implement Google Analytics

1 participant