Commit Graph

6352 Commits

Author SHA1 Message Date
Gabriel Jablonski
6ef7f29348
fix(compose-conversation): allow modal to shrink on narrow viewports (#280)
Wrappers around the conversation/group forms had a hardcoded w-[42rem] without min-w-0, so on viewports below 672px the modal overflowed both sides. The forms themselves also forced w-[42rem] on a flex-col cross axis, which kept overflowing the wrapper after it shrank. Add min-w-0 to the wrappers and let the forms follow the wrapper width via w-full.
2026-05-04 22:20:31 -03:00
Gabriel Jablonski
72c9821270
feat(whatsapp): add emoji reactions UI (#276)
* feat(whatsapp): add emoji reactions UI

Adds end-to-end agent UI for emoji reactions on WhatsApp inboxes
(Cloud API, Baileys, Z-API). Reactions arrive as Messages with
is_reaction=true; this PR exposes them in the bubble UI and lets
agents react with toggle/replace/remove semantics.

- Add POST /reactions endpoint with toggle/replace logic that handles
  multi-device echoes from the same connected number
- Add Channel::Whatsapp#supports_reactions? capability
- Add Message.hide_removed_reactions scope and use it in conversation
  card preview / last_non_activity_message
- Enrich last_non_activity_message with in_reply_to_snippet for
  reaction previews in chat list
- Frontend: hover EmojiReactionPicker (8 quick + full picker) with
  alignment-aware positioning, single ReactionDisplay chip aggregating
  emojis with total count, conversation card preview shows "Você
  reagiu" for own/multi-device echoes

* fix: address CodeRabbit review feedback

- MessagePreview: render "Você" for outgoing reaction echoes that have no
  sender (multi-device echoes from the connected number)
- MessagesView#findCurrentUserReaction: prefer active reactions over
  deleted rows so a stale deleted echo cannot hijack the toggle target
- conversationHelper: drop removed reactions up-front so the activity
  fallback never returns null when older non-removed messages exist
- imap_import rake: wrap IMAP work in begin/ensure so the session is
  closed even when uid_search/scan_new_email_uids raises
- ReactionDisplay: include reaction.id in the user row so v-for keys
  stay stable across re-renders

* fix: address CodeRabbit round 2 feedback

- enterprise Message override of mark_pending_conversation_as_open_for_human_response
  now early-returns on reaction? so reactions can no longer auto-open Captain-pending
  conversations (matches the OSS guard)
- whatsapp incoming reaction-removal handlers (Cloud/Baileys/Z-API) look up the
  reaction Message globally by sender instead of through the inbound conversation
  scope, then operate on existing.conversation; otherwise an old/resolved thread
  could be silently no-op'd while the inbound flow created a stray empty thread
- EmojiReactionPicker: localize quick-emoji tooltip labels via i18n keys
- Message.vue: track pendingTimeouts and clear them on unmount so the cooldown
  setTimeout no longer touches state after teardown
- toggleMessageReaction action returns the API promise so callers can reconcile
  if the cable echo is delayed

* fix: address CodeRabbit round 3 feedback

- MessageFinder#page_window: pluck the 20-row window IDs before taking .min
  so the latest page honors PAGE_LIMIT (ActiveRecord's .minimum(:id) ignores
  .limit and aggregates over the whole relation)
- ReactionsController#current_user_reaction: rank active reactions ahead of
  deleted rows (same invariant as the frontend lookup) so a stale deleted
  echo can no longer hijack the toggle target and resurrect itself
- Whatsapp incoming handlers (Cloud, Baileys individual & group, Z-API) now
  branch on reaction_removal? BEFORE set_conversation / find_or_create_group_
  conversation, so a blank reaction-removal webhook can never open or create
  a stray thread just to no-op
- Message#reaction?: strict-boolean cast (via ActiveModel::Type::Boolean)
  so a stored string "false" no longer leaks through .present? as truthy

* fix: address CodeRabbit round 4 feedback

- MessageList: anchor unread divider on the filtered visibleMessages
  (firstUnreadId can land on a reaction that's filtered out, otherwise
  the separator silently disappears)
- ReactionDisplay: render the removable user row as a real <button> when
  it's the current user's reaction so keyboard users can focus/activate it
- MessagesView#findCurrentUserReaction: read sender_type from m.sender_type
  OR m.sender?.type so REST-loaded messages match the same row instead of
  spawning a duplicate optimistic reaction
- Whatsapp incoming reaction-removal lookup (Cloud, Baileys, Z-API): pick
  the newest active row first and only fall back to the newest deleted row
  when no active reaction exists, mirroring the controller invariant
- CardMessagePreview: use MESSAGE_TYPE.OUTGOING constant in place of the
  literal 1 for the multi-device reaction echo check

* fix: address CodeRabbit round 5 feedback

- ReactionsController#ensure_target_is_reactable: reject activity,
  template, failed, is_unsupported and missing-source_id targets so the
  API mirrors the client toolbar gate and refuses reactions that could
  never land on WhatsApp
- MessageList reaction aggregator: treat "agent reacted via Chatwoot"
  and "agent reacted via the connected phone" as the same self bucket
  so the chip no longer double-counts the current user when both shapes
  coexist for one target
- internalChat ReactionDisplay: render the removable user row as a real
  <button> so keyboard users can focus and trigger removal (mirrors the
  fix already applied to components-next/message/ReactionDisplay)
- EventDataPresenter#push_last_non_activity_message: reorder
  created_at: :desc before .first so the cable snapshot publishes the
  latest preview instead of the oldest row
- Z-API mark_existing_reaction_as_removed: drop the blanket
  `return unless incoming_message?` and route the lookup by direction
  (contact sender for incoming removals, senderless outgoing row for
  multi-device removals from the connected phone). Chatwoot-originated
  echoes stay idempotent because the active-first guard finds nothing
  once the controller has flipped deleted=true locally
- spec: assert reaction removal does not change messages.count on the
  in-place Cloud path

* fix: address CodeRabbit round 6 feedback

- ReactionsController: validate the emoji payload is a single grapheme
  cluster containing a Unicode Emoji codepoint (not just <=32 bytes), so
  arbitrary short strings like "ok" or "123" can no longer be persisted
  as a reaction or enqueued as a WhatsApp reaction send
- target_unreactable_error: add the content_attributes['deleted'] guard
  to mirror the frontend picker gate on deleted messages
- IncomingMessageBaseService: move contact_processable? AFTER the
  reaction_removal? early-return so a blocked contact's removal webhook
  can still reconcile an existing reaction row instead of leaving a
  stale chip/preview
- imap_import rake: add safe_close_imap(imap) that falls back to
  disconnect when logout raises Net::IMAP::Error, mirroring
  terminate_imap_connection in BaseFetchEmailService, and replace the
  three ensure-block imap&.logout sites with it

* fix: address CodeRabbit round 7 feedback

- CardMessagePreview: resolve `lastNonActivityMessage` against the live
  `messages` array by id before rendering, so the chat-card preview
  picks up the freshest copy instead of the (possibly stale) snapshot
  that was mutated in place by a reaction toggle / multi-device echo
- Message + ReactionDisplay: thread an `inboxSupportsReactions` →
  `read-only` prop into the chip so non-supported channels (eg.
  360Dialog) render historical reactions without a clickable
  toggle/remove path that would only hit a 422
- conversations/index.js: replace the truthiness `&&` guard around the
  out-of-order MESSAGE_UPDATED check with `Number.isFinite` parsing so
  a malformed/missing `updated_at` is treated as stale instead of
  silently overwriting a fresher local row
- Baileys mark_existing_reaction_as_removed: drop the blanket
  `return unless incoming?` and split the lookup by direction
  (sender for incoming, sender-less outgoing for multi-device removals)
  to mirror the Z-API/Cloud handlers
- Whatsapp reaction-removal lookup (Cloud, Baileys, Z-API): drop the
  fallback to the newest deleted row so a Chatwoot-originated removal
  echo no-ops cleanly instead of bumping `updated_at` and dispatching
  another `conversation.updated`
- conversation jbuilder: explicit `reorder(created_at: :desc)` on
  `last_non_activity_message` so REST and cable both serialize the
  same most-recent preview

* fix: address CodeRabbit round 8 feedback

- ReactionsController#current_user_reaction: also match on
  content_attributes.in_reply_to_external_id = @target_message.source_id
  (via OR with the existing in_reply_to check), so WhatsApp-echoed
  reactions persisted by the incoming handlers — where in_reply_to could
  be blanked if the target wasn't resolvable at save time — are found and
  toggled instead of stacking a duplicate self-reaction
- Mirror the same defensive OR check in the frontend
  MessagesView#findCurrentUserReaction, and thread the target's
  source_id through the toggleReaction event from Message.vue so the
  lookup sees it

* fix: address CodeRabbit round 9 feedback

- emoji_payload_valid?: tighten the final property check from \p{Emoji}
  to \p{Extended_Pictographic} so plain "1", "#", "*" (which Unicode
  tags as Emoji because they're valid keycap bases) are rejected as
  reaction payloads
- EmojiReactionPicker: mirror the translated `title` into `aria-label`
  on the icon-only smile-plus / plus buttons so screen readers announce
  a meaningful action name
- internalChat ReactionDisplay: close the popover when the post-removal
  state would leave ≤1 reactions, so a singleton-user popover never
  lingers after removing one of a pair
- EventDataPresenter + conversation jbuilder: strip HTML before
  truncating `in_reply_to_snippet` so reactions to email/HTML bubbles
  don't surface literal "<p>..." markup in the chat-list preview

* fix: address CodeRabbit round 10 feedback

- MessageList#reactionsByMessageId: break createdAt ties with `<=` so a
  later iteration wins on second-resolution tie; two toggles in the same
  second no longer leave the chip pointing at a stale row
- MessagePreview: require a non-empty `message.attachments` array (via
  `?.length`) before taking the attachment preview branch, so a removed
  reaction with `[]` no longer renders the attachment placeholder
- MessagesView#findCurrentUserReaction: replace the sort-based pick with
  a reduce that deterministically takes the last element on tie, so a
  fast toggle can't hit a stale/deleted row with the same created_at
- Baileys group handler: guard against `@sender_contact.blank?` before
  dispatching mark_existing_reaction_as_removed, otherwise a nil sender
  would fall into the senderless-outgoing branch and match the wrong row
- WhatsApp reaction-removal lookups (Cloud, Baileys, Z-API): scope the
  base query to `inbox_id: inbox.id` so a colliding WhatsApp message id
  across inboxes can never mutate a reaction row from another inbox

* fix: address CodeRabbit round 11 feedback

- ReactionsController#emoji_payload_valid?: broaden the final property
  check to accept flag and keycap emoji. `\p{Extended_Pictographic}` by
  itself is per-codepoint, so 🇧🇷 (two Regional Indicators) and 1️⃣
  (digit + VS16 + U+20E3) failed validation. Allow any grapheme cluster
  that contains at least one pictographic codepoint, a Regional
  Indicator, or the combining keycap, while still rejecting plain
  ASCII like "ok", "1", "#"
- Message.vue#canShowReactionToolbar: hide the picker when the target
  has no provider source_id, mirroring the server guard in
  ReactionsController#target_unreactable_error instead of letting the
  click fall through to a 422
- MessageList#reactionsByMessageId: fall back to a sourceId → id
  lookup when a reaction only carries `inReplyToExternalId` (WhatsApp
  echo / phone-originated), so its chip still renders against the
  target bubble after reload
- getLastMessage: merge the fresher store fields onto the API
  snapshot instead of replacing it, so jbuilder-only enrichments like
  `in_reply_to_snippet` survive the store refresh

* fix(reactions): preserve API fields on card preview and expose a11y state on quick picker

* fix(reactions): consistent originalId resolution, natural PT-BR snippet phrasing, accurate outgoing-echo spec

* fix(reactions): reject requests missing emoji param and align zapi outgoing-echo spec fixture

* fix(reactions): activity preview fallback, camelCase event listener, EN snippet quoting, fromMe group removals, REST chat-only preview

* fix(reactions): reject non-string emoji, scope page reactions to window, exempt reactions from human_response, add cloud multi-device removal

* test(message): isolate hide_removed_reactions deleted-branch from blank-content branch

* fix(reactions): coerce in_reply_to_snippet to plain String

strip_tags returns an ActiveSupport::SafeBuffer; truncate preserves the
class. When this snippet flowed into ActionCableBroadcastJob via the
CONVERSATION_UPDATED dispatch, Sidekiq's strict-args check rejected the
non-native JSON type, raising synchronously through the dispatcher and
turning the reactions controller response into a 500 even though the row
had already persisted. The UI then surfaced the generic 'failed to update
reaction' toast despite the chip rendering correctly.

Wrap with String.new so the broadcast payload contains plain Strings.

* fix(reactions): don't auto-scroll to bottom on reaction add

ADD_MESSAGE emits SCROLL_TO_MESSAGE for every new push, which makes
sense for regular outgoing messages (the user just hit send and wants
to see it). Reactions render as chips on the parent bubble, so the
auto-scroll yanked the agent away from whichever older message they
were reacting to. Skip the emit when the incoming message is flagged
as a reaction.

* fix(reactions): skip scroll on conversation-only updates triggered by reactions

The reactions controller dispatches CONVERSATION_UPDATED so the chat list
preview can refresh in place. UPDATE_CONVERSATION mutation always emitted
SCROLL_TO_MESSAGE for the open conversation, so every toggle yanked the
viewport back to the bottom even after the previous fix in ADD_MESSAGE.
When the refreshed preview row is itself a reaction the update is
preview-only and the scroll is unwanted; for a regular incoming message
the latest non-activity row is the message itself, which still triggers
the scroll as before.

* fix(reactions): anchor compact picker to button side instead of centering

The compact picker was centered on the smile button, so half its width
always extended toward the bubble side and overflowed past the chat edge
on short messages. Anchor it to the button's outer side and nudge 4px
toward the bubble so it lines up with the trigger.

* test(reactions): regression coverage for safebuffer + scroll skip

The previous CodeRabbit rounds shipped three bugs the existing specs
didn't catch: a SafeBuffer return from `strip_tags` that 500'd the
reactions controller via Sidekiq strict-args, and two SCROLL_TO_MESSAGE
emits (one per mutation) that yanked the open conversation to the
bottom on every emoji toggle. Lock all three behaviors.

Also tighten the spec policy in AGENTS.md so new features default to
having specs instead of skipping them.

* test(baileys): align send_message_body helper with id:updated_at format

The reactions PR switched chatwootMessageId to "<id>:<updated_at>" so
toggle/replace cycles get a fresh idempotency key against baileys-api,
but the shared spec helper still merged the bare integer id. 18 baileys
provider specs were silently broken on CI as a result.

* fix(reactions): skip set_contact for unknown reaction-removal webhooks

Reaction-removal cloud webhooks were unconditionally creating a contact
even when the sender was unknown and there was nothing to remove,
because set_contact ran before the reaction_removal? short-circuit
(needed earlier so blocked-contact reconciliation works). Add a
sender-agnostic existence check on the inbox/in_reply_to scope and bail
out before set_contact when no candidate row exists.

Also realign two specs that were not updated when the chatwootMessageId
format gained an `:updated_at` suffix and when zapi reaction-removal
short-circuited instead of creating a Message.

* test(conversation): include last_non_activity_message in push_data fixture

Reactions PR added last_non_activity_message to the push_data payload
but conversation_spec's exact-match expectation wasn't updated, so the
sharded CI shard that landed on this file flipped red.
2026-04-30 21:09:12 -03:00
Gabriel Jablonski
5cc78c7b33
feat(super-admin): hide assignee tabs for basic agents (#279)
* fix(featurable): backport feature_flag_value helper from chatwoot-pro-main

Adds the two's-complement-aware helper that returns a signed bigint-safe
value for SQL queries against the feature_flags column. Mirrors the
existing helper in chatwoot-pro-main so future backports of pro features
that reference it (e.g. kanban filters) compile cleanly on main.

Note: the helper does NOT fix FlagShihTzu's write path; new account-level
toggles should use account.settings jsonb instead of feature_flags
(see AGENTS.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(super-admin): toggle to hide assignee tabs for basic agents

Adds two account-level settings, configurable from the super admin
dashboard, that hide the "Unassigned" and "All" tabs of the conversation
list for users with the basic agent role (admins and custom roles are
unaffected). Hiding "Unassigned" implicitly hides "All", since seeing
the full queue without the unassigned subset is incoherent. The
constraint is enforced both in the backend (before_validation forces
hide_agent_all_tab=true when hide_agent_unassigned_tab is on) and in
the super admin form (the "All" checkbox is disabled and auto-checked
when "Unassigned" is checked).

Storage uses account.settings (jsonb) instead of feature_flags to
sidestep the bigint bit-position overflow that happens once features.yml
crosses 64 entries, and to keep keys stable across the main and
chatwoot-pro-main forks where feature bit positions diverge. AGENTS.md
documents the rationale and the recipe to add future toggles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat-list): guard activeAssigneeTabCount against missing tab

When the visibility settings hide the currently selected tab, the
fallback watch resets activeAssigneeTab to ME, but activeAssigneeTabCount
re-evaluates in the same reactive cycle and can read .count on undefined
before the watch flushes. Use optional chaining + nullish fallback so
the count safely returns 0 during the brief inconsistency.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 16:25:45 -03:00
Gabriel Jablonski
b5757eea5d
fix(branding): add SuperAdmin-only notice on upgrade gates (#278)
* fix(branding): add SuperAdmin-only notice on upgrade gates

Some upgrade prompts (Kanban paywall, group creation form, group-disabled
banner in conversation view) are rendered only to SuperAdmins and link to
fazer.ai. Admins viewing those screens were worried that the fazer.ai
link was also being shown to their agents, even though it is not.

Add a discreet "Only system administrators can see this message" line
under each SuperAdmin-only block to make the audience explicit.

* fix(branding): inline SuperAdmin notice into Banner component

The notice was being rendered as a standalone <p> below the conversation
banner, which made it easy to miss. Add an optional noticeMessage prop
to the Banner component and render it inside the banner with italic +
reduced opacity styling, then pass it from the groups-disabled branch
of the MessagesView banner.
2026-04-28 13:20:38 -03:00
Gabriel Jablonski
8b663342c2
fix(whatsapp): process reactions from WhatsApp Cloud API (#275)
Aligns reaction handling with the Baileys/Zapi providers so that
reaction webhooks from the official WhatsApp Cloud API create a
message flagged as is_reaction and linked to the original wamid,
instead of being silently dropped.
2026-04-23 17:22:54 -03:00
Gabriel Jablonski
55c7c435bc
fix(pwa): decouple installability from DISPLAY_MANIFEST branding flag (#272)
DISPLAY_MANIFEST previously gated the entire PWA manifest link and
theme-color meta, so white-label installs that set the flag to false
to hide Chatwoot branding also lost the mobile "Install app" prompt.

Serve /manifest.json from a new dynamic controller that reflects
INSTALLATION_NAME, LOGO_THUMBNAIL and a new BRAND_COLOR config, and
keep the manifest link, theme-color and apple-mobile-web-app meta
emitted regardless of DISPLAY_MANIFEST. The flag now gates only the
Chatwoot-branded raster assets (favicons, apple-icon PNGs, ms-icon,
marketing meta description).
2026-04-22 14:33:38 -03:00
Gabriel Jablonski
079d4b4996
fix(conversations): enforce NOT NULL on contact_id + cleanup orphans (#273)
* fix(conversations): enforce NOT NULL + FK on contact_id

Conversations had contact_id nullable with no FK to contacts. Combined
with dependent: :destroy_async on Contact#conversations, deleting a
contact could leave conversations pointing to a missing contact,
breaking the conversations#index API with
"undefined method 'additional_attributes' for nil" from the contact
partial.

Changes:
- Migration cleans up existing orphans, sets contact_id NOT NULL and
  adds a FK with ON DELETE CASCADE so the invariant is enforced at the
  DB level (complements the existing Rails presence validation).
- ContactMergeAction uses update_all for conversations/messages/notes/
  contact_inboxes so a failing callback cannot silently leave records
  pointing to the mergee contact before it is destroyed.
- Drop the now-redundant orphan filter in Conversations::ResolutionJob
  and its spec; the invariant is enforced at the schema level.

* fix: address review feedback

- Drop the ON DELETE CASCADE FK on conversations.contact_id. Several
  conversation-owned tables (messages, mentions, conversation_participants,
  reporting_events, csat_survey_responses, calls, applied_slas, sla_events,
  and polymorphic notifications) still have plain conversation_id references
  without FK cascades. The DB-level cascade would skip Conversation's
  dependent: cleanup and replace the NULL-contact bug with orphan children,
  and would also conflict with the existing non-cascade FKs on
  scheduled_messages/recurring_scheduled_messages. Keep the invariant at the
  Rails layer (NOT NULL + presence validation + dependent: :destroy_async).
- Clean up orphan conversations in the migration via Rails destroy so
  dependent associations are propagated correctly, instead of a raw
  DELETE FROM conversations that would orphan all child rows.
- Revert ContactMergeAction.merge_* methods back to per-record update! so
  Conversation#after_update_commit still fires (notify_status_change /
  CONVERSATION_CONTACT_CHANGED) for contact_id changes. The bang form
  still removes the silent-failure risk of the original .update call.
2026-04-22 13:57:40 -03:00
Gabriel Jablonski
2f5178eb4f
fix(guides): point FAZER_AI_GUIDES_URL to /#/guides (#271)
Hub was refactored and the guides page now lives at /#/guides.
The old /#/dashboard#guides path still redirects, but new
installs should use the correct URL directly.
2026-04-21 11:05:17 -03:00
Gabriel Jablonski
bd61458720
fix(internal-chat): use internal_chat_channel_id in delete payloads (#270)
Backend broadcast payloads for internal_chat.message.deleted and
internal_chat.reaction.deleted used channel_id as the key, but the
frontend ActionCable handlers (and all other internal-chat events)
expect internal_chat_channel_id. This caused deleted messages and
removed reactions to stay visible on screen until a manual refresh.

Rename the key on the backend so the payloads match the convention
shared with message.created/updated and reaction.created, and drop the
defensive fallback on the frontend reaction-deleted handler.
2026-04-19 14:06:02 -03:00
Gabriel Jablonski
e032fc7774
feat(whatsapp): convert inbox between WhatsApp providers (#268)
* feat(whatsapp): allow converting inbox between WhatsApp providers

Adds a Convert flow to switch a WhatsApp inbox between the four
supported providers (default/360dialog, whatsapp_cloud, baileys, zapi)
without losing conversations, agents, or history.

- Channel::Whatsapp#convert_provider! runs inside a transaction:
  disconnects the old provider, clears provider_connection and
  message_templates, assigns the new provider/config, and triggers
  webhook setup plus template resync on the new service.
- New POST /api/v1/accounts/:id/inboxes/:id/convert_provider endpoint
  guarded by InboxPolicy#convert_provider? (admin only).
- UI adds a Convert button on the inbox Settings page with a
  type-to-confirm ConvertInboxModal that lists the effects before
  redirecting to a dedicated route reusing the WhatsApp provider
  wizard in convert mode (phone number locked, current provider
  hidden from the picker).

* chore(whatsapp): polish convert UI colors and expand specs

- Settings: use slate for the Convert trigger and ruby for the modal
  confirm to mirror the delete gate instead of the less conventional
  amber variant.
- Drop the redundant "current provider is hidden from the list"
  sentence from the convert wizard description.
- Add specs for the post-conversion webhook setup path (triggered and
  skipped branches) and the sync_templates error-rescue behaviour.

* fix: address CodeRabbit review on convert-provider flow

- Whitelist provider_config keys in the convert endpoint via permit
  rather than permit!, and default to an empty hash when omitted so
  the request no longer crashes.
- Pre-validate the new provider config before disconnecting the old
  session so a bad target config no longer terminates the existing
  provider; also keep the disconnect bound to the old provider_url.
- Guard ConvertInboxModal's submit handler so pressing Enter cannot
  bypass the type-to-confirm gate, and migrate it to <script setup>.
- Reject invalid ?provider= query values in convert mode so hidden
  providers (Twilio, the current provider) cannot be reached via URL.
- Await the inbox fetch in InboxConvert before running the route guard
  so directly opening the route for a non-WhatsApp inbox redirects.
- Remove the unreachable second CloudWhatsapp branch in Whatsapp.vue.

* fix: address second CodeRabbit round on convert-provider flow

- Unify provider picker validation so create mode also rejects
  unknown ?provider= values, with a single helper that accepts
  available providers plus the whatsapp_manual fallback.
- Simplify the pre-validation rollback in convert_provider!: the
  errors snapshot/merge dance was redundant because assign_attributes
  does not clear errors.
- Follow the repo convention of asserting on error.class.name so the
  rollback spec stays stable under reloading/parallel environments.
- Strengthen the controller success spec with provider_connection and
  message_templates cleanup invariants, and set Content-Type on the
  templates stub so HTTParty parses the empty data array correctly.

* fix: address third CodeRabbit round on convert-provider flow

- Add 360Dialog entry to the Whatsapp provider catalog, keep it hidden
  from the create picker (preserving the existing fork behavior) but
  expose it in the convert picker where it is a valid target. Restore
  URL reachability for ?provider=360dialog in create mode.
- Scope the WHATSAPP_MANUAL allowance to create mode only: the manual
  fallback flow is not reachable in convert mode.
- Redirect to the inboxes list in InboxConvert when the inbox is still
  absent after the store fetch, so the page no longer stays blank.
- Use an explicit allowlist of WhatsApp providers to gate the Convert
  button instead of negating Twilio, so adding a new WhatsApp channel
  type will not silently expose the flow.
- Bind the disabled provider display field with :value instead of
  v-model, since the underlying computed is getter-only.
- Add Content-Type: application/json to the templates stub in the
  model spec so HTTParty parses the empty data array.

* fix: address fourth CodeRabbit round on convert-provider flow

- Reject no-op conversions that target the same provider as the one
  already configured, so the endpoint no longer wipes provider
  connection and message templates on a request that changes nothing.
- Call the provider service's disconnect directly so failures abort
  the conversion instead of being silently swallowed; otherwise the
  old external session could remain live while the inbox flips to
  the new provider.
- Cover both behaviors with specs.

* fix: address fifth CodeRabbit round on convert-provider flow

- Reset the Vuelidate state when closing ConvertInboxModal so reopening
  the gate does not surface stale validation errors.
- Call teardown_webhooks before converting away from whatsapp_cloud so
  the Meta webhook subscription is removed for embedded_signup channels,
  mirroring the destroy-time cleanup (manual-setup channels keep the
  existing no-op behavior). Swallow teardown failures so a flaky Meta
  call does not abort the swap.
- Switch the rollback specs to compare message_templates counts instead
  of the boolean be_present matcher so they remain meaningful if the
  fixture happens to have an empty templates list.

* fix: address sixth CodeRabbit round on convert-provider flow

- Derive the convert header's current-provider label from the shared
  PROVIDER_CATALOG so the picker and header stay in sync.
- Assert the full Cloud provider_config payload and the absence of the
  Baileys-only provider_url key on both the controller success spec
  and the model atomic-swap spec.
- In the sync-error spec, reload and assert that the record was
  actually flipped to the new provider before the sync rescue fires,
  so the test can't pass on a pre-save failure.

* test: pin 422 error payload on convert_provider negative paths

The unsupported-conversion and invalid-config specs only checked the
status code, so they would have stayed green if the 422 started coming
from a different branch. Pin the response body so each example actually
covers the failure case it names.

* fix(baileys): save custom host as provider_url, not url

The Baileys form was writing the custom endpoint to
provider_config['url'] while the backend reads
provider_config['provider_url']. That silently broke the custom-host
feature for newly created or converted Baileys inboxes: they always
fell back to BAILEYS_PROVIDER_DEFAULT_URL. Align the key on both ends.

* fix(whatsapp): skip second validation pass in convert_provider!

The transaction's save! was re-running validate_provider_config after
the old provider's session had already been disconnected, so a transient
Graph API failure on the second check could roll back the swap while
leaving the external session terminated — the exact inconsistency the
pre-flight valid? was meant to rule out.

Capture the validated provider_config snapshot after valid? (so fields
populated by before_validation callbacks like webhook_verify_token are
preserved) and switch the final persist to save!(validate: false) so the
earlier check stays authoritative.

* fix: normalize provider-conversion failures and pass accountId

- The convert_provider action only rescued ActiveRecord::RecordInvalid,
  so disconnect/teardown failures bubbled up as 500 with no stable
  payload. Catch StandardError, log the class + message, and return a
  422 with a generic user-facing message so the dashboard can surface
  the error consistently.
- Nested settings routes live under /accounts/:accountId, so the
  router push from Settings.vue must include accountId alongside
  inboxId. Mirrors how sibling pages navigate to settings_inbox_show.

* fix: report missing :provider as 400 and sync modal v-model

- The generic rescue StandardError on convert_provider was masking
  ActionController::ParameterMissing behind a misleading
  provider-conversion error message. Catch it explicitly before the
  generic rescue and return 400 with the parameter-missing message.
- ConvertInboxModal's closeModal now drives localShow to false so
  parents using v-model:show stay in sync on every close path,
  not only when the explicit onClose listener flips the flag.

* fix(whatsapp): serialize concurrent convert_provider calls with_lock

Without a per-record lock, two admin requests against the same inbox
could both pass the pre-flight validation, race the disconnect/save,
and then run setup_webhooks/sync_templates in arbitrary order, leaving
the persisted provider out of sync with the external configuration.

Wrap the whole convert flow in with_lock so the loser blocks until the
winner commits; the subsequent no-op guard then rejects a second
conversion request targeting the provider the first one just set.

* test: harden convert_provider policy + controller failure specs

- Pass accountId explicitly in InboxConvert redirects so the route
  navigation mirrors how Settings.vue reaches settings_inbox_convert.
- Add a spec that assigns the agent to the inbox and still expects 401,
  so a future regression in InboxPolicy#convert_provider? can no longer
  slip past on the show policy alone.
- Add a spec that stubs convert_provider! to raise StandardError and
  asserts the controller's generic-failure 422 payload, pinning the
  dashboard contract for provider-side failures.

* test: pin convert_provider success response payload

Parse the rendered body and assert provider + provider_config so the
spec catches regressions where the DB is updated correctly but the
serialized response drifts (dashboard store commits response.data).

* fix(whatsapp): reset teardown guard after pre-conversion webhook cleanup

teardown_webhooks memoizes @webhook_teardown_initiated = true to prevent
double execution during destroy. Calling it from convert_provider!
leaves that flag set, so a subsequent destroy! or follow-up conversion
on the same instance would skip webhook removal silently. Reset the
flag in an ensure block so the destroy-time guard stays scoped to
destroy only.

* fix: include accountId in post-conversion redirect params

* test: pin same-provider convert returns 422

* fix(whatsapp): reset template columns when post-conversion sync fails

* fix(convert): enforce provider allowlist in InboxConvert route guard

* test: broaden Cloud templates stub to match account-scoped path

* test(whatsapp): cover cloud to baileys conversion branch
2026-04-18 20:57:27 -03:00
Gabriel Jablonski
adc0d892e0
feat(internal-chat): support paste and drag-drop for attachments (#269) 2026-04-18 10:48:50 -03:00
gabrieljablonski
c9b1917886 fix(installation_config): restore coder that accepts both jsonb hash and YAML string
Upstream migration 20260324102005_repurpose_response_bot_flag_for_custom_tools
calls InstallationConfig.value before the normalization migration
(20260418020000) runs. On instances whose serialized_value column holds
native jsonb hashes, that read raises "TypeError: no implicit conversion of
Hash into String" and aborts the whole migration chain, leaving db:migrate
stuck and production unable to deploy.

Restore SerializedValueCoder.load that tolerates either shape. dump is
updated to always emit YAML strings so new writes converge on the upstream
format; the 20260418020000 migration still normalizes the backlog.
2026-04-17 23:48:46 -03:00
gabrieljablonski
9cb045f46f fix(installation_config): normalize legacy native-hash rows to YAML strings
Some rows in installation_configs.serialized_value were written as native
jsonb objects by older code paths, while the YAML coder expects JSON-encoded
YAML strings. Reading the object-shaped rows raised "TypeError: no implicit
conversion of Hash into String" in production after the upstream 4.13.0 merge.

Convert every object-shaped row to the YAML-string shape the coder produces,
so the stock serialize :serialized_value, coder: YAML, ... works for all
rows without needing a custom coder.
2026-04-17 23:33:50 -03:00
Gabriel Jablonski
97b71915aa
fix(mailer): fall back to user account for devise email locale (#267)
Devise emails sent from unauthenticated flows (password reset,
unlock, confirmation re-send) were always rendered in the default
locale because Current.account is nil in that context. Use the
user's first account as a fallback so the email respects the
account locale configured by the user.
2026-04-17 21:58:08 -03:00
Gabriel Jablonski
f8ffe3dc48
Merge pull request #266 from fazer-ai/chore/merge-4.13.0
Chore/merge upstream 4.13.0
2026-04-17 21:34:32 -03:00
gabrieljablonski
b0e7688aad fix(jobs): resolve channel service class by name in SendReplyJob
CHANNEL_SERVICES held frozen references to service class objects, captured
when SendReplyJob was first autoloaded. In test env (cache_classes=false),
Zeitwerk reloads triggered between specs (notably by request specs)
replaced the constants with new class objects, leaving the hash pointing
to the stale ones. RSpec stubs applied to the current constant were then
bypassed when the job called service_class.new(...) through the stale
reference, causing flaky CI failures in spec/jobs/send_reply_job_spec.rb
when sharded together with V2::ReportBuilder + Captain::Preferences specs.

Storing class names as strings and resolving via constantize per call
fixes this and is the standard Rails autoload-safe pattern.
2026-04-17 21:24:29 -03:00
gabrieljablonski
35ea658548 chore(schema): auto-reinject f_unaccent block after schema dump
The Rails schema dumper can't capture CREATE FUNCTION statements, so every
db:schema:dump silently drops the execute <<~SQL block that defines
f_unaccent, breaking schema loading downstream (the functional GIN
indexes reference the missing function).

Mirror the existing schema-load enhance hook with a post-dump task that
re-injects the block between the last enable_extension and the first
create_table. Idempotent: skips when the block is already present.

Also bundle the merge-upstream skill that documents the recurring merge
patterns for this fork.
2026-04-17 18:33:13 -03:00
gabrieljablonski
4f7683e55a fix(signature): coalesce null message_signature to empty string
users.message_signature is nullable, so currentUser.message_signature can
arrive as null for accounts without a signature set. Vue prop defaults
only kick in for undefined, so the null passed through v-model to the
Editor, which called MarkdownIt.parse(null) and threw 'Input data should
be a String', breaking the profile settings page.
2026-04-17 18:20:37 -03:00
gabrieljablonski
4d155e4c01 fix(merge): CI offenses missed by pre-commit hook
- Editor.vue: consolidate duplicate defineExpose() calls introduced when
  removing signature functions (broke Vite build, cascaded into super_admin
  request specs via ActionView::Template::Error)
- omniauth_callbacks_controller + backfill migration: Rails/SaveBang
  autocorrect (offenses live in unchanged upstream files, so pre-commit
  hook skipped them; CI runs rubocop project-wide)
2026-04-17 16:40:00 -03:00
gabrieljablonski
112385fd9e Merge branch 'main' into chore/merge-4.13.0
Resolves 26 conflicts via manual review. Key decisions:

- signature: kept fork's send-time architecture (PR #79), discarded upstream's
  editor-manipulation functions
- WhatsApp incoming: combined fork's two-layer locking (source_id + contact
  phone) with upstream's blocked-contact drop. Fixed pre-existing regression
  where echoes were silently dropped
- InstallationConfig: upstream's simplified coder (validated against legacy
  YAML-in-jsonb data)
- schema.rb: regenerated, stripped kanban tables from other branches,
  restored f_unaccent SQL function
2026-04-17 16:23:47 -03:00
Sojan Jose
5e82f24be5 Merge branch 'release/4.13.0' into develop 2026-04-16 19:03:04 +05:30
Sojan Jose
e123a4e500 Bump version to 4.13.0 2026-04-16 19:02:23 +05:30
Sojan Jose
135be52431
feat: Introduce last responding agent option to automation assign agent (#12326)
Introduce a `Last Responding Agent` options to assign_agents action in
automations to cover the following use cases.

- Assign conversations to first responding agent : ( automation message
created at , if assignee is nil, assign last responding agent )
- Ensure conversations are not resolved with out an assignee : (
automation conversation resolved at : if assignee is nil, assign last
responding agent )

and potential other cases.

fixes: #1592
2026-04-16 18:54:35 +05:30
Captain
03c10ba147
chore: Update translations (#14080)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-04-16 18:12:33 +05:30
Gatesby2026
aa2e8f99e4
fix(i18n): correct zh/zh_CN conversation assignment message translations (#14033)
## Summary

The `assignee_name` and `user_name` variables are swapped in the Chinese
(zh/zh_CN) locale files for conversation assignment activity messages,
causing the rendered text to show the wrong person as the assignee.

### Before (incorrect)

| Template | English (correct) | Chinese (incorrect) |
|---|---|---|
| `assignee.assigned` | Assigned to **AgentA** by **Admin** | 由
**AgentA** 分配给 **Admin** |
| `team.assigned` | Assigned to **TeamX** by **Admin** | 由 **TeamX** 分配给
**Admin** |
| `team.assigned_with_assignee` | Assigned to **AgentA** via **TeamX**
by **Admin** | 由 **AgentA** 分配给 **TeamX** 团队的 **Admin** |

The Chinese text reads as if the conversation was assigned **to Admin**
(the API caller), when it was actually assigned **to AgentA**.

### After (correct)

| Template | Chinese (fixed) |
|---|---|
| `assignee.assigned` | 由 **Admin** 分配给 **AgentA** |
| `team.assigned` | 由 **Admin** 分配给 **TeamX** |
| `team.assigned_with_assignee` | 由 **Admin** 通过 **TeamX** 团队分配给
**AgentA** |

Now correctly matches the English template semantics.

## Files Changed

- `config/locales/zh_CN.yml` — 3 lines
- `config/locales/zh.yml` — 3 lines

## How to Verify

1. Set locale to `zh_CN`
2. Have an admin assign a conversation to an agent
3. Check the activity message in the conversation — the assignee name
should appear after "分配给", not before it

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-04-16 16:34:20 +05:30
Sojan Jose
aee979ee0b
fix: add explicit remove assignment actions to macros and automations (#12172)
This updates macros and automations so agents can explicitly remove
assigned agents or teams, while keeping the existing `Assign -> None`
flow working for backward compatibility.

Fixes: #7551
Closes: #7551

## Why
The original macro change exposed unassignment only through `Assign ->
None`, which made macros behave differently from automations and left
the explicit remove actions inconsistent across the product. This keeps
the lower-risk compatibility path and adds the explicit remove actions
requested in review.

## What this change does
- Adds `Remove Assigned Agent` and `Remove Assigned Team` as explicit
actions in macros.
- Adds the same explicit remove actions in automations.
- Keeps `Assign Agent -> None` and `Assign Team -> None` working for
existing behavior and stored payloads.
- Preserves backward compatibility for existing macro and automation
execution payloads.
- Downmerges the latest `develop` and resolves the conflicts while
keeping both the new remove actions and current `develop` behavior.

## Validation
- Verified both remove actions are available and selectable in the macro
editor.
- Verified both remove actions are available and selectable in the
automation builder.
- Applied a disposable macro with `Remove Assigned Agent` and `Remove
Assigned Team` on a real conversation and confirmed both fields were
cleared.
- Applied a disposable macro with `Assign Agent -> None` and `Assign
Team -> None` on a real conversation and confirmed both fields were
still cleared.
2026-04-16 15:57:41 +05:30
Vishnu Narayanan
72b8a31f2d
fix: handle users being stuck on is_creating billing flow (#12750)
Fixes https://linear.app/chatwoot/issue/CW-5880/handle-customers-being-stuck-on-biling-page
2026-04-16 13:22:31 +05:30
Sivin Varghese
48533e2a5d
fix: strip markdown hard-break backslashes from webhook payloads (#13950) 2026-04-16 13:19:35 +05:30
Sivin Varghese
b5264a2560
feat: Adds the ability to resize the editor (#13916)
# Pull Request Template

## Description

This PR adds support for resizing the reply editor up to nearly half the
screen height. It also deprecates the old modal-based pop-out reply box,
clicking the same button now expands the editor inline. Users can adjust
the height using the slider or the expand button.


## Type of change

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

## How Has This Been Tested?

### Loom video
https://www.loom.com/share/be27e1c06d19475ab404289710b3b0da


## 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
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
2026-04-16 12:37:56 +05:30
rotsen
98cf1ce9f6
fix(bulk-select): limit select-all to visible items; add secondary slot (#12891)
Update BulkSelectBar to compute selection state (indeterminate/all) from
visible item IDs and only toggle selection for visible items. Preserve
existing selection for off-screen items when toggling, and guard against
empty visibility. Add detection/rendering for an optional
secondary-actions slot and adjust layout/divider. Also fix
ContactsBulkActionBar selection logic to determine "all selected" by
verifying every visible ID is in the selection. These changes ensure
correct select-all behavior with filtered/visible lists and support
additional UI actions.



https://github.com/user-attachments/assets/d06b78d1-a64a-4c0c-a82a-f870140236c7


# Pull Request Template

## Description

Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## 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.


## 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

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-04-16 12:22:53 +05:30
Sivin Varghese
5eee331da3
feat: add slash command menu to article editor (#14035) 2026-04-16 11:27:59 +05:30
Sivin Varghese
edd0fc98db
feat: Table support in article editor (#13974) 2026-04-16 11:23:10 +05:30
Gabriel Jablonski
cc008951db
fix(sidebar): improve active child route matching logic (#13121) 2026-04-16 10:57:16 +05:30
Aakash Bakhle
97dae52841
fix: use committed model registry for RubyLLM (#14067)
RubyLLM bundles a static models.json that doesn't know about models
released after the gem was published. Self-hosted users configuring
newer models hit ModelNotFoundError.

Added a rake task that refreshes the registry from models.dev and saves
to disk. ~~Called during Docker image build so every deploy gets fresh
model data. Falls back silently to the bundled registry if models.dev is
unreachable.~~

Commit the models.json file to code so it is available across
deployments.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-04-16 10:28:38 +05:30
Aakash Bakhle
5264de24b0
feat: migrations for document auto-sync [AI-141] (#14041)
# Pull Request Template

## Description

Add migrations for document auto-sync

Fixes # (issue)

## Type of change

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

## How Has This Been Tested?
locally

## 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
2026-04-15 17:56:10 +05:30
Sojan Jose
b96bf41234
chore: Enable Participating tab for conversations (#11714)
## Summary

This PR enables the **Participating** conversation view in the main
sidebar and keeps the behavior aligned with existing conversation views.

## What changed

- Added **Participating** under Conversations in the new sidebar.
- Added a guard in conversation realtime `addConversation` flow so
generic `conversation.created` events are not injected while the user is
on Participating view.
- Added participating route mapping in conversation-list redirect helper
so list redirects resolve correctly to `/participating/conversations`.

## Scope notes

- Kept changes minimal and consistent with current `develop` behavior.
- No additional update-event filtering was added beyond what existing
views already do.

---------


Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-04-15 17:03:39 +05:30
Tanmay Deep Sharma
3f9f054c43
fix: drop WhatsApp incoming messages from blocked contacts (#14061)
## Linear ticket

https://linear.app/chatwoot/issue/CW-6839/blocked-contact-can-still-send-messages-to-whatsapp-inbox

## Description

Drop WhatsApp incoming messages  from blocked contacts

## Type of change

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

## How Has This Been Tested?

- Incoming messages for blocked contacts 

## 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

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-04-15 13:42:48 +07:00
dependabot[bot]
8e5d4f4d23
chore(deps): bump axios from 1.13.6 to 1.15.0 (#14051)
Bumps [axios](https://github.com/axios/axios) from 1.13.6 to 1.15.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/releases">axios's
releases</a>.</em></p>
<blockquote>
<h2>v1.15.0</h2>
<p>This release delivers two critical security patches, adds runtime
support for Deno and Bun, and includes significant CI hardening,
documentation improvements, and routine dependency updates.</p>
<h2>⚠️ Important Changes</h2>
<ul>
<li><strong>Deprecation:</strong> <code>url.parse()</code> usage has
been replaced to address Node.js deprecation warnings. If you are on a
recent version of Node.js, this resolves console warnings you may have
been seeing. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10625">#10625</a></strong>)</li>
</ul>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Proxy Handling:</strong> Fixed a <code>no_proxy</code>
hostname normalisation bypass that could lead to Server-Side Request
Forgery (SSRF). (<strong><a
href="https://redirect.github.com/axios/axios/issues/10661">#10661</a></strong>)</li>
<li><strong>Header Injection:</strong> Fixed an unrestricted cloud
metadata exfiltration vulnerability via a header injection chain.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10660">#10660</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>Runtime Support:</strong> Added compatibility checks and
documentation for Deno and Bun environments. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10652">#10652</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10653">#10653</a></strong>)</li>
</ul>
<h2>🔧 Maintenance &amp; Chores</h2>
<ul>
<li><strong>CI Security:</strong> Hardened workflow permissions to least
privilege, added the <code>zizmor</code> security scanner, pinned action
versions, and gated npm publishing with OIDC and environment protection.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10618">#10618</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10619">#10619</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10627">#10627</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10637">#10637</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10666">#10666</a></strong>)</li>
<li><strong>Dependencies:</strong> Bumped
<code>serialize-javascript</code>, <code>handlebars</code>,
<code>picomatch</code>, <code>vite</code>, and
<code>denoland/setup-deno</code> to latest versions. Added a 7-day
Dependabot cooldown period. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10574">#10574</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10572">#10572</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10568">#10568</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10663">#10663</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10664">#10664</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10665">#10665</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10669">#10669</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10670">#10670</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10616">#10616</a></strong>)</li>
<li><strong>Documentation:</strong> Unified docs, improved
<code>beforeRedirect</code> credential leakage example, clarified
<code>withCredentials</code>/<code>withXSRFToken</code> behaviour,
HTTP/2 support notes, async/await timeout error handling, header case
preservation, and various typo fixes. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10649">#10649</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10624">#10624</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/7452">#7452</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/7471">#7471</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10654">#10654</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10644">#10644</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10589">#10589</a></strong>)</li>
<li><strong>Housekeeping:</strong> Removed stale files, regenerated
lockfile, and updated sponsor scripts and blocks. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10584">#10584</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10650">#10650</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10582">#10582</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10640">#10640</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10659">#10659</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10668">#10668</a></strong>)</li>
<li><strong>Tests:</strong> Added regression coverage for urlencoded
<code>Content-Type</code> casing. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10573">#10573</a></strong>)</li>
</ul>
<h2>🌟 New Contributors</h2>
<p>We are thrilled to welcome our new contributors. Thank you for
helping improve Axios:</p>
<ul>
<li><strong><a
href="https://github.com/raashish1601"><code>@​raashish1601</code></a></strong>
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10573">#10573</a></strong>)</li>
<li><strong><a
href="https://github.com/Kilros0817"><code>@​Kilros0817</code></a></strong>
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10625">#10625</a></strong>)</li>
<li><strong><a
href="https://github.com/ashstrc"><code>@​ashstrc</code></a></strong>
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10624">#10624</a></strong>)</li>
<li><strong><a
href="https://github.com/Abhi3975"><code>@​Abhi3975</code></a></strong>
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10589">#10589</a></strong>)</li>
<li><strong><a
href="https://github.com/theamodhshetty"><code>@​theamodhshetty</code></a></strong>
(<strong><a
href="https://redirect.github.com/axios/axios/issues/7452">#7452</a></strong>)</li>
</ul>
<h2>v1.14.0</h2>
<p>This release focuses on compatibility fixes, adapter stability
improvements, and test/tooling modernisation.</p>
<h2>⚠️ Important Changes</h2>
<ul>
<li><strong>Breaking Changes:</strong> None identified in this
release.</li>
<li><strong>Action Required:</strong> If you rely on env-based proxy
behaviour or CJS resolution edge-cases, validate your integration after
upgrade (notably <code>proxy-from-env</code> v2 alignment and
<code>main</code> entry compatibility fix).</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>Runtime Features:</strong> No new end-user features were
introduced in this release.</li>
<li><strong>Test Coverage Expansion:</strong> Added broader smoke/module
test coverage for CJS and ESM package usage. (<a
href="https://redirect.github.com/axios/axios/pull/7510">#7510</a>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>Headers:</strong> Trim trailing CRLF in normalised header
values. (<a
href="https://redirect.github.com/axios/axios/pull/7456">#7456</a>)</li>
<li><strong>HTTP/2:</strong> Close detached HTTP/2 sessions on timeout
to avoid lingering sessions. (<a
href="https://redirect.github.com/axios/axios/pull/7457">#7457</a>)</li>
<li><strong>Fetch Adapter:</strong> Cancel <code>ReadableStream</code>
created during request-stream capability probing to prevent async
resource leaks. (<a
href="https://redirect.github.com/axios/axios/pull/7515">#7515</a>)</li>
<li><strong>Proxy Handling:</strong> Fixed env proxy behavior with
<code>proxy-from-env</code> v2 usage. (<a
href="https://redirect.github.com/axios/axios/pull/7499">#7499</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/blob/v1.x/CHANGELOG.md">axios's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2><a
href="https://github.com/axios/axios/compare/v1.13.2...v1.13.3">1.13.3</a>
(2026-01-20)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>http2:</strong> Use port 443 for HTTPS connections by
default. (<a
href="https://redirect.github.com/axios/axios/issues/7256">#7256</a>)
(<a
href="d7e6065346">d7e6065</a>)</li>
<li><strong>interceptor:</strong> handle the error in the same
interceptor (<a
href="https://redirect.github.com/axios/axios/issues/6269">#6269</a>)
(<a
href="5945e40bb1">5945e40</a>)</li>
<li>main field in package.json should correspond to cjs artifacts (<a
href="https://redirect.github.com/axios/axios/issues/5756">#5756</a>)
(<a
href="7373fbff24">7373fbf</a>)</li>
<li><strong>package.json:</strong> add 'bun' package.json 'exports'
condition. Load the Node.js build in Bun instead of the browser build
(<a
href="https://redirect.github.com/axios/axios/issues/5754">#5754</a>)
(<a
href="b89217e3e9">b89217e</a>)</li>
<li>silentJSONParsing=false should throw on invalid JSON (<a
href="https://redirect.github.com/axios/axios/issues/7253">#7253</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/7257">#7257</a>)
(<a
href="7d19335e43">7d19335</a>)</li>
<li>turn AxiosError into a native error (<a
href="https://redirect.github.com/axios/axios/issues/5394">#5394</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/5558">#5558</a>)
(<a
href="1c6a86dd2c">1c6a86d</a>)</li>
<li><strong>types:</strong> add handlers to AxiosInterceptorManager
interface (<a
href="https://redirect.github.com/axios/axios/issues/5551">#5551</a>)
(<a
href="8d1271b49f">8d1271b</a>)</li>
<li><strong>types:</strong> restore AxiosError.cause type from unknown
to Error (<a
href="https://redirect.github.com/axios/axios/issues/7327">#7327</a>)
(<a
href="d8233d9e8e">d8233d9</a>)</li>
<li>unclear error message is thrown when specifying an empty proxy
authorization (<a
href="https://redirect.github.com/axios/axios/issues/6314">#6314</a>)
(<a
href="6ef867e684">6ef867e</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>add <code>undefined</code> as a value in AxiosRequestConfig (<a
href="https://redirect.github.com/axios/axios/issues/5560">#5560</a>)
(<a
href="095033c626">095033c</a>)</li>
<li>add automatic minor and patch upgrades to dependabot (<a
href="https://redirect.github.com/axios/axios/issues/6053">#6053</a>)
(<a
href="65a7584eda">65a7584</a>)</li>
<li>add Node.js coverage script using c8 (closes <a
href="https://redirect.github.com/axios/axios/issues/7289">#7289</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/7294">#7294</a>)
(<a
href="ec9d94e9f8">ec9d94e</a>)</li>
<li>added copilot instructions (<a
href="3f83143bfe">3f83143</a>)</li>
<li>compatibility with frozen prototypes (<a
href="https://redirect.github.com/axios/axios/issues/6265">#6265</a>)
(<a
href="860e03396a">860e033</a>)</li>
<li>enhance pipeFileToResponse with error handling (<a
href="https://redirect.github.com/axios/axios/issues/7169">#7169</a>)
(<a
href="88d7884254">88d7884</a>)</li>
<li><strong>types:</strong> Intellisense for string literals in a
widened union (<a
href="https://redirect.github.com/axios/axios/issues/6134">#6134</a>)
(<a
href="f73474d02c">f73474d</a>),
closes <a
href="https://redirect.github.com//redirect.github.com/microsoft/TypeScript/issues/33471/issues/issuecomment-1376364329">microsoft/TypeScript#33471</a></li>
</ul>
<h3>Reverts</h3>
<ul>
<li>Revert &quot;fix: silentJSONParsing=false should throw on invalid
JSON (<a
href="https://redirect.github.com/axios/axios/issues/7253">#7253</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/7">#7</a>…&quot;
(<a
href="https://redirect.github.com/axios/axios/issues/7298">#7298</a>)
(<a
href="a4230f5581">a4230f5</a>),
closes <a
href="https://redirect.github.com/axios/axios/issues/7253">#7253</a> <a
href="https://redirect.github.com/axios/axios/issues/7">#7</a> <a
href="https://redirect.github.com/axios/axios/issues/7298">#7298</a></li>
<li><strong>deps:</strong> bump peter-evans/create-pull-request from 7
to 8 in the github-actions group (<a
href="https://redirect.github.com/axios/axios/issues/7334">#7334</a>)
(<a
href="2d6ad5e48b">2d6ad5e</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a href="https://github.com/ashvin2005"
title="+1752/-4 ([#7218](https://github.com/axios/axios/issues/7218)
[#7218](https://github.com/axios/axios/issues/7218) )">Ashvin
Tiwari</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/mochinikunj"
title="+940/-12 ([#7294](https://github.com/axios/axios/issues/7294)
[#7294](https://github.com/axios/axios/issues/7294) )">Nikunj
Mochi</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/imanchalsingh"
title="+544/-102 ([#7169](https://github.com/axios/axios/issues/7169)
[#7185](https://github.com/axios/axios/issues/7185) )">Anchal
Singh</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/jasonsaayman"
title="+317/-73 ([#7334](https://github.com/axios/axios/issues/7334)
[#7298](https://github.com/axios/axios/issues/7298)
)">jasonsaayman</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/brodo"
title="+99/-120 ([#5558](https://github.com/axios/axios/issues/5558)
)">Julian Dax</a></li>
<li><!-- raw HTML omitted --> <a
href="https://github.com/AKASHDHARDUBEY" title="+167/-0
([#7287](https://github.com/axios/axios/issues/7287)
[#7288](https://github.com/axios/axios/issues/7288) )">Akash Dhar
Dubey</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/madhumitaaa"
title="+20/-68 ([#7198](https://github.com/axios/axios/issues/7198)
)">Madhumita</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/Tackoil"
title="+80/-2 ([#6269](https://github.com/axios/axios/issues/6269)
)">Tackoil</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/justindhillon"
title="+41/-41 ([#6324](https://github.com/axios/axios/issues/6324)
[#6315](https://github.com/axios/axios/issues/6315) )">Justin
Dhillon</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/Rudrxxx"
title="+71/-2 ([#7257](https://github.com/axios/axios/issues/7257)
)">Rudransh</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/WuMingDao"
title="+36/-36 ([#7215](https://github.com/axios/axios/issues/7215)
)">WuMingDao</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/codenomnom"
title="+70/-0 ([#7201](https://github.com/axios/axios/issues/7201)
[#7201](https://github.com/axios/axios/issues/7201)
)">codenomnom</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/Nandann018-ux"
title="+60/-10 ([#7272](https://github.com/axios/axios/issues/7272)
)">Nandan Acharya</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/KernelDeimos"
title="+22/-40 ([#7042](https://github.com/axios/axios/issues/7042)
)">Eric Dubé</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/tiborpilz"
title="+40/-4 ([#5551](https://github.com/axios/axios/issues/5551)
)">Tibor Pilz</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/joaoGabriel55"
title="+31/-4 ([#6314](https://github.com/axios/axios/issues/6314)
)">Gabriel Quaresma</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/turadg"
title="+23/-6 ([#6265](https://github.com/axios/axios/issues/6265)
)">Turadg Aleahmad</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="772a4e54ec"><code>772a4e5</code></a>
chore(release): prepare release 1.15.0 (<a
href="https://redirect.github.com/axios/axios/issues/10671">#10671</a>)</li>
<li><a
href="4b071371be"><code>4b07137</code></a>
chore(deps-dev): bump vite from 8.0.0 to 8.0.5 in /tests/smoke/esm (<a
href="https://redirect.github.com/axios/axios/issues/10663">#10663</a>)</li>
<li><a
href="51e57b39db"><code>51e57b3</code></a>
chore(deps-dev): bump vite from 8.0.2 to 8.0.5 (<a
href="https://redirect.github.com/axios/axios/issues/10664">#10664</a>)</li>
<li><a
href="fba1a77930"><code>fba1a77</code></a>
chore(deps-dev): bump vite from 8.0.2 to 8.0.5 in /tests/module/esm (<a
href="https://redirect.github.com/axios/axios/issues/10665">#10665</a>)</li>
<li><a
href="0bf6e28eac"><code>0bf6e28</code></a>
chore(deps): bump denoland/setup-deno in the github-actions group (<a
href="https://redirect.github.com/axios/axios/issues/10669">#10669</a>)</li>
<li><a
href="8107157c57"><code>8107157</code></a>
chore(deps-dev): bump the development_dependencies group with 4 updates
(<a
href="https://redirect.github.com/axios/axios/issues/10670">#10670</a>)</li>
<li><a
href="e66530e330"><code>e66530e</code></a>
ci: require npm-publish environment for releases (<a
href="https://redirect.github.com/axios/axios/issues/10666">#10666</a>)</li>
<li><a
href="49f23cbfe4"><code>49f23cb</code></a>
chore(sponsor): update sponsor block (<a
href="https://redirect.github.com/axios/axios/issues/10668">#10668</a>)</li>
<li><a
href="363185461b"><code>3631854</code></a>
fix: unrestricted cloud metadata exfiltration via header injection chain
(<a
href="https://redirect.github.com/axios/axios/issues/10">#10</a>...</li>
<li><a
href="fb3befb6da"><code>fb3befb</code></a>
fix: no_proxy hostname normalization bypass leads to ssrf (<a
href="https://redirect.github.com/axios/axios/issues/10661">#10661</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/axios/axios/compare/v1.13.6...v1.15.0">compare
view</a></li>
</ul>
</details>
<details>
<summary>Install script changes</summary>
<p>This version modifies <code>prepare</code> script that runs during
installation. Review the package contents before updating.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=axios&package-manager=npm_and_yarn&previous-version=1.13.6&new-version=1.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2026-04-15 00:44:54 +05:30
Gabriel Jablonski
4f33deb978
release v4.12.0-fazer-ai.54 (#265)
* fix(whatsapp): preserve green color on chat list typing indicator

The messagePreviewClass computed includes text-n-slate-11/12, which
overrode text-green-500 in the compiled Tailwind order. Split padding
into a dedicated computed and apply only it on the typing preview.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(whatsapp): clear contact typing indicator when message is received

Dispatch CONVERSATION_TYPING_OFF after a new incoming message is
persisted from baileys messages.upsert, so the dashboard clears the
typing/recording indicator without waiting for a paused/unavailable
presence event.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(conversations): dispatch messages.read event when unread messages exist

The throttling introduced in upstream #13355 returned early for the
"has unread" branches, skipping dispatch_messages_read_event. That
meant the MESSAGES_READ event only fired when there were no unread
messages, so ChannelListener never called channel.read_messages on
the baileys provider when an agent actually read a conversation.

Consolidate the unread/throttle guard so the dispatch runs in all
paths where update_last_seen_on_conversation runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:51:31 -03:00
Sivin Varghese
64f6bfc811
feat: Inline edit support for contact info (#13976)
# Pull Request Template

## Description

This PR adds inline editing support for contact name, phone number,
email, and company fields in the conversation contact sidebar

## Type of change

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

## How Has This Been Tested?

**Screencast**


https://github.com/user-attachments/assets/e9f8e37d-145b-4736-b27a-eb9ea66847bd



## 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: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-04-14 16:53:40 +04:00
Sivin Varghese
72c9e1775b
fix: Prevent article editor from resetting content while typing (#14014)
# Pull Request Template

## Description


### Description

This PR fixes an issue where the editor would reset content and move the
cursor while typing. The issue was caused by a dual debounce setup
(400ms + 2500ms) that saved content and then overwrote local state with
stale API responses while the user was still typing.

### What changed

* Editor now uses local state (`localTitle`, `localContent`) as the
source of truth while editing
* Vuex store is only used on initial load or navigation
* Replaced dual debounce with a single 500ms debounce (fewer API calls)
* `UPDATE_ARTICLE` now merges updates instead of replacing the article
  * Prevents status changes from wiping unsaved content
* Removed `updateAsync` for a simpler update flow

### How it works

User types
→ local ref updates immediately (editor reads from this)
→ 500ms debounce triggers
→ dispatches `articles/update`
→ API persists the change
→ on success: store merges the response (used by other components)
→ editor remains unaffected (continues using local state)



Fixes
https://linear.app/chatwoot/issue/CW-6727/better-syncing-of-content-the-editor-randomly-updates-the-content

## Type of change

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

## How Has This Been Tested?

1. Open any Help Center article for editing
2. Type continuously for a few seconds — content should not reset or
jump
3. Change article status (publish/archive/draft) while editing — content
should remain intact
4. Test on a slow network (use DevTools throttling) — typing should
remain smooth


## 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
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-04-14 16:48:38 +04:00
Petterson
b7b6e67df7
fix(captain): localize AI summary to account language (#13790)
AI-generated summaries now respect the account's language setting.
Previously, summaries were always returned in English regardless of the
user's configured language, making section headings like "Customer
Intent" and "Action Items" appear in English even for non-English
accounts.

Previous behavior:
<img width="1336" height="790" alt="image"
src="https://github.com/user-attachments/assets/5df8b78b-1218-438d-9578-a806b5cb94ac"
/>


Current Behavior: 
<img width="1253" height="372" alt="image"
src="https://github.com/user-attachments/assets/ae932c97-06da-4baf-9f77-9719bc9162e8"
/>


## What changed
- Added explicit account locale to the AI system prompt in
`Captain::SummaryService`
- Updated the summary prompt template to instruct the model to translate
section headings

## How to test
1. Configure an account with a non-English language (e.g., Portuguese)
2. Open a conversation with messages
3. Use the Copilot "Summarize" feature
4. Verify that section headings ("Customer Intent", "Conversation
Summary", etc.) appear in the account's language

---------

Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
2026-04-14 17:36:10 +05:30
Sivin Varghese
288c1cb757
fix: Respect app direction for incoming email content (#14011) 2026-04-14 13:45:34 +05:30
Sivin Varghese
a8c8b38f51
fix: create article on title blur instead of debounce (#14037) 2026-04-13 23:23:25 +05:30
gabrieljablonski
02f70ff611 fix: resolve CI failures from presence update method collision
Rename consolidate_contact to consolidate_presence_contact in
PresenceUpdate to avoid overriding the 3-arg version from
GroupContactMessageHandler when both modules are mixed into
IncomingMessageBaileysService.

Fix CSAT spec side effects where conversation callbacks triggered
ActivityMessageJob unexpectedly during test setup.
2026-04-13 13:41:58 -03:00
Muhsin Keloth
f422c83c26
feat: Add unified Call model for voice calling (#14026)
Adds a Call model to track voice call state across providers (Twilio,
WhatsApp). This replaces storing call data in
conversation.additional_attributes and provides a foundation for call
analytics multi-call-per-conversation support, and future voice
providers.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-13 20:28:09 +04:00
gabrieljablonski
104a05a511 fix: fix CI failures from presence subscribe changes
- Use optional chaining on presenceSubscribe call in setActiveChat to
  handle missing mock in tests
- Update setup_channel_provider spec stubs to include autoPresenceSubscribe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:14:31 -03:00
Gabriel Jablonski
11e9932e9b
feat(whatsapp): show contact typing and recording indicators via baileys presence (#264)
* feat(whatsapp): show contact typing and recording indicators via baileys presence

Subscribe to WhatsApp presence updates via the baileys-api provider to
display real-time typing and recording indicators in the dashboard.

- Handle presence.update webhook events (composing, recording, paused,
  available) and broadcast via ActionCable
- Add conversation.recording event to ActionCable, webhook, and channel
  listeners for parity with typing_on/typing_off
- Show "typing..." / "recording..." in green text on the chat list,
  replacing the message preview
- Show "X is typing" / "X is recording audio" in the conversation view
- Add presence_subscribe provider config option (default off) to gate
  all subscription calls to the baileys-api
- Subscribe to presence on conversation open and periodically (1 min)
  for the top 10 chat list conversations
- Consolidate contact LID from presence.update jidAlt payload
- Prevent echo-back of contact typing events to the channel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback

- Filter chat list typing indicator to contact-only events
- Add dedupe to presence subscribe bulk calls
- Use strong parameters for conversation_ids
- Remove redundant YAML quotes in swagger webhook enum

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback

- Extract phone from data[:id] when JID is @s.whatsapp.net (fallback
  when jidAlt is absent)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback

- Filter recording users in getTypingUsersText to show correct names
- Add 10s timeout to presence_subscribe HTTP request

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scope typing timer per user instead of per conversation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: make presence subscribe best-effort with rescue per channel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback

- Add messagePreviewClass to typing preview for consistent padding
- Fix specs to use WebMock assertions instead of instance spying

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:38:11 -03:00
Tanmay Deep Sharma
722e68eecb
fix: validate support_email format and handle parse errors in mailer (#13958)
## Description

ConversationReplyMailer#parse_email calls
Mail::Address.new(email_string).address without error handling. When an
account's support_email contains a non-email string (e.g., "Smith
Smith"), the mail gem raises Mail::Field::IncompleteParseError, crashing
conversation transcript emails.

This has caused 1,056 errors on Sentry (EXTERNAL-CHATINC-JX) since Feb
25, all from a single account that has a name stored in the
support_email field instead of a valid email address.

Closes
https://linear.app/chatwoot/issue/CW-6687/mailfieldincompleteparseerror-mailaddresslist-can-not-parse-orsmith

## 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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-04-13 19:06:06 +07:00
Tanmay Deep Sharma
0592cccca9
fix: prevent lost custom_attributes updates from concurrent jsonb writes (#14040)
## Linear ticket
https://linear.app/chatwoot/issue/CW-6834/billing-upgrade-didnt-work

## Description
A `customer.subscription.updated` Stripe webhook for account 76162
returned 200 OK but did not persist the new `subscribed_quantity`. Root
cause: a race condition between the webhook handler and
`increment_response_usage` (Captain usage counter), both doing
read-modify-write on the `custom_attributes` JSONB column. The webhook
wrote `quantity: 6`, then a concurrent `save` from
`increment_response_usage` overwrote the entire hash with stale data —
restoring `quantity: 5`.

Fix: use atomic `jsonb_set` so usage counter updates only touch the
single key they care about, instead of rewriting the whole
`custom_attributes` hash. `increment_custom_attribute` also performs the
increment in SQL, making concurrent increments correct as well.

## Type of change

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

## How Has This Been Tested?

- New regression spec in `handle_stripe_event_service_spec.rb` that
simulates concurrent webhook + `increment_response_usage` and asserts
both `subscribed_quantity` and `captain_responses_usage` survive
- Existing account, billing, captain, and topup specs all pass locally

## 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-04-13 19:03:37 +07:00