Skip to content

feat(conversations): make widget ticket recovery text customizable - #90802

Open
christiaan-ph wants to merge 5 commits into
masterfrom
posthog-code/widget-ticket-recovery-text
Open

feat(conversations): make widget ticket recovery text customizable#90802
christiaan-ph wants to merge 5 commits into
masterfrom
posthog-code/widget-ticket-recovery-text

Conversation

@christiaan-ph

@christiaan-ph christiaan-ph commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Teams that run the support widget in another language can customize the greeting and the input placeholder, but the ticket recovery footer ("Don't see your previous tickets? Recover them here") stays hardcoded in English. A customer running the widget in a non-English product asked for a way to translate it.

Changes

  • The In-app widget settings gain two inputs under Visual settings: "Ticket recovery text" and "Ticket recovery link text".
  • Both inputs carry a short description: the recovery footer only shows to visitors who aren't using identity verification, and the link text is the clickable part of the message.
  • The two Save buttons show a loading state while a team update is in flight, so rapid saves cannot overwrite each other with a stale settings snapshot.
  • Remote config serves the values as ticketRecoveryText and ticketRecoveryLinkText in the conversations block, defaulting to the current strings.
  • The widget itself reads the new keys in the companion PR: feat(conversations): make widget ticket recovery text customizable posthog-js#4668.
  • The settings follow the same save-per-field pattern as the greeting and placeholder inputs; the kea logic changes are mechanical copies of that pattern.

widget-visual-settings

How did you test this code?

  • Extended test_conversations_enabled_with_defaults and test_conversations_enabled_with_custom_config in test_remote_config.py. They catch remote config dropping the default strings or ignoring the stored settings. Both pass locally.
  • Manual check on a local stack, including the follow-up copy commits: saved custom values through the new inputs, confirmed they persist to conversations_settings, and confirmed the widget (on the companion branch) renders both custom recovery strings in the footer. The widget serves a cached config, so a hard refresh is needed to see fresh saves locally.
  • Not run: the full frontend Jest suite; the logic change has no dedicated tests, matching the existing greeting and placeholder fields.

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored with Claude Code from a support request. Skills invoked: /writing-tests, /writing-user-facing-copy, /writing-pr-descriptions, /review-code. The session verified the change end to end on a local stack (settings UI save flow, Postgres persistence, remote config output). The follow-up commits apply copy findings from a multi-agent review: label wording and field descriptions. An accessibility pass over the widget form (label association, distinct Save button names) was drafted and then pulled back out; it is tracked as team follow-up work instead. No customer material appears in the diff; the test strings are invented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@christiaan-ph christiaan-ph self-assigned this Aug 28, 2026
@trunk-io

trunk-io Bot commented Aug 28, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Bundle size — 🔺 +9.3 KiB (+0.0%)

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

Total: 68.13 MiB · 🔺 +9.3 KiB (+0.0%)

File Size Δ vs base
posthog-app/_parent/products/engineering_analytics/frontend/scenes/EngineeringAnalyticsScene.js 71.7 KiB 🔺 +9.9 KiB (+16.1%)
render-query/src/render-query/render-query.js 21.76 MiB 🔺 +1.6 KiB (+0.0%)
posthog-app/src/scenes/models/NodeDetailScene.js 15.6 KiB 🟢 -1.5 KiB (-8.5%)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.40 MiB 🟢 -1.3 KiB (-0.0%)

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.37 MiB · 22 files no change ███░░░░░░░ 30.3% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.82 MiB · 3,247 files 🔺 +683 B (+0.0%) █████████░ 90.8% of 9.71 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
307.0 KiB ../node_modules/.pnpm/posthog-js@1.422.0_@types+react@18.3.27_react@18.3.1/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
263.4 KiB ../node_modules/.pnpm/posthog-js@1.422.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.js
252.5 KiB src/taxonomy/core-filter-definitions-by-group.json
154.2 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
104.7 KiB src/lib/api.ts
95.2 KiB ../packages/quill/packages/quill/dist/index.js
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

Toolbar bundle — eager 2.26 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.26 MiB · 18 files 🔺 +52 B (+0.0%) ████░░░░░░ 39.5% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 45 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
749.3 KiB dist/toolbar/toolbar-app-J2MBJBF4.css
588.4 KiB dist/toolbar/chunk-chunk-SFURV3BL.js
484.7 KiB dist/toolbar/chunk-chunk-Y2PMGK4R.js
133.8 KiB dist/toolbar/chunk-chunk-6NWA4NUR.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
71.3 KiB dist/toolbar/toolbar-app-BXSMFDC7.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-2SIN7665.js
20.9 KiB dist/toolbar/chunk-chunk-UT2FXBCX.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +84.7 KiB (+0.0%)

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

Total: 1435.19 MiB · 🔺 +84.7 KiB (+0.0%)

⚠️ Playwright — 1 flaky

🎭 Playwright report · View test results →

⚠️ 1 flaky test:

  • Hover chart to see tooltip with data point values (chromium)

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

@christiaan-ph christiaan-ph added the enhancement New feature or request label Aug 28, 2026
@trunk-io

trunk-io Bot commented Aug 28, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

christiaan-ph and others added 2 commits August 28, 2026 12:47
Rename the "Ticket recovery link" label to "Ticket recovery link text" so it
reads as the link's wording rather than a URL, and explain under both ticket
recovery fields when the footer is shown. Associate every widget settings
label with its input via htmlFor/id and give each Save button a distinct
accessible name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…copy

Remove the htmlFor/id label association and Save button aria-labels so the
form keeps one labeling pattern throughout. Widget form accessibility is
tracked as team follow-up work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@christiaan-ph
christiaan-ph marked this pull request as ready for review August 28, 2026 12:05
@christiaan-ph christiaan-ph added team/conversations Conversations team feature/conversations Conversations (support inbox + widget) labels Aug 28, 2026
@github-actions

github-actions Bot commented Aug 28, 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 7c4d01d · box box-520fbdaa0d78 · ready in 625s (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 August 28, 2026 12:06
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
products/conversations/frontend/scenes/settings/supportSettingsLogic.ts:1701-1702
**Unrelated updates erase recovery drafts**

When a user enters either recovery-text draft and then changes another widget setting such as color, position, enabled state, or required email, that setting's `updateCurrentTeamSuccess` resets both draft values to `null`, causing the inputs to revert to their persisted text and silently discard the user's edits.

### Issue 2
products/conversations/frontend/scenes/settings/WidgetSection.tsx:199-207
**Recovery settings remain undocumented**

This adds two user-facing settings and corresponding remote-config properties without updating the matching product documentation, leaving users and integrators unable to discover the newly supported configuration there.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(conversations): scope widget setting..." | Re-trigger Greptile

Comment on lines 1701 to 1702
actions.setIdentificationFormTitleValue(null)
actions.setIdentificationFormDescriptionValue(null)

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.

P1 Unrelated updates erase recovery drafts

When a user enters either recovery-text draft and then changes another widget setting such as color, position, enabled state, or required email, that setting's updateCurrentTeamSuccess resets both draft values to null, causing the inputs to revert to their persisted text and silently discard the user's edits.

Prompt To Fix With AI
This is a comment left during a code review.
Path: products/conversations/frontend/scenes/settings/supportSettingsLogic.ts
Line: 1701-1702

Comment:
**Unrelated updates erase recovery drafts**

When a user enters either recovery-text draft and then changes another widget setting such as color, position, enabled state, or required email, that setting's `updateCurrentTeamSuccess` resets both draft values to `null`, causing the inputs to revert to their persisted text and silently discard the user's edits.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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.

Confirmed this is a real issue, and it predates this PR. The updateCurrentTeamSuccess handler clears every draft input (greeting, placeholder, both identification form fields, both recovery fields, and the Slack copy fields) to null after any successful team update. Because each input falls back to its saved value when the draft is null, an unsaved draft is silently dropped the moment another setting saves, including the color/position/enabled/require-email controls that save immediately when toggled. This PR only added the two recovery fields to that existing reset list, following the same save-per-field pattern as greeting and placeholder. A proper fix resets only the field that was actually saved, but the current save flow sends the whole settings object every time, so the success handler can't tell which field changed without restructuring how saves work — a change that should apply consistently to all the copy fields, not just the two recovery ones. That's a design decision beyond this PR's scope, so I'm escalating rather than making a recovery-only patch that would behave differently from its sibling fields. What a maintainer needs to decide: whether to redesign the draft-reset so unrelated saves keep in-progress drafts (e.g. each save clears only its own draft), applied across all widget copy fields, ideally alongside the concurrent-save handling from the related thread. Note the loading-guard fix on the recovery Save buttons does not address this, since a toggle elsewhere still triggers the reset.

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.

Agreed. This is a real, pre-existing issue in the shared copy-field save flow, and fixing only the recovery fields would create inconsistent behavior with greeting, placeholder, identification, and Slack copy. Keeping it as a follow-up for a coordinated draft-reset redesign is the right scope decision; the recovery Save-button loading guard does not address resets triggered by unrelated immediate-save controls.

Comment thread products/conversations/frontend/scenes/settings/WidgetSection.tsx
@christiaan-ph christiaan-ph added the reviewhog ($$$) Reviews pull requests before humans do label Aug 28, 2026
@posthog

posthog Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 2 should fix, 0 consider.

Published 2 findings (view the review).

Resolved comments: 2 fixed, 1 declined, 1 left for you

@posthog

posthog Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot 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.

PostHog Review

Found 2 should fix.

Comment thread products/conversations/frontend/scenes/settings/WidgetSection.tsx
Comment thread products/conversations/frontend/scenes/settings/WidgetSection.tsx
@posthog posthog Bot removed the reviewhog ($$$) Reviews pull requests before humans do label Aug 28, 2026
…erwrite

The two recovery Save buttons stayed clickable during an in-flight team
update. Because each save spreads the same conversations_settings snapshot
and the server merge is client-wins, a second concurrent save reverted the
first, losing the value silently.

Set loading={currentTeamLoading} on both recovery Save buttons so an
in-flight save disables them, matching the double-submission guard in
SlackSection and the repo convention.

Generated-By: PostHog Desktop
Task-Id: 0a532af1-05d4-4511-9f72-2b09847186d8
@github-actions
github-actions Bot requested a deployment to preview-pr-90802 August 28, 2026 12:31 In progress
The description said the recovery footer is hidden from "identified"
visitors. In PostHog "identified" means posthog.identify(), which does not
hide the footer. The footer is hidden only for visitors using identity
verification (a signed identity_distinct_id + identity_hash), matching the
sibling in-app footer gate and the wording in the Identity verification
settings section.

Reword to "Only shown to visitors who aren't using identity verification"
so a team running a logged-in product does not wrongly skip translating it.

Generated-By: PostHog Desktop
Task-Id: 0a532af1-05d4-4511-9f72-2b09847186d8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature/conversations Conversations (support inbox + widget) team/conversations Conversations team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant