Skip to content

feat(conversations): route in-app support submissions to conversations when flag on - #70726

Merged
abigailbramble merged 6 commits into
masterfrom
posthog-code/conversations-support-routing
Jul 14, 2026
Merged

feat(conversations): route in-app support submissions to conversations when flag on#70726
abigailbramble merged 6 commits into
masterfrom
posthog-code/conversations-support-routing

Conversation

@abigailbramble

Copy link
Copy Markdown
Contributor

Problem

The product-support-side-panel flag only gated the side panel support tab. Every other in-app route to support still created a Zendesk ticket even for users who are otherwise fully switched to conversations: the Max AI ticket and feedback prompts, early access feature-preview feedback, and the logged-out support modal on auth pages. So "flag on" didn't actually mean "support goes to conversations" everywhere.

Changes

Centralizes the routing decision in supportLogic's submit path. When the flag is on and the conversations widget is available, the message is sent as a new conversations ticket via posthog.conversations.sendMessage(). Otherwise it falls through to the unchanged Zendesk path.

  • A null sendMessage response falls back to Zendesk (nothing left the browser). A thrown error does not, to avoid double-filing a ticket that may already have reached the server.
  • Submits wait briefly for the lazily-loaded conversations extension before falling back, and flag-on fallbacks are tagged conversations_fallback so the volume is observable while Zendesk is retired.
  • "Contact support" CTAs (error boundary, insight empty states, and so on) open the side panel's own new-ticket composer, prefilled with the message and any exception context, and submitting lands the user in the created ticket's thread — the same experience as creating a ticket from the sidebar. Other surfaces (Max AI, feature-preview feedback) show a toast whose View button opens that thread.
  • Conversations tickets carry just the message, so the form hides the Zendesk triage fields (message type, topic, severity) when the flag is on. The "engineer" wording is kept across the conversations surfaces.

Flag off is byte-identical to today. This is one of three PRs split out of a larger conversations/Zendesk-switchover branch and stands alone; the backend behavior for logged-out email replies lands in a sibling PR (anonymous widget email tickets).

Note

Logged-out users only get a coherent "reply by email" experience once the sibling anonymous-email-tickets PR is also live. The flag rarely matches logged-out users (org-targeted), and the toast copy is hedged, so the two can merge in either order before the flag is turned up for logged-out traffic.

How did you test this code?

Automated only (I, Claude, did not manually click through the UI):

  • supportLogic tests: flag on routes to conversations (Zendesk transport not hit), flag off / extension unavailable / null response fall back to exactly one Zendesk ticket, a thrown sendMessage does not fall back, the submit waits for the lazily-loaded extension before falling back, and exception context is preserved on the conversations message. Suite passes.
  • sidepanelTicketsLogic tests: a support CTA opens the composer prefilled (both when the panel is already mounted and when it mounts fresh), and a submission toast's View opens the specific ticket thread. Suite passes.
  • Typecheck is clean for the changed files.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Abigail directed this; I (Claude, via PostHog Code) wrote it. Carved out of a larger switchover branch. Skills invoked: /writing-tests (gating the logic tests) and /adopting-generated-api-types context for the frontend touchpoints.

Key decisions, several from a multi-agent review pass on the original branch: the null-vs-throw distinction on sendMessage is deliberate (null means nothing was sent, so Zendesk fallback is safe; a throw might mean the server already got it, so we don't fall back); support CTAs drive the sidebar's own NewTicket composer via a consumed intent rather than a second composer implementation, so there's one composer and one post-submit destination; and the submit waits for the lazily-loaded extension (with a tagged fallback) rather than racing it to Zendesk. Conversations tickets stay message-only because the agent ticket view already surfaces session replay and session-scoped exceptions for widget-channel tickets — the appended exception line is a belt-and-suspenders for email-channel tickets and cases where the session can't resolve it.


Created with PostHog Code

…s when flag on

The product-support-side-panel flag only gated the side panel support
tab, so the Max AI ticket/feedback prompts, early access feature-preview
feedback, and the logged-out support modal kept creating Zendesk tickets
even for users fully switched to conversations.

Centralize the routing in supportLogic's submit listener: when the flag
is on and the conversations widget is available, send the message as a
new conversations ticket via posthog.conversations.sendMessage();
otherwise fall through to the unchanged Zendesk path. A null sendMessage
response falls back to Zendesk (nothing was sent), while a thrown error
does not, to avoid double-filing. Submits wait briefly for the lazily-
loaded extension before falling back, and flag-on fallbacks are tagged
so the volume is observable while Zendesk is retired.

With the flag on, "contact support" CTAs open the side panel's own
new-ticket composer, prefilled with any message (and exception context),
and land the user in the created ticket's thread - the same experience
as the sidebar. Other surfaces show a toast whose View button opens that
thread. Conversations tickets carry just the message, so the form hides
the Zendesk triage fields (message type, topic, severity), and the
"engineer" wording is kept across the conversations surfaces.

Generated-By: PostHog Code
Task-Id: 3c8e8aac-4f86-4315-8b5c-2564f28720fb
@abigailbramble abigailbramble self-assigned this Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Bundle size — 🔺 +272.7 KiB (+0.4%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.72 MiB · 🔺 +272.7 KiB (+0.4%)

File Size Δ vs base
toolbar/src/toolbar/debug/chunk-EventDebugMenu.js 255.4 KiB 🔺 +236.8 KiB (+1278.5%)
render-query/src/render-query/render-query.js 21.24 MiB 🟢 -2.8 KiB (-0.0%)
posthog-app/src/scenes/AuthenticatedShell.js 222.4 KiB 🔺 +1.6 KiB (+0.7%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.21 MiB · 22 files no change ███░░░░░░░ 28.2% of 4.29 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.12 MiB · 2,975 files 🔺 +3.8 KiB (+0.0%) █████████░ 87.8% of 9.25 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
278.6 KiB ../node_modules/.pnpm/posthog-js@1.399.5/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
234.9 KiB src/taxonomy/core-filter-definitions-by-group.json
222.1 KiB ../node_modules/.pnpm/posthog-js@1.399.5/node_modules/posthog-js/dist/module.js
164.0 KiB src/queries/validators.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Dist folder size — 🟢 -1.09 MiB (-0.1%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1284.86 MiB · 🟢 -1.09 MiB (-0.1%)

⚠️ Playwright — 1 failed

🎭 Playwright report · View test results →

1 failed test:

  • Separates feature flag properties into their own tab (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

@trunk-io

trunk-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 70d200e · box box-d9aefea4302c · ready in 1091s (push → usable) · build log · rebuilds on every push, torn down on close

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 14, 2026 14:07
@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat(conversations): route in-app suppor..." | Re-trigger Greptile

…ails

A failed feature-preview feedback submit showed an error toast but still
cleared the textarea and collapsed the panel, losing the user's text.
The submit path reports failure by not advancing lastSubmittedTicketId,
so the feedback loader now keeps the panel open (and the draft) when the
ticket wasn't created, and only clears it on success.

Generated-By: PostHog Code
Task-Id: 3c8e8aac-4f86-4315-8b5c-2564f28720fb
The PostHog AI ticket and feedback prompts only cleared their submitting
state when lastSubmittedTicketId advanced (success). A failed submit left
the spinner running / the modal hanging open with no way forward. Await
the submit and, when no ticket was created, clear the pending state so the
user can retry (the error toast already explains what happened).

Generated-By: PostHog Code
Task-Id: 3c8e8aac-4f86-4315-8b5c-2564f28720fb
@github-actions
github-actions Bot requested a deployment to preview-pr-70726 July 14, 2026 14:56 In progress
The new-ticket composer was keyed on a revision that also advanced on
every editor change (onDraftChange routed typing back through the logic),
so typing remounted the editor and dropped focus. Make the draft a
one-shot seed for prefilled CTAs only: drop onDraftChange, bump the
revision solely on injection, and clear the draft when leaving the
composer so a later blank "New ticket" starts empty.

Generated-By: PostHog Code
Task-Id: 3c8e8aac-4f86-4315-8b5c-2564f28720fb

@veryayskiy veryayskiy left a comment

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.

Couple small fixes:

  • supportLogic.ts (fallback comment body, ~L810): when the flag is on but the extension is unavailable / sendMessage returns null, the triage fields are hidden so kind/target_area are undefined, and the Zendesk ticket body renders literal Kind: undefined / Target area: undefined. Fall back to kind ?? 'support' / target_area ?? 'General' there like the subject line already does.
  • sidepanelTicketsLogic.ts: on an enabled mount the re-consume runs twice — afterMount calls initTickets() (whose listener already re-consumes isEmailFormOpen/pendingViewTicket) and then afterMount re-consumes again itself. Harmless today because the guards are idempotent, but it's redundant; drop the re-consume from one of the two.

Stamped!

@abigailbramble
abigailbramble merged commit 2dab4a6 into master Jul 14, 2026
222 of 224 checks passed
@abigailbramble
abigailbramble deleted the posthog-code/conversations-support-routing branch July 14, 2026 20:44
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-14 21:20 UTC Run
prod-us ✅ Deployed 2026-07-14 21:45 UTC Run
prod-eu ✅ Deployed 2026-07-14 21:46 UTC Run

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.

2 participants