feat(signals): bring cloud inbox custom events in line with desktop#65036
Conversation
Add a shared inboxAnalytics module mirroring the desktop Code app's inbox telemetry (event names + property shapes), with an inbox_client property so cloud and desktop are comparable in one project. Wires up Inbox viewed, Inbox report opened/closed (with dwell time), dismiss/restore actions, and a real Signal source connected event — previously the cloud inbox only fired a thin slice of these.
There was a problem hiding this comment.
This PR exceeded the automated size ceiling (552 lines across 13 files), triggering a gate denial. The change itself is low-risk — a frontend-only analytics refactor centralizing posthog.capture calls into a new inboxAnalytics.ts module with tests — but it needs a quick human sign-off to clear the size gate.
|
… action/close variants
|
Re: the outside-diff finding on |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2f3a0e6bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
|
Size Change: -14 kB (-0.02%) Total Size: 64.5 MB 📦 View Changed
ℹ️ View Unchanged
|
… view/open edge cases
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cac9ce3331
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try { | ||
| await api.signalReports.setState(reportId, { state: 'potential' }) | ||
| // Fire only after the restore persists, matching ReportDetailActions' fallback path. | ||
| captureInboxReportAction({ report, actionType: 'restore', surface: 'list_row' }) |
There was a problem hiding this comment.
Pass detail report context to restore analytics
When an archived report is restored from a detail page whose archived list has not loaded (for example /inbox/reports/<suppressedId> or a quick restore on /inbox/archived/<id>), ReportDetailActions still routes through the mounted archived reportListLogic, but values.reports.find(...) returns undefined. This call then emits Inbox report action with report_id: null and loses priority/actionability even though the detail already has the report, so restore analytics for deeplinks are misclassified; pass the detail report into restoreReport or fall back to the detail-pane capture when the list doesn't contain it.
Useful? React with 👍 / 👎.
| if (values.selectedReportId !== id) { | ||
| actions.setSelectedReportId(id) | ||
| // First route to a report before any list URL was seen → cold deep-link; otherwise an in-app click. | ||
| actions.setSelectedReportId(id, id ? (cache.inboxListVisited ? 'click' : 'deeplink') : 'unknown') |
There was a problem hiding this comment.
Keep tab-qualified report deeplinks as deeplinks
When a cold link lands on a non-default detail route like /inbox/reports/<id> or /inbox/archived/<id>, this handler first dispatches setActiveTab(tab) while selectedReportId is still null, so that action's URL sync routes through /inbox/<tab> and marks cache.inboxListVisited before this line derives the open method. Those direct/shared links are then recorded as open_method: 'click' instead of deeplink; this is separate fresh evidence from the bare-report redirect because the tab-qualified route itself flips the visited flag before opening the report.
Useful? React with 👍 / 👎.
| rank, | ||
| listSize, | ||
| }) | ||
| cache.openTracking = { report, openedAt: Date.now() } |
There was a problem hiding this comment.
Start dwell tracking when the detail is shown
For in-app opens from a loaded row, setSelectedReportId seeds selectedReport immediately so the detail pane is visible while the authoritative GET is still in flight, but the open event and openedAt timestamp are only set here after that request succeeds. On a slow request this undercounts dwell time by the full fetch latency, and if the user backs out before the fetch returns the stale-load guard skips the open entirely even though the user viewed the report; initialize tracking from the seeded report/selection time and reconcile when the fetch returns.
Useful? React with 👍 / 👎.
|
🎭 Playwright report · View test results →
These issues are not necessarily caused by your changes. |
Problem
The cloud inbox (Signals) and the desktop "Code" app ship nearly the same inbox UI, but their telemetry had drifted badly apart. Desktop emits a full engagement funnel —
Inbox viewed,Inbox report opened,Inbox report closed(with dwell time), a richInbox report action, and a realSignal source connected. Cloud only fired a thin slice:Inbox report actionforcreate_pr+ reviewer edits, and asignals source interestevent. So for the cloud inbox we had no visibility into how often it's opened, which reports get drilled into, how long people spend on a report, or when sources actually get connected — and we couldn't compare the two clients in one project.Changes
Added a shared
inboxAnalytics.tsmodule that mirrors the desktop app's inbox event names and property shapes, and wired the cloud inbox into it. Every event now carries aninbox_client: 'cloud'discriminator (desktop sends'desktop') so funnels and breakdowns can split or combine the two clients.Events now emitted from the cloud inbox:
Inbox viewedtab,report_count,total_count, filters, priority/actionability breakdownInbox report openedopen_method(click/deeplink),previous_report_id,rank,list_size,report_age_hoursInbox report closedtime_spent_ms,close_methodInbox report actionsurface,is_bulk,bulk_size,dismissal_reasonSignal source connectedsource_product,is_first_connection,via_setup_wizardThe biggest gaps closed: dwell time per report, the open/view funnel, and instrumenting dismiss/restore (the archive UI existed but fired nothing). The existing
signals source interestevent is preserved, just routed through the shared module.Centralizing also means the previously inline
posthog.capture(...)calls scattered across three components now go through one typed helper, so the event names and payloads can't silently drift from desktop again.How did you test this code?
I'm an agent (Claude Code). Automated checks I actually ran:
inboxAnalytics.test.ts(5 cases) covering theinbox_clientstamp, priority/actionability breakdown, single vs bulk action shape, and the connected-source flags — all pass viahogli test.pnpm --filter=@posthog/frontend typescript:check— passes.oxlint+oxfmton the touched files — clean.No manual UI testing performed.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Andy asked to compare the inbox telemetry between the desktop Code app (
../code) and the cloud Signals inbox, then bring cloud up to parity with a proper set of custom events plus a client discriminator. I mapped both sides (desktop centralizes events inpackages/shared/src/analytics-events.tsvia tracking hooks; cloud had ad-hocposthog.capturein three files), then mirrored the desktop event names and property shapes in a new cloud module.Key decisions:
inbox_client(notsurface) as the cloud-vs-desktop discriminator, since desktop already usessurfacefor the action surface (detail_pane/bulk_bar/list_row).inboxSceneLogickeyed on the selected-report lifecycle, with dwell time and a flush on unmount; open method is inferred (cold deep-link vs in-app click) from whether a list URL was seen first.dismissin the shareduseReportArchivehook so both the card and the detail pane are covered from one place; bulk dismiss fires once frominboxBulkActionsLogic.action_typealigned with desktop's enum (added one cloud-onlyrestorefor the Archive tab).No repo skills were required for this change. Regenerated kea logic types with
kea-typegen(the*LogicType.tsfiles are gitignored / CI-regenerated).