From d2e6d6aee3fad279f1988e08c93703b58058381b Mon Sep 17 00:00:00 2001 From: Gabriel Jablonski Date: Mon, 22 Dec 2025 20:10:45 -0300 Subject: [PATCH 1/4] fix: Improve handling of empty custom attributes list in settings (#13127) ## Description This PR fixes a UX bug in the Custom Attributes settings page where switching tabs becomes impossible when the currently selected tab has no attributes. Closes #13120 ### The Problem When viewing the Custom Attributes settings page, if one tab (e.g., Conversation) has no attributes, users could not switch to the other tab (e.g., Contact) which might have attributes. ### Root Cause The `SettingsLayout` component receives `no-records-found` prop which, when true, hides the entire body content including the TabBar. Since the TabBar was inside the body slot, it would be hidden whenever the current tab had no attributes, preventing users from switching tabs. ### The Fix - Removed the `no-records-found` and `no-records-message` props from `SettingsLayout` - Moved the empty state message inline within the body, displayed below the TabBar - The TabBar is now always visible regardless of whether there are attributes in the selected tab ### Key Changes - Modified `Index.vue` to handle empty state inline while keeping TabBar accessible ## Type of change - [X] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? 1. Navigate to Settings > Custom Attributes 2. Ensure only one tab (e.g., Contact) has custom attributes 3. Switch to the empty tab (e.g., Conversation) 4. Verify the TabBar remains visible and the empty state message is shown 5. Switch back to the tab with attributes 6. Verify attributes are displayed correctly 7. Repeat with both tabs empty and both tabs with attributes --- .../routes/dashboard/settings/attributes/Index.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue index bf93ffa1f..13b980c75 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue @@ -150,8 +150,6 @@ const derivedAttributes = computed(() => Date: Mon, 22 Dec 2025 18:55:42 -0800 Subject: [PATCH 2/4] Bump version to 4.9.1 --- VERSION_CW | 2 +- config/app.yml | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION_CW b/VERSION_CW index 88f181192..5b341fd79 100644 --- a/VERSION_CW +++ b/VERSION_CW @@ -1 +1 @@ -4.8.0 +4.9.1 diff --git a/config/app.yml b/config/app.yml index cba7e1fa4..0c7a390f0 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '4.9.0' + version: '4.9.1' development: <<: *shared diff --git a/package.json b/package.json index e98dc665f..87f44528a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "4.9.0", + "version": "4.9.1", "license": "MIT", "scripts": { "eslint": "eslint app/**/*.{js,vue}", From d57170712dcddf1656ce50601236d1008720a830 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> Date: Tue, 23 Dec 2025 13:59:20 +0700 Subject: [PATCH 3/4] fix: increase the alfred connection pool size to 10 (#13138) ## Description Increased the alfred pool size to 10, so that each worker has enough redis connection thread pool to work ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] 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 - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- > [!NOTE] > Introduces configurability for the Redis pool used by `alfred`. > > - `$alfred` `ConnectionPool` size now reads from `ENV['REDIS_ALFRED_SIZE']` with a default of `5` > - Adds `REDIS_ALFRED_SIZE=10` to `.env.example` > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 96cdff8c0ea40f82a57d70be053780e87384ed47. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: tanmay --- .env.example | 2 ++ config/initializers/01_redis.rb | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index d5c7a76f9..55750b2f2 100644 --- a/.env.example +++ b/.env.example @@ -274,3 +274,5 @@ AZURE_APP_SECRET= # Set to true if you want to remove stale contact inboxes # contact_inboxes with no conversation older than 90 days will be removed # REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false + +# REDIS_ALFRED_SIZE=10 diff --git a/config/initializers/01_redis.rb b/config/initializers/01_redis.rb index 93c12fce7..84ae88d29 100644 --- a/config/initializers/01_redis.rb +++ b/config/initializers/01_redis.rb @@ -5,7 +5,8 @@ # Alfred # Add here as you use it for more features # Used for Round Robin, Conversation Emails & Online Presence -$alfred = ConnectionPool.new(size: 5, timeout: 1) do +alfred_size = ENV.fetch('REDIS_ALFRED_SIZE', 5) +$alfred = ConnectionPool.new(size: alfred_size, timeout: 1) do redis = Rails.env.test? ? MockRedis.new : Redis.new(Redis::Config.app) Redis::Namespace.new('alfred', redis: redis, warning: true) end From 79381a4c5ff3cd1a2470b927dc3429c1c87c88c5 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 30 Dec 2025 11:25:54 -0800 Subject: [PATCH 4/4] fix: Add code_block method to WhatsApp and Instagram markdown renderers (#13166) Problem: SystemStackError: stack level too deep occurred when rendering messages with indented content (4+ spaces) for WhatsApp, Instagram, and Facebook channels. Root Cause: CommonMarker::Renderer#code_block contains a self-recursive placeholder that must be overridden: ``` def code_block(node) code_block(node) # calls itself infinitely end ``` WhatsAppRenderer and InstagramRenderer were missing this override, causing infinite recursion when markdown with 4-space indentation (interpreted as code blocks) was rendered. Fix: Added code_block method to both renderers that outputs the node content as plain text: ``` def code_block(node) out(node.string_content) end ``` Fix https://linear.app/chatwoot/issue/CW-6217/systemstackerror-stack-level-too-deep-systemstackerror --- .../markdown_renderers/instagram_renderer.rb | 4 ++ .../markdown_renderers/whats_app_renderer.rb | 4 ++ .../markdown_renderer_service_spec.rb | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/app/services/messages/markdown_renderers/instagram_renderer.rb b/app/services/messages/markdown_renderers/instagram_renderer.rb index 3b47577b2..4ceb6f38e 100644 --- a/app/services/messages/markdown_renderers/instagram_renderer.rb +++ b/app/services/messages/markdown_renderers/instagram_renderer.rb @@ -42,6 +42,10 @@ class Messages::MarkdownRenderers::InstagramRenderer < Messages::MarkdownRendere cr end + def code_block(node) + out(node.string_content) + end + def softbreak(_node) out("\n") end diff --git a/app/services/messages/markdown_renderers/whats_app_renderer.rb b/app/services/messages/markdown_renderers/whats_app_renderer.rb index 9f7dbab6e..8f98218cf 100644 --- a/app/services/messages/markdown_renderers/whats_app_renderer.rb +++ b/app/services/messages/markdown_renderers/whats_app_renderer.rb @@ -30,6 +30,10 @@ class Messages::MarkdownRenderers::WhatsAppRenderer < Messages::MarkdownRenderer cr end + def code_block(node) + out(node.string_content) + end + def softbreak(_node) out("\n") end diff --git a/spec/services/messages/markdown_renderer_service_spec.rb b/spec/services/messages/markdown_renderer_service_spec.rb index 5b8e02e2f..e43b0d6d0 100644 --- a/spec/services/messages/markdown_renderer_service_spec.rb +++ b/spec/services/messages/markdown_renderer_service_spec.rb @@ -74,6 +74,24 @@ RSpec.describe Messages::MarkdownRendererService, type: :service do expect(result.scan("\n").count).to eq(4) expect(result).to include("Para 1\n\n\n\nPara 2") end + + it 'renders code blocks as plain text' do + content = "```\ncode here\n```" + result = described_class.new(content, channel_type).render + expect(result.strip).to eq('code here') + end + + it 'renders indented code blocks as plain text preserving exact content' do + content = ' indented code line' + result = described_class.new(content, channel_type).render + expect(result).to eq('indented code line') + end + + it 'handles code blocks with emojis and special characters without stack overflow' do + content = " first line\n 🌐 second line\n" + result = described_class.new(content, channel_type).render + expect(result).to eq("first line\n🌐 second line") + end end context 'when channel is Channel::Instagram' do @@ -130,6 +148,24 @@ RSpec.describe Messages::MarkdownRendererService, type: :service do expect(result.scan("\n").count).to eq(4) expect(result).to include("Para 1\n\n\n\nPara 2") end + + it 'renders code blocks as plain text' do + content = "```\ncode here\n```" + result = described_class.new(content, channel_type).render + expect(result.strip).to eq('code here') + end + + it 'renders indented code blocks as plain text preserving exact content' do + content = ' indented code line' + result = described_class.new(content, channel_type).render + expect(result).to eq('indented code line') + end + + it 'handles code blocks with emojis and special characters without stack overflow' do + content = " first line\n 🌐 second line\n" + result = described_class.new(content, channel_type).render + expect(result).to eq("first line\n🌐 second line") + end end context 'when channel is Channel::Line' do @@ -358,6 +394,18 @@ RSpec.describe Messages::MarkdownRendererService, type: :service do expect(result).to include('1. first step') expect(result).to include('2. second step') end + + it 'renders code blocks as plain text' do + content = "```\ncode here\n```" + result = described_class.new(content, channel_type).render + expect(result.strip).to eq('code here') + end + + it 'handles code blocks with emojis and special characters without stack overflow' do + content = " first line\n 🌐 second line\n" + result = described_class.new(content, channel_type).render + expect(result).to eq("first line\n🌐 second line") + end end context 'when channel is Channel::TwilioSms' do