Commit Graph
435 Commits
Author SHA1 Message Date
Tanmay Deep Sharma a5ce1edf20 feat(voice): WhatsApp calling UI — inbox toggle, call widget, badges
Frontend for WhatsApp WABA calling: calling status toggle in inbox settings, WhatsApp Call channel tile, voice badges, call widget redesign, and conversation header call button.
2026-05-20 15:57:05 +05:30
Tanmay Deep Sharma 0ef7585ccb Merge remote-tracking branch 'origin/develop' into feat/whatsapp-call-ui 2026-05-20 15:56:35 +05:30
Sivin VargheseandGitHub bca95efb82 feat: add image resize support in articles (#14293) 2026-05-19 19:34:43 +05:30
6560dbb68d feat: Add an option on the dashboard to allow switching help center layout (#14491)
<img width="633" height="431" alt="Screenshot 2026-05-18 at 12 32 55 PM"
src="https://github.com/user-attachments/assets/682d4c5f-4c76-465b-8d2f-92fbc2bb2a40"
/>

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-05-19 06:42:48 -07:00
Sivin VargheseandGitHub 497d34c097 fix: render markdown in CSAT survey messages (#14468) 2026-05-19 10:27:14 +05:30
Sivin VargheseandGitHub bcb66cdcc0 chore: Support creating articles from category view (#14406)
# Pull Request Template

## Description

This PR adds support for creating articles directly from the category
view. Previously, articles could only be created from the main articles
page. With this change, users can now create a new article while
browsing a specific category, making the workflow faster and more
convenient.

Fixes
https://linear.app/chatwoot/issue/CW-7050/create-an-article-when-inside-a-category

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

### Screencast


https://github.com/user-attachments/assets/e5a72a85-676e-4747-948a-6b1a19d2089f




## 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
2026-05-18 18:09:53 +05:30
Tanmay Deep Sharma 3d914065e0 feat(inbox): preserve channel logo with voice badge in inbox/sidebar
Inboxes with voice enabled were showing a generic phone icon in the
inbox list and sidebar, hiding the underlying channel (e.g. WhatsApp).
Keep the native channel logo and overlay an audio-waveform badge when
voice is enabled. The native Twilio Voice inbox (TwilioSms with no
WhatsApp medium) still resolves to the phone icon.
2026-05-13 16:28:40 +05:30
Aarav UniyalandGitHub c6dceb0e07 fix: contacts dropdown overlap (#14305) 2026-05-13 15:28:58 +05:30
Tanmay Deep SharmaandClaude Opus 4.7 ddc863c6f7 feat(voice): Redesign call widget and in-thread call bubbles
Brings the floating call widget and in-thread voice-call bubbles in
line with the new Figma design across Twilio and WhatsApp, light and
dark.

- New CallCard component for the floating widget with incoming /
  outgoing / ongoing states, rounded-square avatar, status badges,
  duration timer, and "Go to conversation thread" footer.
- Floating widget header now shows the contact's city, country, and
  country flag (derived from country_code) when available, falling
  back to the channel icon + inbox name.
- In-thread VoiceCall bubble redesigned: 44x44 tonal icon container,
  title + duration/subtext, audio player + transcript when a
  recording exists, and a "Call back" pill for missed inbound calls.
- isOutbound on the call bubble now accepts both outgoing/outbound
  and incoming/inbound spellings and falls back to message
  orientation, so the label can no longer disagree with which side
  of the thread the bubble sits on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:44:20 +05:30
Tanmay Deep Sharma d765e7c106 Merge remote-tracking branch 'origin/develop' into feat/whatsapp-call-ui
# Conflicts:
#	spec/enterprise/services/voice/inbound_call_builder_spec.rb
2026-05-12 17:38:27 +05:30
Sivin VargheseandGitHub 3df827c931 chore: update Captain documents filter UI (#14429) 2026-05-12 11:27:30 +05:30
Sojan JoseandGitHub 086aa36ffe feat(companies): add notes and history to company details (#14401) 2026-05-11 23:15:25 +05:30
f6be0d80ef feat: UI changes for document auto sync [AI-153] (#14258)
# Pull Request Template

## Description

FE code for document sync

Adds:
- UI to show counts (stats) of available web pages, stale and synced
documents and last synced at
- Bulk action and manual ways to sync web documents
- index to stats related columns

## Type of change

Please delete options that are not relevant.

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.

https://linear.app/chatwoot/issue/AI-153/fe-document-auto-sync

Documents dashboard:
<img width="2160" height="986" alt="CleanShot 2026-05-11 at 17 57 09@2x"
src="https://github.com/user-attachments/assets/6d934764-964c-4656-b005-1b4f0329e553"
/>

Filters:
<img width="1138" height="564" alt="CleanShot 2026-05-11 at 17 58 13@2x"
src="https://github.com/user-attachments/assets/cee780e6-eb8f-4aed-8cc5-b674244a821b"
/>

Needs update:
<img width="2222" height="966" alt="CleanShot 2026-05-11 at 17 57 53@2x"
src="https://github.com/user-attachments/assets/70c85ddd-7eb1-4328-ba14-7929e67e7b36"
/>

pdfs:
<img width="2180" height="558" alt="CleanShot 2026-05-11 at 17 58 30@2x"
src="https://github.com/user-attachments/assets/975b5c9f-bd1c-4979-9870-8f926d7f6e11"
/>

bulk actions:
<img width="2244" height="992" alt="CleanShot 2026-05-11 at 17 58 57@2x"
src="https://github.com/user-attachments/assets/bdb3c63f-d2de-41dc-a6d5-8821d3303be0"
/>

single url sync:
<img width="2264" height="722" alt="CleanShot 2026-05-11 at 17 59 19@2x"
src="https://github.com/user-attachments/assets/7d7323a5-0fcb-4be9-8635-55e56964999b"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] 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
- [x] 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
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-05-11 20:13:29 +05:30
Sojan JoseandGitHub 10597863d7 feat(companies): add company creation flow (#14402) 2026-05-08 11:46:33 +05:30
Tanmay Deep Sharma 3f65838155 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 19:57:42 +07:00
Sivin VargheseandGitHub 2435d7503c feat: Update conversation bulk action UI (#14118) 2026-05-07 14:58:38 +05:30
Tanmay Deep Sharma ad48473ced Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui
# Conflicts:
#	app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue
2026-05-07 15:41:24 +07:00
salmonumbrellaandGitHub fe44b07147 feat(companies): add company detail page (#14054) 2026-05-06 20:50:27 +05:30
Tanmay Deep Sharma 83b59ec969 fix(voice): address PR #14346 review feedback in one pass
Multi-agent / module-state correctness
- Hoist WhatsApp outbound init lock to module scope so header + contact-panel
  buttons share one guard; add an active-session guard so a second click
  returns { status: 'locked' } instead of cleanup()-ing the live call.
- isLocalWhatsappCall() filter on voice_call.outbound_connected and
  voice_call.ended cable handlers — account-wide broadcasts no longer
  feed foreign SDP into this tab's PeerConnection or stop its recorder.
- Permission-flow path (200 with no call id) now releases the
  prepareOutboundOffer() mic + RTCPeerConnection instead of leaving the
  mic indicator stuck on.
- Drop the intentionallyClosing guard around sendWhatsappTerminateBeacon
  so a hangup-then-close race still terminates Meta's side (beacon
  endpoint is idempotent).
- Distinguish locked init from permission_requested in callers to avoid
  a false "call initiated" alert.

Provider routing
- joinCall outbound short-circuit now scoped to WhatsApp-like calls so
  FloatingCallWidget's auto-join for outbound Twilio still works.
- isWhatsappLikeCall(callId-keyed) so calls seeded by message.updated /
  refresh path (which lack provider metadata) route to the WhatsApp flow.
- syncConversationCallVisibility per-call filter via shouldShowCall, so
  outbound calls aren't ripped from under the caller on assignee change.
- removeCallsForConversation tears down each active call via
  teardownByProvider — WhatsApp gets cleanupWhatsappSession (closes pc,
  stops recorder/mic) instead of a Twilio-only endClientCall.
- await reject before dismissing in rejectIncomingCall so a failing reject
  keeps the call surfaced for retry.

Per-bubble overhead
- Split useCallSession into the root-mount hook + a lightweight
  useCallActions for components like VoiceCall.vue that just need state +
  actions without registering global window/Twilio listeners. Globals
  attach once via a refcount, dismissed-call sids live at module scope so
  the seed watcher can't re-add a locally dismissed ringing call.

Lookup correctness
- /contacts/:id/conversations accepts an optional inbox_id filter; the
  WhatsApp call button passes inboxId so a contact's older WhatsApp
  thread doesn't fall outside the BE's 20-row cap.

Twilio lifecycle / security
- Defer accepted_by_agent claim to the participant-join webhook so a
  failed agent device init doesn't leave the call ringing-but-claimed
  with no recovery path. mark_agent_joined still raises 409 if another
  agent has already claimed.
- Verify X-Twilio-Signature on recording_status — the controller fetches
  the recording with channel auth credentials, so an unsigned POST
  could coerce credential-bearing requests to an attacker-controlled host.

Legacy data
- Migration to delete orphaned inboxes whose channel_type still says
  'Channel::Voice' after the model was removed. The polymorphic
  belongs_to :channel lookup on those rows otherwise crashes the inbox
  serializer with `uninitialized constant Channel::Voice`.
2026-05-06 17:14:08 +07:00
Tanmay Deep SharmaandClaude Opus 4.7 4093a8a48d feat(voice): unify call button flow and redesign call widget
- Contact-panel call button now continues in the open conversation when
  the conversation's inbox is voice-capable, mirroring the header button.
  For non-voice channels it falls back to the inbox picker.
- Twilio and WhatsApp paths share the same upstream decision; both pass
  a conversationId hint to the provider so the agent stays in the same
  thread.
- Floating call widget redesigned: avatar + name + phone with duration
  on top, a clickable "View chat history" pill linking to the
  conversation, and labeled End / Join action buttons. Mute is preserved
  for active WhatsApp calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:45:10 +07:00
Tanmay Deep Sharma 8df58d0a93 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-05 18:11:11 +07:00
Sivin VargheseandGitHub 6386eec5e7 fix: regex validation not applied for custom text attributes in UI (#14110)
# Pull Request Template

## Description

This PR fixes multiple issues related to regex patterns and validation
for custom attributes.
1. Fixed regex patterns being double-escaped when saving from Add and
Edit flows
2. Fixed regex validation not being enforced in the widget pre-chat form
3. Minor UI improvements in the Add/Edit custom attribute dialog


Fixes
[CW-6625](https://linear.app/chatwoot/issue/CW-6625/bug-report-custom-attribute-regex-validation-not-working-in-ui),
https://github.com/chatwoot/chatwoot/issues/13771

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Loom video**

**Before**
https://www.loom.com/share/14f1983a8bc84f9fabc3663afd83cd50

**After**
https://www.loom.com/share/867c0484741140c1944fcbd43914c9c0

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] 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
- [x] 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
2026-05-05 12:55:53 +05:30
Tanmay Deep Sharma e6a35193f3 fix(voice): align outbound WhatsApp call lifecycle with real pickup
A pile of related fixes around the dashboard's WhatsApp call flow:

- ConversationHeader / Contacts/VoiceCallButton: drop the immediate
  setCallActive at initiate time. The call sits in incomingCalls
  (callDirection: outbound) until the backend signals real pickup, so
  the duration timer never starts pre-pickup. Phone button is disabled
  whenever there is an active or incoming call.

- FloatingCallWidget:
  * Loop a ringtone (bell.mp3) for inbound ringing only.
  * Hide the green Join button for outbound — the agent has nothing to
    "join", and clicking it routed through acceptIncomingCall →
    prepareInboundAnswer → cleanup() and tore down the live outbound
    session before the API 409 ("already accepted by another agent").
  * Auto-join watcher skips whatsapp outbound (Twilio's joinConference
    flow only).

- useCallSession:
  * joinCall short-circuits for outbound calls — defense-in-depth so
    no future surface can re-trigger the destroyed-session bug.
  * endCall + outbound rejectIncomingCall pass call.callId to
    endActiveCall, so terminate fires even if module state was wiped.

- useWhatsappCallSession:
  * New recorderArmed flag, reset by cleanup. ontrack only calls
    setupRecorder when armed.
  * Inbound's acceptIncomingCall arms the recorder before the API
    round-trip (agent click = pickup).
  * armOutboundRecorder exported for the cable handler when ACCEPTED
    arrives.
  * endActiveCall accepts a callIdOverride to fall back when the
    module's activeCallId was nulled by an earlier cleanup.

- actionCable:
  * Split the cable contract: outbound_connected only applies the SDP
    answer (tunnel-up signal); outbound_accepted (new) is the real
    pickup signal — flips active and arms the recorder.
2026-05-05 14:15:40 +07:00
Tanmay Deep Sharma 4a79abb367 fix(voice): direction-aware copy for voice call bubble
Outbound IN_PROGRESS no longer claims "They answered" — the backend can
flip in_progress before the contact actually picks up (Twilio agent-join
flow), so the label "Call in progress" stands on its own. Outbound
no_answer now reads "Contact didn't pick up" instead of the inbound
"Missed call / No answer" framing, and outbound ringing shows
"Calling…" instead of "Not answered yet".
2026-05-05 14:14:52 +07:00
Tanmay Deep Sharma 19b5496c56 Merge remote-tracking branch 'origin/develop' into feat/whatsapp-call-ui
# Conflicts:
#	app/javascript/dashboard/components-next/message/bubbles/VoiceCall.vue
#	app/javascript/dashboard/composables/useCallSession.js
#	app/javascript/dashboard/helper/voice.js
#	config/locales/en.yml
#	enterprise/app/services/enterprise/whatsapp/providers/whatsapp_cloud_service.rb
2026-05-04 20:48:25 +07:00
0bd0cab868 feat(voice): Attach call recordings + show call duration on the bubble (#14344)
When an inbound voice call ends, the conversation bubble now (1) renders
an inline audio player as soon as Twilio finishes the recording and (2)
shows the call duration alongside "Call ended" so the agent gets the
at-a-glance summary without opening the recording.

Fixes
https://linear.app/chatwoot/issue/PLA-118/feat-recordings-on-calls-should-be-attached-on-the-conversation
and
https://linear.app/chatwoot/issue/PLA-119/duration-of-the-call-is-not-visible-on-the-chat-bubble

## How to test

1. Set up a Twilio voice inbox and trigger an inbound call.
2. Answer the call from an agent, talk for a few seconds, then hang up.
3. As soon as the call ends, the bubble should read **"Call ended —
0:NN"** (where NN is the call duration in seconds).
4. Wait a few seconds for Twilio to finish processing the recording
(usually <30s after hangup).
5. The same bubble should now show an inline audio player below the
duration. Press play; the recording should be audible.
6. Refresh the page — both the duration and the player should still be
there.
7. End a second call on the same conversation — its bubble should get
its own duration + player, independent of the first.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-05-04 17:14:01 +04:00
ea87610999 feat(voice): Join active call from the conversation bubble (#14343)
Agents can now click **Join call** directly on the incoming call bubble
in the conversation timeline. If they refresh the page or miss the
floating widget while a call is still ringing, the bubble becomes the
recovery affordance — one click joins the conference, no need to wait
for the next event.

The button only appears when the call is still ringing, no other agent
has claimed it, and the conversation is unassigned or assigned to the
current agent (mirroring the floating widget's eligibility rules). It
disappears as soon as anyone joins the call or it ends.

Fixes
https://linear.app/chatwoot/issue/PLA-117/ability-to-join-the-call-by-clicking-on-call-bubble-in-a-conversation

## How to test

1. Set up a Twilio voice inbox and trigger an inbound call to it.
2. As an agent who is eligible to answer (unassigned conversation, or
assigned to you), open the conversation **without answering from the
floating widget**. The bubble should show a teal **Join call** link
under "Not answered yet".
3. Refresh the page mid-ring — the link should still be there.
4. Click **Join call** — you should be connected to the conference, the
bubble should flip to "Call in progress / You answered", and the link
should disappear.
5. As a second agent who is **not** eligible (conversation assigned to
someone else), open the same conversation — the link should not appear.
6. Wait for the call to end — the bubble should show "Call ended" with
no Join link.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-05-04 12:24:31 +04:00
Tanmay Deep Sharma 0b1ad1a854 chore(voice): trim dead exports, fix duplicate-OR bug, drop redundant comments
Branch review pass:
- useWhatsappCallSession: drop unused `error` ref and unused exports
  (hasActiveWhatsappCall, isWhatsappCallMuted), fold sendWhatsappCallBeacon
  into a private beaconTerminate helper since only sendWhatsappTerminateBeacon
  consumed it externally. Trim WHAT-comments; keep WHY-comments
  (browser-quirk explanations, race-condition notes, auth-cookie rationale).
- VoiceCall.vue bubble: fix `data || data` short-circuit that did nothing —
  upstream key transform was the same on both branches; collapse to one read.
- calls/useCallSession/actionCable/FloatingCallWidget/VoiceCallButton:
  drop comments that just describe what the next line already says.

Net diff: -63 lines across 7 files. No behavior change.
2026-05-04 15:16:04 +07:00
Tanmay Deep Sharma 6835f9c85b feat(voice): WhatsApp Cloud Calling — UI 2026-05-02 16:06:48 +07:00
Tanmay Deep Sharma a766ebf642 chore(voice): align FE files to incoming-pipeline base (UI changes consolidated to PR #14346) 2026-05-02 14:40:06 +07:00
Tanmay Deep Sharma 0e0e0868d7 chore(voice): split FE changes out to feat/whatsapp-call-ui (PR #14346) 2026-05-02 14:31:13 +07:00
Tanmay Deep Sharma d9077c64d3 fix(voice): robust accept-with-fetch + remote-end recording race + single-duration bubble + audio playback signals 2026-05-01 18:29:54 +07:00
Tanmay Deep Sharma 1abb51992b fix(voice): fall back to content_attributes.data for duration when call payload lacks it 2026-05-01 17:50:02 +07:00
Tanmay Deep Sharma 6e11bebeae fix(audio): resolve real duration for MediaRecorder WebM blobs via end-seek trick 2026-05-01 11:16:55 +07:00
Tanmay Deep Sharma 63c193358c fix(voice): render audio + transcript + duration on call bubble; upload recording on remote-end too 2026-05-01 11:06:53 +07:00
517b67dfd6 feat: Add rich template preview for WhatsApp & Twilio Templates (#13206)
This PR introduces a comprehensive, display-only template preview system
for both WhatsApp and Twilio templates rendering of all supported
template types.

This lays the foundation for rich template previews across:
- Template selection modals
- Campaign creation flows
- Message composer
- Message screen.

<img width="1818" height="2058" alt="template-preview"
src="https://github.com/user-attachments/assets/9833b28c-f824-4568-8f74-6da09ac61f62"
/>

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-30 18:54:34 +04:00
353089473e feat(voice): Assignment aware visibility and join conflict for inbound calls (#14333)
### Description
Inbound voice calls now route ownership cleanly: the call widget is
hidden from agents who aren't the conversation assignee, the first agent
to pick up becomes the assignee, and any later join attempt by another
agent is rejected with a clear "<agent> is already handling the call."
alert.

Closes
https://linear.app/chatwoot/issue/PLA-98/inbound-voice-calls-assignment-aware-visibility-auto-assignment-on

### How to test

1. As Agent A and Agent B, open the dashboard for the same voice inbox
in two browsers.
2. Place an inbound call to the inbox with the conversation
**unassigned** — both agents should see the call widget.
3. Have Agent A click **Join**. Agent A's widget transitions to the
active call; Agent B's widget disappears (conversation is now assigned
to Agent A).
4. While the call is in progress, attempt to join from a third agent
(e.g., via the bubble in the conversation timeline) — the join is
rejected with the toast `Agent A is already handling the call.`
5. Resolve the conversation, then place a second call to a conversation
that is already manually assigned to Agent A — only Agent A sees the
widget; nobody else does.
6. Race test: trigger two near-simultaneous join attempts (two agents
click Join within a few hundred ms of each other) — exactly one wins;
the other gets the conflict alert.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-30 18:38:10 +04:00
Tanmay Deep Sharma 112a8f9e9e feat(voice): branch contact-panel call button on inbox provider for WhatsApp 2026-04-30 20:14:21 +07:00
1124c1b4c2 feat(voice): Wire Twilio voice flow through unified call model (#14091)
Twilio voice now uses first-class `Call` records as the source of truth
for call state, instead of storing it on
`conversation.additional_attributes` and `conversation.identifier`. Each
call gets its own record, its own `voice_call` bubble matched by
`call_sid`, and its own conference name keyed off `Call.id`. Multiple
calls on the same conversation (for `lock_to_single_conversation`
inboxes) now work correctly, and the conversation card stays in sync
with the real latest message.
Fixes https://linear.app/chatwoot/issue/PLA-121/lock-to-single-thread

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-30 11:25:39 +04:00
5325e05143 feat: Add platform-wide status banners for outage notifications (#13943)
Adds a platform-wide status banner system to notify all users about
external service outages. Super Admins can create, edit, and manage
banners via the Super Admin console. Banners support markdown for links
and are dismissible by users.


<img width="1099" height="236" alt="image"
src="https://github.com/user-attachments/assets/047a7994-d885-4a8a-b9c4-aeb32f15474a"
/>




## How to test
1. Set `ENABLE_PLATFORM_BANNERS=true` in your environment
2. Go to Super Admin → Platform Banners
3. Create a banner with a message like: `Elevated error rates from Meta
APIs. [Check status](https://metastatus.com)`
4. Select a banner type: `info` (blue), `warning` (amber), or `error`
(red)
5. Visit the dashboard — the banner should appear at the top
6. Click "Dismiss" — the banner hides and stays dismissed across page
reloads
7. Deactivate the banner in Super Admin — it disappears on next page
load

## What changed
- New `PlatformBanner` model with `banner_message`, `banner_type`
(info/warning/error), and `active` flag
- Super Admin CRUD via Administrate (controller, dashboard, routes,
sidebar icon)
- `DashboardController` serves active banners via `globalConfig`
- `StatusBanner.vue` component renders banners with markdown support and
per-banner localStorage dismiss
- Feature gated behind `ENABLE_PLATFORM_BANNERS` env var

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-29 17:18:38 +04:00
0e122188e9 feat: Add voice calling as a capability on Twilio SMS channel(Enterprise) (#13963)
Voice calling is now a capability on the existing TwilioSms rather than
a separate Voice model. A single Twilio phone number handles both SMS
and voice calls through one inbox.
Fixes
https://linear.app/chatwoot/issue/CW-6683/add-voice-calling-as-a-capability-on-twilio-sms-channel
and https://linear.app/chatwoot/issue/PLA-120/add-the-support-for-sms

**What changed**
- Replaced Channel::Voice with voice_enabled flag on Channel::TwilioSms
- Added voice_enabled, twiml_app_sid, api_key_secret columns to
channel_twilio_sms table
  - Dropped channel_voice table (no production data)
- All voice logic lives in Enterprise layer via
prepend_mod_with('Channel::TwilioSms')
- Added Voice settings tab on Twilio SMS inbox settings to
enable/disable voice
  - Validates Twilio number voice capability before provisioning
- Teardown service cleans up TwiML app and credentials when voice is
disabled
- Frontend voice detection uses isVoiceCallEnabled() /
getVoiceCallProvider() helpers — extensible to future providers
  - Gated by channel_voice feature flag

**How to test**

1. Enable feature flag:
Account.find(<id>).enable_features('channel_voice')
2. Create voice inbox: Inboxes → Voice tile → enter Twilio credentials →
verify incoming/outgoing calls and SMS work
3. Enable voice on existing SMS inbox: Inboxes → select Twilio SMS inbox
→ Voice tab → toggle on → provide API key credentials → verify calls
work
4. Disable voice: Voice tab → toggle off → verify TwiML app is deleted,
credentials cleared, SMS still works
5. Re-enable voice: Toggle on again → must provide api_key_secret again
→ new TwiML app provisioned

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-29 11:32:19 +04:00
Konstantin VasilevandGitHub 7820739f3a fix: prevent Country from being set to Zimbabwe in the contacts list (#14078)
## Description

This PR resolves an issue that was causing Zimbabwe country being
selected in the Contacts list.
The root cause is objects in `countries.json` file are missing the
`code` property which was causing the countries map to always have the
last country in the list also map to an `undefined` key. In turn, this
was causing an error when contact's `country` was undefined.

Additional thing to keep in mind: I am not sure if there is a case when
contact's `country` property is used or if it can be removed as well,
but in my Chatwoot installation contacts only have a `countryCode`
property, and there is no way to set `country` from the widget sdk.

Fixes #7822

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Simply running the vue application locally targeting my live chatwoot
API.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] 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
- [x] Any dependent changes have been merged and published in downstream
modules
2026-04-28 11:36:40 +05:30
224556fd1b feat: onboarding account details with enriched data [UPM-17][UPM-18] (#13979)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2026-04-28 10:35:51 +05:30
16b8693e1b fix: standardize contact company field on company_name (#14099)
Standardizes the contact company import/filter/automation contract on
`company_name`.

Closes #14096
Revives #9907

## Why

Contact company is read across the current CRM/contact UI from
`additional_attributes['company_name']`, but CSV import and a few
backend filter/automation paths still used the older `company` key. That
meant imported company values could be saved in a place the dashboard,
sorting, filters, and automation conditions did not consistently read
from.

Based on the production data check, the legacy `company` automation
configuration is effectively dead: the affected account did not have
contacts populated with `additional_attributes['company']`. So this PR
intentionally avoids adding long-term fallback behavior and uses
`company_name` as the single key going forward.

## What changed

- Contact CSV import now writes only `company_name` into
`additional_attributes['company_name']`.
- The example contact import CSV now uses the `company_name` header.
- Contact company sorting/filter config now uses `company_name`.
- Automation condition config now uses `company_name`.
- Existing standard automation conditions with `attribute_key:
'company'` are migrated to `company_name`.
- Existing saved contact filters with standard `attribute_key:
'company'` are migrated to `company_name`.
- Custom attributes named `company` are preserved and are not rewritten
by the migration.

## How to test

- Import a contact CSV with a `company_name` column and confirm the
Contact Company field is populated.
- Sort contacts by Company and confirm imported contacts are ordered
correctly.
- Create/edit an automation with Company as a condition and confirm it
saves with `company_name`.
- Verify existing saved contact filters and automation rules using the
old standard `company` key are migrated to `company_name`.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-04-27 18:43:26 +05:30
Sivin VargheseandGitHub 8faa5a74b1 fix: prevent focus jump to title after new article auto-creates (#14145)
# Pull Request Template

## Description

When creating a help center article, typing a title and navigating into
the content auto-creates the article and switches the route
(`/articles/new` → `/articles/.../edit/:slug`). During this transition,
focus was jumping back to the title, interrupting editing.

This happened because `ArticleEditor` always autofocuses the title. On
route change, the component remounts and re-triggers focus. Now, after
auto-create, focus stays in the body as expected.


Fixes
https://linear.app/chatwoot/issue/CW-6951/issue-with-the-cursor-position-on-the-help-center-article-when

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Screencast**


https://github.com/user-attachments/assets/dac3f7c6-08c4-4df2-afb0-7731ee76424b



## 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
2026-04-27 13:30:51 +05:30
2ada713f29 feat: Add bulk actions for help center articles (translate, status change, delete) (#14137)
Fixes
https://linear.app/chatwoot/issue/CW-6950/support-bulk-actions-for-publish-archive-move-to-draft-delete-in

How to test

1. Go to Help Center → Articles
2. Select articles using checkboxes → bulk bar appears
3. Click Publish/Draft/Archive → articles update, list refreshes
4. Click Delete → confirmation dialog → articles removed
5. Click Translate (requires Captain enabled) → select locale + category
→ translation starts
6. Try translating to a locale that already has translations → warning
with links to existing articles → "Overwrite and translate" proceeds
8. Single article: click three-dot menu → Translate → same dialog opens
for that article



https://github.com/user-attachments/assets/7c76495e-f89e-4456-92bd-a6639a9992f4

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 09:13:43 -07:00
PranavandGitHub 751c28d94d feat(ee): Add article translation via LLM in help center (#14136)
Adds the ability to translate help center articles to other languages using Captain's LLM infrastructure. Translated articles are created as drafts linked to the source article.

Fixes
https://linear.app/chatwoot/issue/CW-6901/translate-article-to-another-language

**How to test**

1. Navigate to Help Center → Articles for a portal with multiple locales
2. Click the three-dot menu on any article → "Translate"
3. Select a target language and category → click Translate
4. Switch to the target locale — the translated article appears as a
draft
5. Try translating the same article again — a warning shows the existing
translation with a link to open it in a new tab
6. Click "Overwrite and translate" to replace the existing translation


https://github.com/user-attachments/assets/1d2e991b-f0ac-403a-bcc1-2181b5731ea4
2026-04-24 08:51:26 -07:00
Sivin VargheseandGitHub 475db85318 fix: prevent template picker dropdown cut-off in compose form (#14129) 2026-04-22 17:13:26 +05:30
Sivin VargheseandGitHub 1de90ec136 fix: clean up conversation list rendering (#14107) 2026-04-20 15:48:06 +05:30
Sivin VargheseandGitHub 3a4738729d feat(v5): New expanded layout for chatlist (#13652) 2026-04-20 14:02:10 +05:30