Menu

#427 feat(tray): add PostHog product analytics for DMG installs

closed
nobody
None
2026-07-10
2026-07-09
Anonymous
No

Originally created by: adityaharishch

Summary

  • Adds tray/src-tauri/src/analytics.rs, a minimal best-effort PostHog Cloud client: at most one app_installed event ever, and one daily_usage event per completed local day (focus hours + worklog generated/approved/rejected counts).
  • Raw HTTP POST to PostHog's /i/v0/e/ capture endpoint only — no posthog-js, so session replay/autocapture/surveys/feature flags are never touched. $geoip_disable is set on every event since the request comes straight from the user's machine.
  • Gated to Canonical (DMG) installs only via install::detect_install_mode() — npm/dev/bare runs never send anything.
  • Anonymous distinct_id + day bookkeeping persisted to ~/.meridian/analytics_state.json, separate from settings.json (never dashboard-visible).
  • Wired into the poll loop's existing 60s health tick (poll/mod.rs) — the extra work per tick is a cheap file read; the HTTP calls themselves are capped to ~1/day.
  • Adds the uuid crate (already in the workspace lockfile transitively) for the anonymous id.

Test plan

  • [x] cargo fmt --check / cargo clippy -- -D warnings / cargo test (all pass via pre-commit + pre-push hooks)
  • [x] UI build + UI tests (pre-push hook)
  • [ ] Manual: run a packaged DMG build, confirm app_installed fires once and appears in PostHog Live Events, and that a day-boundary produces one daily_usage event with sane focus_hours/worklogs_* values

Summary by CodeRabbit

  • New Features
  • The setup wizard now includes a Notifications permission step and can guide you to the system notifications settings when access is denied.
  • The onboarding flow now shows a formatted local model label and improved completion summaries.

  • Bug Fixes

  • The wizard no longer requests Input Monitoring during setup, reducing unnecessary permission prompts.
  • Notification-related status handling is now more consistent, including clearer unavailable/denied states.

  • Style

  • Updated onboarding copy and UI wording for clearer, more consistent user-facing text.

Related

Tickets: #430

Discussion

  • Anonymous

    Anonymous - 2026-07-09

    Originally posted by: coderabbitai[bot]

    Review Change Stack

    [!IMPORTANT]

    Review skipped

    Auto reviews are disabled on base/target branches other than the default branch.

    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: ASSERTIVE

    Plan: Pro Plus

    Run ID: 3bbe5c2b-67ea-43c1-962f-1a01e4650957

    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 Adds a PostHog analytics module wired into the tray poll loop, replaces the Input Monitoring permission flow with a Notifications permission flow across Rust commands and the setup wizard UI, updates the wizard window builder with macOS fullscreen support, and applies stylesheet/documentation/text formatting updates. ### Changes **Analytics Telemetry** |Layer / File(s)|Summary| |---|---| |**Dependency and module registration**
    `tray/src-tauri/Cargo.toml`, `tray/src-tauri/src/lib.rs`|Adds `uuid` v4 dependency and registers the new `analytics` module.| |**Analytics state, capture, and daily tick**
    `tray/src-tauri/src/analytics.rs`, `tray/src-tauri/src/poll/mod.rs`|Implements persisted analytics state, PostHog capture, `maybe_send_daily_tick`/`send_daily_usage`, invoked from the poll loop's health tick.| **Notifications Permission Migration** |Layer / File(s)|Summary| |---|---| |**Rust permission commands and pane handling**
    `tray/src-tauri/src/commands/setup.rs`, `tray/src-tauri/src/commands/system.rs`, `tray/src-tauri/src/sys.rs`, `tray/src-tauri/src/lib.rs`, `tray/src-tauri/src/capture/ui_events.rs`|Removes Input Monitoring commands, adds `check_notifications`/`request_notifications`, opens the notifications settings pane, exposes `notifier` crate-wide, and stops requesting Input Monitoring at runtime.| |**Wizard data model and icon**
    `ui/app/setup/data.ts`, `ui/app/setup/atoms.tsx`, `ui/__tests__/setup-data.test.ts`|Adds `NotifState`, updates `PermissionMeta`/`PERMISSIONS` for notifications with `required` flag, adds bell icon and `fmtModelLabel` helper with tests.| |**Setup wizard page and steps wiring**
    `ui/app/setup/page.tsx`, `ui/app/setup/steps.tsx`|Updates permission state, polling, and grant actions from input to notifications, updates `Wiz` interface, badges, and completion summary counts/model label.| **Tray Wizard Window Fullscreen** |Layer / File(s)|Summary| |---|---| |**Wizard window builder and fullscreen helper**
    `tray/src-tauri/src/tray.rs`, `ui/app/setup/page.tsx`|Changes wizard window builder to transparent/resizable sizing, adds macOS `make_fullscreenable` helper, and applies radial-gradient background with viewport scaling on the setup page.| **Docs, Stylesheet, and Copy Updates** |Layer / File(s)|Summary| |---|---| |**Documentation and stylesheet rules**
    `CLAUDE.md`, `NOTIFICATIONS.md`, `ui/STYLESHEET.md`|Adds hyphen-only text rule, fixes RFC-3339 `expires_at` example, and adds a Design Standard section covering color budget, gradients, and data-viz rules.| |**Setup wizard copy fixes**
    `ui/app/setup/steps.tsx`|Updates various wizard strings to plain hyphens and revised wording (net hints, MLX status, integrations, welcome, completion).| **Estimated code review effort:** 3 (Moderate) | ~30 minutes ### Sequence Diagram(s) :::mermaid sequenceDiagram participant PollLoop participant Analytics participant StateFile participant PostHog PollLoop->>Analytics: maybe_send_daily_tick(app, pool) Analytics->>StateFile: load_or_init state Analytics->>PostHog: capture app_installed (once per machine) Analytics->>Analytics: detect local day change Analytics->>PostHog: capture daily_usage (previous day) Analytics->>StateFile: save updated state :::mermaid sequenceDiagram participant SetupPage participant TauriCommand participant NotificationsPlugin participant SystemSettings SetupPage->>TauriCommand: check_notifications() TauriCommand->>NotificationsPlugin: query permission state NotificationsPlugin-->>TauriCommand: PermissionState TauriCommand-->>SetupPage: label state SetupPage->>TauriCommand: request_notifications() TauriCommand->>NotificationsPlugin: request permission NotificationsPlugin-->>TauriCommand: resulting state TauriCommand-->>SetupPage: label state SetupPage->>SystemSettings: open notifications pane (if denied) **Poem** > A rabbit hops the tray with glee, > No more input-watch, notifications are free! > Analytics ticks once a day, > Fullscreen windows lead the way, > Hyphens plain, not dashes wide - > This burrow's code review, hop inside! 🐇
    🚥 Pre-merge checks | ✅ 5
    ✅ Passed checks (5 passed) | Check name | Status | Explanation | | :------------------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------- | | Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Title check | ✅ Passed | The title clearly summarizes the main change: adding PostHog analytics for DMG installs in the tray app. | | Description check | ✅ Passed | The PR description includes a summary and test plan; the checklist and related issues sections are present but not filled in. |
    ✨ Finishing Touches
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `feat/posthog-analytics`

    Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

    ❤️ Share - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

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

     
  • Anonymous

    Anonymous - 2026-07-09

    Originally posted by: adityaharishch

    Pushed 749ffdc0 addressing the review feedback:

    Fixed (real bugs):

    • send_daily_usage no longer swallows a get_today/get_worklogs read failure into fabricated zeros. It now returns whether the send actually succeeded, and maybe_send_daily_tick only advances last_sent_day on success — a transient DB hiccup on the day boundary retries next tick instead of reporting "0 focus hours, 0 worklogs" and permanently losing that day.
    • The analytics tick is now spawned (tauri::async_runtime::spawn) off the poll loop's health tick instead of awaited inline, so a slow/hanging PostHog response (5s timeout) can no longer delay notification drain / tray icon refresh on the rare tick it actually fires.
    • Extracted the day-rollover decision into a pure day_rollover_action helper with 4 unit tests (first-observation, same-day no-op, day-boundary, multi-day-gap) — it's exercised without a live DB/HTTP client now.
    • Module doc now states explicitly that multi-day gaps are never backfilled (only the most-recently-closed day is ever reported) — this was already the intended design, just wasn't spelled out.

    Not resolved silently — needs a product call from @Akarsh-Hegde/@adityaharishch:
    The design/disclosure question (setup wizard currently says "Everything is processed on your Mac. Meridian has no servers to send to" — that becomes false for Canonical installs once this ships) is a genuine product decision, not something I'll resolve unilaterally in code. Flagging it back rather than guessing: do we (a) update that wizard copy to scope the claim correctly, (b) add a settings toggle/opt-out, or (c) ship as-is with the decision documented somewhere? Also noting your nuance is correct and worth keeping in mind: $geoip_disable suppresses derived location properties, PostHog's edge still sees the raw source IP at the network level.

    Not applicable: the ui/app/setup/data.ts em-dash comment — confirmed via gh pr diff --name-only that file isn't part of the current 5-file diff (stale from an earlier CodeRabbit pass before this PR was rebased against main).

    Still open: the manual DMG-run verification (events actually landing in PostHog Live Events) — can't do that from here, needs a packaged build against the real project.

     
  • Anonymous

    Anonymous - 2026-07-10

    Originally posted by: adityaharishch

    Resolved the open design question from the review: decision is to disclose truthfully rather than gate behind a setting or leave the copy overbroad.

    Pushed 6e55c957 — the Permissions step's privacy claim in `ui/app/setup/steps.tsx` (which read "Everything is processed on your Mac. Meridian has no servers to send to.") is now:

    "Your screen, tasks, and worklogs stay on this Mac and are never uploaded. We send anonymous usage stats (like daily focus time) to improve Meridian - never your content."

    This keeps the (still true) part of the original claim — no screen/OCR/task/worklog content ever leaves the machine — while being honest that anonymous usage stats do go to PostHog for Canonical/DMG installs. Scoped it to this one string only; the other "never uploaded" claims elsewhere in the wizard (screen-reading step, on-device model step) were left untouched since they're specifically about screen content, which is genuinely never uploaded regardless of this feature.

     
  • Anonymous

    Anonymous - 2026-07-10

    Ticket changed by: adityaharishch

    • status: open --> closed
     

Log in to post a comment.