From ab01ab78539277056e8de7496d34d904b014c211 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Thu, 16 Jul 2026 11:41:28 +0530 Subject: [PATCH 1/3] fix(perf): back off conversation meta polling when the count is unknown (#15031) ## Description The conversation sidebar counts (`GET /conversations/meta`) are polled on a debounced cadence selected from the last known conversation count (`allCount` in the `conversationStats` store). `allCount` starts at `0` and is only set after a **successful** meta response. When the meta query is slow or failing under load, `allCount` stays `0`, which selected the fastest cadence, so every client polled as aggressively as possible exactly when the endpoint was already struggling. On large accounts with many concurrent agents this multiplies into sustained load that keeps the query slow, a self-reinforcing loop. This PR makes two changes: 1. **Treat an unknown count (`0`) as a large account** and poll at the slowest cadence instead of the fastest (`getMetaDebounceKey`, extracted as a pure, testable function). This breaks the failure loop. 2. **Raise the debounce intervals across all tiers** so counts are polled less frequently under sustained activity: - fast: max 1 poll / 2s -> 1 / 5s - mid: 1 / 10s -> 1 / 20s - slow (large/unknown accounts): 1 / 20s -> 1 / 30s First-load counts still render immediately (the debounce fires on the leading edge for the very first call), so the higher intervals only affect the sustained poll rate, not initial render. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) --- .../store/modules/conversationStats.js | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/app/javascript/dashboard/store/modules/conversationStats.js b/app/javascript/dashboard/store/modules/conversationStats.js index 204b87ea1..1f029d0ec 100644 --- a/app/javascript/dashboard/store/modules/conversationStats.js +++ b/app/javascript/dashboard/store/modules/conversationStats.js @@ -25,24 +25,32 @@ const fetchMetaData = async (commit, params) => { } }; -const debouncedFetchMetaData = debounce(fetchMetaData, 500, false, 2000); -const longDebouncedFetchMetaData = debounce(fetchMetaData, 5000, false, 10000); +const debouncedFetchMetaData = debounce(fetchMetaData, 1000, false, 5000); +const longDebouncedFetchMetaData = debounce(fetchMetaData, 7500, false, 20000); const superLongDebouncedFetchMetaData = debounce( fetchMetaData, - 10000, + 15000, false, - 20000 + 30000 ); +const metaDebouncers = { + default: debouncedFetchMetaData, + long: longDebouncedFetchMetaData, + superLong: superLongDebouncedFetchMetaData, +}; + +// allCount is 0 until a meta request succeeds; under load it stays 0, so treat +// the unknown case as a large account and poll slowest instead of fastest. +export const getMetaDebounceKey = allCount => { + if (allCount > 2000 || allCount === 0) return 'superLong'; + if (allCount > 100) return 'long'; + return 'default'; +}; + export const actions = { - get: async ({ commit, state: $state }, params) => { - if ($state.allCount > 2000) { - superLongDebouncedFetchMetaData(commit, params); - } else if ($state.allCount > 100) { - longDebouncedFetchMetaData(commit, params); - } else { - debouncedFetchMetaData(commit, params); - } + get: ({ commit, state: $state }, params) => { + metaDebouncers[getMetaDebounceKey($state.allCount)](commit, params); }, set({ commit }, meta) { commit(types.SET_CONV_TAB_META, meta); From 9cb6c501b57e877262d944b3fd7afa59adce48dd Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:21:01 +0530 Subject: [PATCH 2/3] fix: html/body background not applied in appearance mode (#13989) # Pull Request Template ## Description This PR fixes the white background bleed visible in the widget, article viewer, and Help Center when dark mode is active. This change was previously merged but later reverted due to a background being added for the unread bubble. Reverted PR: https://github.com/chatwoot/chatwoot/pull/13955, https://github.com/chatwoot/chatwoot/pull/13981 **What was happening** While scrolling, the `` element retained a white background in dark mode. This occurred because dark mode classes were only applied to inner container elements, not the root. **What changed** * **Widget:** Updated the `useDarkMode` composable to sync the `dark` class to `` using `watchEffect`, allowing `` to inherit dark theme variables. Also added background styles to `html`, `body`, and `#app` in `woot.scss`. * **Help center portal:** Moved `bg-white dark:bg-slate-900` from `
` to `` in the portal layout so the entire page background responds correctly to dark mode, including within the widget iframe. * **ArticleViewer:** Replaced hardcoded `bg-white` with `bg-n-solid-1` to ensure better theming. Fixes https://linear.app/chatwoot/issue/CW-6704/widget-body-colour-not-implemented ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ### Screencasts ### Before **Widget** https://github.com/user-attachments/assets/e0224ad1-81a6-440a-a824-e115fb806728 **Help center** https://github.com/user-attachments/assets/40a8ded5-5360-474d-9ec5-fd23e037c845 ### After **Widget** https://github.com/user-attachments/assets/dd37cc68-99fc-4d60-b2ae-cf41f9d4d38c image **Help center** https://github.com/user-attachments/assets/bc998c4e-ef77-46fa-ac7f-4ea16d912ce3 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth --- app/javascript/widget/App.vue | 4 ++++ app/javascript/widget/views/ArticleViewer.vue | 2 +- app/views/layouts/portal.html.erb | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue index 19a33b8a4..7637ffc2f 100755 --- a/app/javascript/widget/App.vue +++ b/app/javascript/widget/App.vue @@ -66,6 +66,9 @@ export default { ? getLanguageDirection(this.$root.$i18n.locale) : false; }, + isUnreadOrCampaignView() { + return ['unread-messages', 'campaigns'].includes(this.$route.name); + }, }, watch: { activeCampaign() { @@ -374,6 +377,7 @@ export default { 'is-widget-right': isRightAligned, 'is-bubble-hidden': hideMessageBubble, 'is-flat-design': isWidgetStyleFlat, + 'bg-n-slate-2 dark:bg-n-solid-1': !isUnreadOrCampaignView, dark: prefersDarkMode, }" > diff --git a/app/javascript/widget/views/ArticleViewer.vue b/app/javascript/widget/views/ArticleViewer.vue index 9289d0546..bc4cf775c 100644 --- a/app/javascript/widget/views/ArticleViewer.vue +++ b/app/javascript/widget/views/ArticleViewer.vue @@ -10,7 +10,7 @@ export default { diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb index ac3f7e574..3085106dd 100644 --- a/app/views/layouts/portal.html.erb +++ b/app/views/layouts/portal.html.erb @@ -3,9 +3,9 @@ <%= render 'layouts/portal_head' %> - +
-
+
<%= render 'public/api/v1/portals/header', portal: @portal unless @is_plain_layout_enabled %> <%= yield %> <%= render 'public/api/v1/portals/footer' unless @is_plain_layout_enabled || @portal.account.feature_enabled?('disable_branding') %> From f1a07657e996934f932c107ac4ef691a977a3282 Mon Sep 17 00:00:00 2001 From: Pranav Date: Wed, 15 Jul 2026 23:58:30 -0700 Subject: [PATCH 3/3] fix(whatsapp): restore embedded signup reauthorization banner (#15035) Show the reauthorization banner whenever an embedded signup inbox has a reauthorization error instead of suppressing it when the manual migration flow is enabled. Reword the manual migration banner and dialog as a recommendation to reconnect with your own Meta app, and switch their styling from amber warning to blue info. --- app/javascript/dashboard/i18n/locale/en/inboxMgmt.json | 8 ++++---- .../routes/dashboard/settings/inbox/Settings.vue | 4 +--- .../inbox/components/WhatsappManualMigrationBanner.vue | 8 ++++---- .../inbox/components/WhatsappManualMigrationDialog.vue | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 50d8ba3f9..07506c20f 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -848,8 +848,8 @@ "WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.", "WHATSAPP_MANUAL_MIGRATION": { "BANNER": { - "TITLE": "WhatsApp setup action required", - "DESCRIPTION": "Meta restrictions are affecting WhatsApp setup and management features. Reconnect this inbox manually to keep your WhatsApp configuration up to date.", + "TITLE": "Manual setup recommended", + "DESCRIPTION": "This inbox connects through the shared Meta app used for embedded signup, which recent Meta restrictions have affected. To avoid similar issues in the future, we recommend reconnecting it with your own Meta app.", "START": "Start manual migration", "GUIDE": "View guide" }, @@ -857,8 +857,8 @@ "EYEBROW": "WhatsApp manual migration", "TITLE": "Reconnect WhatsApp inbox", "CLOSE": "Close", - "ACTION_REQUIRED_TITLE": "Action required for this WhatsApp inbox", - "ACTION_REQUIRED_DESCRIPTION": "Meta restrictions are affecting setup and management features. This guided flow updates the WhatsApp API connection without creating a new inbox.", + "ACTION_REQUIRED_TITLE": "Reconnect with your own Meta app", + "ACTION_REQUIRED_DESCRIPTION": "Inboxes connected through your own Meta app are not affected by restrictions on the shared embedded signup app. This guided flow updates the WhatsApp API connection without creating a new inbox.", "GUIDE_LINK": "Open the manual setup guide", "PRESERVED_TITLE": "Preserved", "PRESERVED_DESCRIPTION": "Conversations, contacts, collaborators, routing, business hours, and inbox settings.", diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 8e42d0c27..222d238a8 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -387,12 +387,10 @@ export default { return this.inbox.provider_config?.source === 'embedded_signup'; }, whatsappUnauthorized() { - // The manual migration banner supersedes the embedded-signup reauthorize flow when the feature is enabled. return ( this.isAWhatsAppCloudChannel && this.isEmbeddedSignupWhatsApp && - this.inbox.reauthorization_required && - !this.showWhatsAppManualMigration + this.inbox.reauthorization_required ); }, whatsappRegistrationIncomplete() { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappManualMigrationBanner.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappManualMigrationBanner.vue index bc80d867a..26c5c7d78 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappManualMigrationBanner.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappManualMigrationBanner.vue @@ -21,14 +21,14 @@ const copy = computed(() => ({