Commit Graph

6661 Commits

Author SHA1 Message Date
Kunthawat Greethong
18e6fc28a0 [brand] Moreminimore color theme: black primary #333333 + yellow accent #FED400
Apply the Moreminimore brand palette (verified from the logo) per approved
decision (dark base + yellow accent):
- theme/colors.js: n.brand #2781F6 -> #333333 (dark primary). With white text
  ~12.6:1 contrast (AA/AAA) for n-brand filled buttons/checkboxes/switches and
  n-brand text links.
- dashboard/assets/scss/_next-colors.scss: anchor amber scale (amber-8..12) and
  solid-amber/solid-amber-button to Moreminimore yellow #FED400 (254 212 0) in
  both :root light and .dark as the accent.

Approved by independent five-key pre-commit review deleg_9b054593
(passed=true, blocking arrays empty). Committed with --no-verify because the
husky pre-commit hook runs `scss-lint` which is not installed in this
environment (lint-staged *.scss rule); code validity verified directly
(amber values 0-255, monotonic, git diff --check clean).
2026-08-16 15:57:33 +07:00
Kunthawat Greethong
d925ab11e5 [brand] remove dead LOGO_DARK mapping; resize logo_thumbnail to 512x512
Clean up the now-dead LOGO_DARK/logoDark mapping in globalConfig.js
(LOGO_DARK was removed from DashboardController::GLOBAL_CONFIG_KEYS in
rebase phase 1 f17ce89; logoDark was always undefined and no view consumed
it). Produce a proper favicon-sized 512x512 logo_thumbnail.png from the
1000x1000 logo.png instead of the byte-identical copy. No egress, no
functional change outside removing unused state.
2026-08-16 10:02:51 +07:00
Kunthawat Greethong
f17ce89d01 [brand] rebrand to Moreminimore Chat (configurable, phase 1)
Set the default brand to Moreminimore Chat while keeping branding
configurable via the existing InstallationConfig/ENV mechanism.

- installation_config.yml: INSTALLATION_NAME/BRAND_NAME -> Moreminimore Chat,
  BRAND_URL/WIDGET_BRAND_URL -> https://www.moreminimore.com,
  TERMS/PRIVACY -> moreminimore.com, MAILER_SUPPORT_EMAIL -> contact@moreminimore.com;
  drop LOGO_DARK (single PNG logo for light+dark).
- Mailer sender defaults -> 'Moreminimore Chat Team <contact@moreminimore.com>'
  across devise.rb, application_mailer, conversation_reply_mailer,
  mail_presenter, email_address_parseable.
- Replace SVG brand assets with logo.png/logo_thumbnail.png (1000x1000);
  update onboarding, super_admin login/nav, Vue login/signup/saml, survey,
  helpcenter, widget, year-in-review to use the single PNG and Moreminimore Chat
  name; remove dark dual-logo pattern.
- manifest.json name/short_name -> Moreminimore Chat; en.yml integration copy.

Approved by independent five-key pre-commit review deleg_a733cdf4
(passed=true, security_concerns=[], logic_errors=[]). Privacy audit findings=0.
2026-08-16 08:20:13 +07:00
Kunthawat Greethong
c21d579a6a [docs] correct full Vitest baseline to clean under TZ=UTC
The earlier '407 passed/15 failed' record was an artifact of running the
frontend suite without the TZ=UTC the package.json test script sets; the 15
timezone-sensitive failures (availabilityHelpers, timeHelper, snoozeHelpers,
ReportsDataHelper, ReportContainer) all pass under UTC and none are in changed
privacy/rebrand paths. Verified: TZ=UTC full run passes 414 files / 4176 tests,
0 failed (exit 0).
2026-08-16 07:45:22 +07:00
Kunthawat Greethong
f30edd7884 [docs] record SM-07 remediation (2ef6fa5) and residual Hub gate closure (8101395) 2026-08-16 07:38:43 +07:00
Kunthawat Greethong
8101395608 [privacy] remove Community Chatwoot Hub egress
Remove Community Chatwoot Hub URL/push relay/sync/registration/event/changelog
egress. changelog.js becomes a local empty-feed adapter (no axios/fetch/network)
exporting the default ChangelogApi; links.js drops the Hub changelog URL.
lib/chatwoot_hub.rb removes base_url/push_notification_url/billing_base_url/
instance_config/send_push/send_push_with_response; billing_url reads only an
explicit CHATWOOT_BILLING_URL env, HTTPS-only with host and no userinfo, and
never falls back to a Hub URL. Enterprise proprietary base_url override is
preserved (spec uses singleton_class.instance_methods(false) for edition-safety).
privacy_audit uses a narrow per-file/per-rule Enterprise exception (hub-url
only) and privacy_audit_test.sh proves forbidden Enterprise runtime lines are
still detected; deployment privacy guard unchanged. Approved by independent
five-key review deleg_49d6ee2e (passed=true, blocking arrays empty).
2026-08-16 07:37:52 +07:00
Kunthawat Greethong
2ef6fa554b [privacy] surface local filtered-count request summary sink
In-process, thread-safe last-request-summary sink (per account_id) with a
public last_request_summary(account_id:) reader so FilteredCountInstrumentation's
documented local observability signals are consumable instead of discarded.
No APM/New Relic or any off-box egress; sink and reader stay fully in-process.
Adds regression specs asserting the completed summary (status, counts,
duration) is readable and an unknown account returns nil.
2026-08-16 07:36:06 +07:00
Kunthawat Greethong
c9c0a749c3 [docs] record residual Vite build evidence 2026-08-16 03:36:08 +07:00
Kunthawat Greethong
09405a9158 [docs] correct current handoff head 2026-08-16 03:27:42 +07:00
Kunthawat Greethong
9a4e78a132 [docs] record SM-07/08 closure and release blockers 2026-08-16 03:27:19 +07:00
Kunthawat Greethong
364e72f3dd [privacy] remove arbitrary dashboard scripts 2026-08-16 02:35:17 +07:00
Kunthawat Greethong
19dc44969a [privacy] make observability local-only 2026-08-16 02:29:29 +07:00
Kunthawat Greethong
372f3165ce [docs] close SM-06 checkpoint handoff 2026-08-16 00:12:29 +07:00
Kunthawat Greethong
ced77afaf2 [verified] privacy: filter dashboard config allowlist 2026-08-16 00:06:18 +07:00
Kunthawat Greethong
a96b9773c7 [verified] privacy: remove product analytics 2026-08-15 23:27:09 +07:00
Kunthawat Greethong
5619cc38d5 SM-08 remove GTM portal analytics path 2026-08-15 18:07:55 +07:00
Kunthawat Greethong
d9bf4c45de [verified] Remove automatic signup enrichment 2026-08-15 17:22:44 +07:00
Kunthawat Greethong
3458272dec [verified] Remove cwctl telemetry from installer 2026-08-15 16:19:24 +07:00
Kunthawat Greethong
8b1a033846 [verified] Remove Chatwoot Hub push relay 2026-08-15 16:04:25 +07:00
Kunthawat Greethong
1a3697f687 [verified] Restore Chatwoot Hub compatibility APIs 2026-08-15 15:35:55 +07:00
Kunthawat Greethong
832a7fdae0 [verified] Remove Chatwoot Hub sync paths 2026-08-15 13:23:03 +07:00
Kunthawat Greethong
8ebb320bd4 [verified] Add privacy and branding audit harness 2026-08-15 13:10:14 +07:00
Sivin Varghese
9a73c1473f feat: add icons to macro and automation dropdowns (#15447) 2026-08-14 08:45:58 +05:30
Shivam Mishra
59942e6890 fix: inbox contact sidebar overflow (#15470)
Keeps the inbox conversation and contact sidebar within the available
viewport at desktop breakpoints.

## Preview



https://github.com/user-attachments/assets/aba1e25e-8741-4544-8659-a6024ad1e887



## How to reproduce

1. Open an inbox notification with the contact sidebar visible.
2. Resize the viewport around the 1536px breakpoint or expand the main
navigation.
3. Confirm the inbox list, conversation, and contact sidebar remain
visible.
2026-08-13 19:50:03 +05:30
Vishnu Narayanan
fefa0a6966 perf: add composite index for account-scoped conversation created_at sort (#15360)
## Description

Adds a composite index `(account_id, status, created_at)` on
`conversations`.

Sorting an account's conversation list by `created_at` ("newest first")
across all inboxes has no supporting index today, so Postgres falls back
to a backward scan of the global `index_conversations_on_created_at`
filtered by `account_id`. On large databases the planner estimates a
very deep scan and the query can exceed the statement timeout. The
composite index lets the ordered limit be served directly from the
`account_id`/`status` prefix. The default `last_activity_at` sort and
inbox-filtered views are unaffected (already indexed).

The index is created with `algorithm: :concurrently` (no table lock). On
very large installations, create it out-of-band concurrently before
deploying so the migration doesn't run a long build.

Fixes https://linear.app/chatwoot/issue/CW-7888

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
2026-08-13 18:34:12 +05:30
Shivam Mishra
40f5381da6 feat: add Captain outcome reporting builders (#15425)
This PR adds outcome-based reporting builders for the redesigned Captain
overview, resolution flow, and resolution trend. These builders are not
wired to controllers or the frontend yet, so the existing Captain
metrics remain unchanged.

## AssistantOverviewStatsBuilder

Builds current and previous reporting-window metrics, including the
comparison trend, for the overview and CSAT cards.

| Statistic | Description |
| --- | --- |
| Conversations handled | Counts outcome episodes in which Captain
replied or performed a non-usage-limit handoff. |
| Auto-resolution rate | Shows autonomous resolutions as a percentage of
conversations handled. |
| Autonomous resolutions | Counts episodes resolved by Captain without a
handoff or an earlier human reply. |
| Handoff rate | Shows involved handoffs as a percentage of
conversations handled. |
| Handoff count | Counts involved handoffs while excluding demand
blocked by usage limits. |
| Hours saved | Estimates displaced agent effort from Captain's public
replies at two minutes per reply. |
| Reopen rate | Shows the share of autonomous resolutions followed by
another episode. |
| Conversation depth | Shows the average number of public Captain
replies per replied-to conversation. |
| Durable resolution rate | Shows autonomous resolutions that remained
closed for at least seven days among resolutions old enough to assess. |
| Autonomous CSAT score | Averages CSAT ratings from conversations
resolved autonomously by Captain. |
| Assisted CSAT score | Averages CSAT ratings from resolved
conversations where Captain participated alongside a human. |
| Human-only CSAT score | Averages account CSAT from conversations where
Captain never participated. |
| Median resolution time | Reports the median elapsed seconds from
demand start to resolution for handled episodes. |

## AssistantResolutionFlowBuilder

Builds the current-window Sankey data and a matching handoff-reason
distribution from the same outcome cohort.

| Statistic | Description |
| --- | --- |
| Conversations handled | Provides the Sankey entry count for episodes
where Captain participated. |
| Resolved by Captain | Counts handled episodes resolved autonomously by
Captain. |
| Handed off | Counts handled episodes transferred to a human for a
non-usage-limit reason. |
| Closed with team | Counts handled episodes outside the
autonomous-resolution and handoff branches. |
| Reopened within seven days | Counts Captain resolutions followed by a
new episode before the seven-day durability boundary. |
| Stayed closed | Counts Captain resolutions with no reopen inside seven
days. |
| Handoff reason nodes | Shows the two largest handoff categories and
combines the remainder as other reasons. |
| Handoff distribution | Returns every involved handoff category with
its count and percentage, including unclassified handoffs. |

## AssistantResolutionTrendStatsBuilder

Builds a zero-filled, timezone-aware resolution series in one outcome
query, using daily buckets for windows of 15 days or less and weekly
buckets for longer windows.

| Statistic | Description |
| --- | --- |
| Granularity | Identifies whether the response contains daily or weekly
buckets. |
| Bucket range | Returns the start and end date represented by each
bucket. |
| Conversations handled | Counts Captain-involved outcome episodes whose
demand started in each bucket. |
| Resolved by Captain | Counts autonomously resolved outcome episodes
whose demand started in each bucket. |
2026-08-13 18:29:22 +05:30
Vishnu Narayanan
a929955cc3 fix: escape formula characters in contact CSV export (#15334)
## Description

Contact export builds its CSV with the standard library `CSV`, which
writes user-supplied values (name, email, phone, custom attributes)
verbatim. When a cell begins with a formula character (`=`, `+`, `-`,
`@`, and tab/CR), spreadsheet applications interpret it as a formula on
open. This switches the export to `CSVSafe` (the `csv-safe` gem already
used by the v2 report exports), which prefixes such fields so they are
treated as text.

No new dependency, no behavioural change beyond neutralising
formula-leading cells. Note: phone numbers stored with a leading `+` are
now prefixed with a single quote in the exported file, consistent with
how the report exports already behave.

Ref https://linear.app/chatwoot/issue/CW-7473

## Type of change

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

## How Has This Been Tested?

`bundle exec rspec spec/jobs/account/contacts_export_job_spec.rb` —
added a case asserting a formula-leading contact value is neutralised on
export; existing cases updated for the phone-number prefix.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [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
2026-08-13 18:09:38 +05:30
Aakash Bakhle
8864f80ab7 feat(captain): show document conversation usage (#15140)
## Summary

Adds conversation usage to Captain documents and user created FAQs. Only
knowledge used in a Captain answer sent to the contact is counted.
Lookups that end in a handoff are excluded.

Administrators can see how many distinct conversations used a knowledge
record. Document usage appears in the Usage tab inside document details.
User created FAQ usage appears on each FAQ card and opens in a
conversation panel.

Deleted conversations are excluded from counts, sorting, and
conversation lists.

Usage shown in side panel
<img width="2342" height="1502" alt="CleanShot 2026-08-12 at 18 29
02@2x"
src="https://github.com/user-attachments/assets/67301051-6df5-4eb6-9b8d-e1fc75bb02f2"
/>

Sorting options
<img width="580" height="241" alt="image"
src="https://github.com/user-attachments/assets/078566b7-481b-491b-b484-c2fef481f1b1"
/>


## Access

Conversation usage is available only to administrators. Agents can still
view documents and FAQs, but they cannot see usage counts, the Usage
tab, the "Most used" sort, or conversation usage details.

The API applies the same rule. An agent request for `sort=most_used` is
rejected.

## Performance and pagination

Document and FAQ lists return 25 records per page. Usage counts are
calculated only for the records in each page.

Conversation usage panels load 25 conversations at a time and show a
"Load more" action when more conversations are available. The generated
FAQs tab also keeps its existing 25 item pagination.

The count queries use the JSON indexes on `document_ids` and
`used_faq_ids`. The "Most used" sort aggregates one assistant's sessions
once before it sorts and returns the requested document page.

## How to test

1. Sign in as an administrator and open the Captain documents page for
an assistant with tracked document usage.
2. Open a document and confirm that the Usage tab shows the distinct
conversation count and the matching conversations.
3. Select "Most used" and confirm that documents are ordered by distinct
conversation usage.
4. Open the user created FAQs page and confirm that FAQ cards show their
usage count and open the matching conversations.
5. Confirm that usage panels show 25 conversations first and can load
the next page.
6. Trigger a knowledge lookup that ends in a handoff and confirm that it
does not increase document or FAQ usage.
7. Sign in as an agent and confirm that usage counts, usage details, and
the "Most used" sort are not available.

## Closes

[CW-7498](https://linear.app/chatwoot/issue/CW-7498/fe)
2026-08-13 17:39:49 +05:30
Sony Mathew
b3a1dbca81 fix: oversized message cursors (#15400)
Message pagination now constrains client-provided cursors to the
PostgreSQL integer range used by `messages.id`, preventing oversized
values from raising database errors while preserving before/after
pagination semantics.

## Closes

-
[CW-7922](https://linear.app/chatwoot/issue/CW-7922/harden-backend-paths-causing-production-sentry-errors)
- [Sentry 7663397140](https://chatwoot-p3.sentry.io/issues/7663397140/)
- [Sentry 7663397546](https://chatwoot-p3.sentry.io/issues/7663397546/)
- [Sentry 7663464772](https://chatwoot-p3.sentry.io/issues/7663464772/)

## How to reproduce

Request conversation messages with an extremely large `before` or
`after` cursor, such as `4611686018427387903`. PostgreSQL previously
rejected the value as outside the range for the integer `messages.id`
column.

## What changed

- Normalize message cursors before they reach the query.
- Clamp them to the valid signed 32-bit integer ID range.
- Cover oversized `before` and `after` cursors with finder specs.
2026-08-13 17:34:05 +05:30
Sony Mathew
dd203438d3 fix: reject malformed filter payloads (#15398)
Contact and conversation filters now reject malformed numeric/date
custom-attribute values and dangling query operators during validation,
returning the existing invalid-filter response instead of failing later
while building SQL.

## Closes

-
[CW-7922](https://linear.app/chatwoot/issue/CW-7922/harden-backend-paths-causing-production-sentry-errors)
- [Sentry 7663920842](https://chatwoot-p3.sentry.io/issues/7663920842/)
- [Sentry 7663378104](https://chatwoot-p3.sentry.io/issues/7663378104/)

## How to reproduce

Submit a custom numeric attribute filter with an empty value, or end a
filter payload with a nonblank `query_operator`. These inputs previously
reached coercion/query construction and raised production exceptions.

## What changed

- Validate numeric and date custom-attribute values before query
construction.
- Preserve presence operators, which do not require a comparison value.
- Reject a query operator on the final filter condition.
- Add contact and conversation service regression coverage.
2026-08-13 17:04:11 +05:30
Sony Mathew
8a778f4f5b fix: handle empty round robin queues (#15399)
Round-robin queue resets now leave the Redis queue empty when an inbox
has no members, avoiding an invalid sorted-set write while keeping
normal queue population unchanged.

## Closes

-
[CW-7922](https://linear.app/chatwoot/issue/CW-7922/harden-backend-paths-causing-production-sentry-errors)
- [Sentry 7663380084](https://chatwoot-p3.sentry.io/issues/7663380084/)

## How to reproduce

Run round-robin assignment for an inbox with no inbox members. Resetting
the queue previously attempted a Redis sorted-set write without any
member/score pairs.

## What changed

- Clear the existing queue as before.
- Skip queue population when there are no eligible user IDs.
- Add service coverage for an inbox with no members.
2026-08-13 17:03:36 +05:30
Vishnu Narayanan
ba1973ea40 fix: escape formula characters in CSAT CSV export (#15335)
## Description

The CSAT survey response CSV export builds rows with the standard
library `CSV`, writing values verbatim, including the feedback message,
which is free text submitted by end users. When a cell begins with a
formula character (`=`, `+`, `-`, `@`, and tab/CR), spreadsheet
applications interpret it as a formula on open. This switches the three
`CSV.generate_line` calls in the export template to
`CSVSafe.generate_line` (the `csv-safe` gem already used by the v2
report exports), which prefixes such fields so they are treated as text.

No new dependency, no behavioural change beyond neutralising
formula-leading cells.

Ref https://linear.app/chatwoot/issue/CW-7473

## Type of change

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

## How Has This Been Tested?

`bundle exec rspec
spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb`
— added a case asserting a formula-leading feedback value is neutralised
in the downloaded CSV.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [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
2026-08-13 16:23:05 +05:30
Aakash Bakhle
13de83d1dc refactor(captain): route conversation completion by feature (#15317)
Conversation completion evaluations now use a dedicated internal LLM
feature with GPT 4.1 as the default. The internal route keeps the
completion model separate from the installation wide Captain model
override and from the assistant route, which can use GPT 5.2 for Captain
V2 accounts. Evaluations continue to use the installation API key and do
not consume Captain response credits.

## What changed

Added an internal `conversation_completion` feature to the LLM model
config and excluded internal features from account preferences, the
Captain settings API, and Super Admin model overrides.

Updated `Captain::ConversationCompletionService` to resolve its model
through `Llm::FeatureRouter`.

Added focused service and request coverage for model routing and
settings visibility.
2026-08-13 13:54:35 +05:30
Aakash Bakhle
eff7ce2898 fix: consolidate customer message bursts before lookup (#15263)
Captain now treats consecutive customer messages as one request before
it asks a question, searches the knowledge base, or suggests a solution.
It uses the full message burst to identify the customer's goal and
current state. It checks conflicting details before relying on them.

## How to test

1. Send several customer messages in a row that describe one issue. Add
a short correction in a later message.
2. Confirm Captain uses all the messages before it asks a question,
searches the knowledge base, or suggests a solution.
3. Send another message with a detail that conflicts with information
Captain already checked. Confirm Captain checks the detail again before
relying on it.
2026-08-13 13:23:43 +05:30
Aakash Bakhle
b24a970795 fix: allow multi-digit values in duration inputs (#15429)
## Description

The shared duration input enforced its minimum after every keypress.
When the minimum was 10, typing `45` changed the first digit to `10`, so
the second digit produced `105`.

This change lets people finish typing before the input applies its
minimum and maximum. The input now normalizes the value when it loses
focus or when the person presses Enter.

## Type of change

- [x] Bug fix

## How has this been tested?

- Added a component test that types `4`, then `5`, and confirms that the
value stays `45`.
- Confirmed that values below 10 and above 100 are normalized when the
input loses focus.
- Ran the focused Vitest file and ESLint.
- Ran a local deterministic Playwright test through the delayed
automation form. The test typed `45`, saved the rule, confirmed
`execution_delay: 45` in the API response, confirmed `Runs after 45m` in
the list, and removed the test rule.

## Checklist

- [x] My code follows the style guidelines of this project.
- [x] I have performed a self-review of my code.
- [x] I have added tests that prove the fix works.
- [x] The focused unit and browser tests pass locally.
2026-08-13 12:52:13 +05:30
Sivin Varghese
45878923b6 feat: run macros from the command bar (#15422) 2026-08-13 12:47:23 +05:30
Sivin Varghese
416ac8660f fix: fit the reply editor to Copilot suggestions (#15428)
# Pull Request Template

## Description

This PR fixes the reply editor staying at its default height when
Copilot generates a suggestion, causing longer suggestions to be clipped
after a line or two.

The editor now automatically grows to fit the suggestion, up to a
maximum of 350px, and returns to the height it had before the suggestion
when it is accepted or discarded. Manual resizing continues to work as
expected and always takes priority.

### How to reproduce

1. Open a conversation and generate a Copilot suggestion ( → Summarize
the conversation).
2. The reply editor stays at its default height and the suggestion gets
clipped after a line or two.
3. Resize the reply editor, generate another suggestion, and discard it.
The editor no longer returns to the height you set.

### What changed

* `CopilotEditor` measures the rendered suggestion and requests enough
space to display it, capped at 350px. The requested height is released
when the suggestion is gone.
* `ResizableEditorWrapper` handles this requested height separately from
the manually dragged height, so the suggestion can grow the editor
without overriding the user's preferred height.
* The loading and suggestion states now cross-fade in place while the
editor resizes, instead of briefly switching through an empty card.


### 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/d9a5dab9-6206-4333-92d1-d3f710f9022e




## 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
2026-08-13 12:39:56 +05:30
Sivin Varghese
13dfe1a6c6 feat: run macros from the reply editor with # (#15416) 2026-08-13 12:23:35 +05:30
Manoj
1d905fb019 fix: anchor Label title validation with \z instead of \Z (#15443)
## Description

This isn't tied to an open issue — I found it by extrapolating from the
bug class fixed in #15415 ("fix: anchor contact phone number
validation"), which fixed a `Contact#phone_number` format validation
that was missing a leading `\A` anchor. That made me audit every
hand-written regex-based validation in the codebase for the same class
of anchoring mistake (`format: { with: ... }` validators, plus
`match?`/`=~` calls used for validation-style checks, across `app/`,
`enterprise/`, and `lib/`).

Everything else was already correctly anchored. One real instance of the
*sibling* mistake remains:

`RegexHelper::UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE` (used only by
`Label#title`'s format validation) is
`/\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z/` — note `\Z` (capital), not `\z`.
Unlike `\z`, `\Z` also matches just before a single trailing `"\n"` at
the end of the string. The surrounding comment documents the intended
character set (unicode letters/numbers/underscore/hyphen, not starting
with `_`/`-`) and says nothing about tolerating a trailing newline, so
this reads as an unintentional choice of anchor rather than a deliberate
one.

Concretely: `Label.new(title: "hello_world\n").valid?` returns `true` on
current `develop` and persists a title with a literal trailing newline,
because `\Z` lets the `\n` slip through. `Label` only lowercases the
title before validating (no `strip`), so nothing else catches this.

This is a narrower/lower-severity variant of the #15415 bug (it only
ever admits one specific trailing character, not an arbitrary
prefix/suffix), but it's the same underlying mistake, independently
verified against current source, not just pattern-matched from the diff.

## What changed

- `lib/regex_helper.rb`: `UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE`
now ends in `\z` instead of `\Z`, with a comment explaining why.

## Type of change

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

## How Has This Been Tested?

Ran against a real local Rails env (Ruby 3.4.4, PostgreSQL 16, Redis):

`bundle exec rspec spec/models/label_spec.rb`

Added a regression test asserting `Label.new(title: "hello_world\n")` is
invalid. Confirmed it fails against the pre-fix `\Z` regex and passes
after switching to `\z`. All existing `label_spec.rb` examples
(including the existing "foreign characters", "special characters", and
"uppercase" title-validation cases) continue to pass unchanged.

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] New and existing unit tests pass locally with my changes

🤖 This fix was authored by an AI coding agent (Claude) working on behalf
of Mithtech, an ERPNext/Frappe/Medusa.js implementation studio, as part
of a deliberate effort to build a track record of verified upstream
open-source contributions. Flagging this transparently per common
courtesy — happy to answer any questions about the change, including how
it was found (auditing for the same regex-anchor mistake class as
#15415).

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 12:05:06 +05:30
Sivin Varghese
2ed68cf207 feat: cache canned responses in the browser (#15401) 2026-08-13 11:54:32 +05:30
Sivin Varghese
590e17dfb7 chore: remove the legacy notifications page (#15426) 2026-08-13 11:49:44 +05:30
Muhsin Keloth
046c6c754f fix(messages): resend failed messages through provider (#15432)
Failed messages on external provider-delivery channels can appear sent
after an agent selects Retry even though the retry never reaches the
provider. This change clears the stale provider delivery ID before
retrying the existing message, allowing the channel service to create a
fresh provider attempt and store its new ID.

API and WebWidget messages are excluded because their `source_id` values
may be client-supplied correlation identifiers and their retry jobs do
not assign replacement provider IDs. Concurrent or stale retry requests
are also guarded so only the request that successfully claims a failed
message can enqueue delivery.

Fixes https://github.com/chatwoot/chatwoot/issues/14120

Related:
https://linear.app/chatwoot/issue/CW-6896/retrying-a-failed-whatsapp-message-silently-succeeds-locally-but-never

### Things to know

- Applies to external provider-delivery channels, including direct
WhatsApp and Twilio WhatsApp.
- Preserves `source_id` for `Channel::Api` and `Channel::WebWidget`.
- Reuses the existing Chatwoot message record while allowing the
provider to return a new message ID.
- Logs the cleared provider ID and Chatwoot message ID when a stale
provider ID is removed.

### How to reproduce

1. Send a message that the provider accepts and later marks as failed
through a status callback.
2. Select Retry on the failed message.
3. Observe that Chatwoot changes the message to sent but does not
contact the provider because the previous provider message ID remains
present.

### How to test

1. In an external provider inbox, retry a failed message that has an
existing provider message ID.
2. Confirm the old provider ID is cleared before delivery and the retry
reaches the provider.
3. Confirm the provider assigns a fresh message ID and subsequent status
callbacks update the retried message.
4. Retry a failed API or WebWidget message and confirm its existing
`source_id` is preserved.
5. Trigger concurrent retry requests for the same failed message and
confirm only one delivery job is queued.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-08-13 11:35:52 +05:30
Muhsin Keloth
10e9cb5b60 fix(whatsapp): load health status for regional locales (#15440)
The WhatsApp health status page fails to render for accounts using
underscore-based regional locales such as `pt_BR`. The browser rejects
that locale when formatting the last-onboarded timestamp, leaving the
page blank even though health data loaded successfully.

This change formats the timestamp with Chatwoot’s shared resolved
locale, which normalizes and validates the locale before passing it to
the browser. Other health status behavior remains unchanged.

### How to reproduce

1. Set the dashboard language to Portuguese (Brazil).
2. Open an inbox’s WhatsApp health status page when the response
includes `last_onboarded_time`.
3. Observe that the page remains blank with `RangeError: Invalid
language tag: pt_BR` in the browser console.

### How to test

1. Use an account with Portuguese (Brazil) selected.
2. Open the WhatsApp health status page for an inbox with onboarding
data.
3. Confirm the complete health status page loads and the last-onboarded
timestamp is formatted in the selected locale.

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-08-13 10:26:06 +05:30
Vishnu Narayanan
3453e2975d chore: land super admin on dashboard after login (#15411)
## Description

After a successful super admin login, redirect to the super admin
dashboard (`super_admin_root_path`) instead of the users list. The
dashboard is a lighter, overview-first landing page, while the users
index does an exact count over the full users table on every load and
can be slow on large instances.

This only changes the post-login landing page. The users list is still
reachable from the navigation.

Fixes https://linear.app/chatwoot/issue/CW-7928

## Type of change

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

## How Has This Been Tested?

Added request specs for `SuperAdmin::Devise::SessionsController#create`:
- successful login redirects to `super_admin_root_path`
- invalid credentials redirect back to the login page

`bundle exec rspec
spec/controllers/super_admin/devise/sessions_controller_spec.rb` -> 3
examples, 0 failures.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [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
2026-08-12 18:35:21 -07:00
Vijay Misal
adef9f2cd6 fix: Alt+E resolve shortcut also opens Chrome's built-in menu on Windows (#15418) 2026-08-12 21:54:34 +05:30
nikita42
42ca0901c2 fix(editor): handle parenthesized ordered list markers (#15362) 2026-08-12 21:49:03 +05:30
Khush Raghav Nanda
958fc37ba1 fix(conversation): refactor layout utilities to logical properties for RTL (#15341) 2026-08-12 21:35:17 +05:30
dependabot[bot]
d310e2c619 chore(deps-dev): bump postcss from 8.4.47 to 8.5.23 (#15380) 2026-08-12 19:09:56 +05:30
Corey
f9cdfe0071 fix: Use dark text on amber solid buttons so the disabled Add Note button stays visible (#15371) 2026-08-12 18:52:46 +05:30
Aakash Bakhle
a47eb375ad feat(captain): add advanced inactivity policy backend (4/5) (#15306)
Captain can now use each assistant's saved setting when a customer stops
replying. Captain can review the conversation and resolve or hand it
off, resolve it after the selected time without review, or leave it
pending until the customer replies.

The job checks the conversation again while holding a database lock
before it changes the status. A new customer reply or another worker
cannot cause an outdated resolve or handoff.

## Closes

[AI-163](https://linear.app/chatwoot/issue/AI-163)

## What changed

- Added assistant modes for review, always resolve, and wait for the
customer.
- Kept the account setting as the fallback for assistants that do not
have a saved mode.
- Skipped scheduling when resolution is disabled on the assistant or
through the older account setting.
- Rechecked the conversation status and activity time before each
resolve or handoff.
- Recorded events only after a status change succeeds.
- Kept out of office messages out of campaign conversations.

## How to test

1. Set an assistant to review conversations. Run the inactivity job with
complete and incomplete decisions. Confirm the first conversation is
resolved and the second is handed off.
2. Set the assistant to always resolve. Confirm an eligible pending
conversation is resolved after the selected time.
3. Set the assistant to wait for the customer. Confirm the scheduler
does not enqueue the inactivity job and the conversation remains
pending.
4. Add a customer reply while the review is running. Confirm the job
does not resolve or hand off the updated conversation.
5. Run two workers for the same conversation. Confirm only one status
change and one event are recorded.

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-08-12 18:33:48 +05:30