Refresh badges on new notifications, bulk actions, snooze and unassign. Drop the second count request that fired on every local status change, look up the timezone once per counts request, and move the count base query into queries.sql.
The new cypress spec drives the view dialog through one group, three
conditions in one group, two groups, the ALL/ANY and AND/OR toggles, a
re-save, a group removal, a rename and a delete. The palette now says
"No results found" in macro mode, and the help center pages link to the
docs.
The callback retrieved oidc_redirect_uri from the session and silently
fell back to an empty string when it was missing, unreadable, or not a
string, then passed that empty value to ExchangeOIDCToken, which would
send no redirect_uri and have the exchange rejected by the IdP. Treat
a missing or unreadable value as a stale session and route it through
the existing session-expired error path, the same way a state mismatch
is handled.
Address CodeRabbit review on the live-lookup change:
- Resolve the redirect URI live in LoginURL and return it so the caller
persists it in the session; ExchangeOIDCToken reuses that stored value
instead of resolving again. The token exchange redirect_uri must match
the one in the auth request (RFC 6749 4.1.3); resolving twice would let
a Root URL change between login and callback produce a mismatch the IdP
rejects.
- Propagate the redirect URL resolution error out of LoginURL rather
than discarding it, so a setting read failure fails the login loudly
instead of sending an empty redirect_uri.
- Pass rootURL as a formatting argument in oidc.RedirectURL instead of
concatenating it into the format string, so a root URL containing a
%-sequence (e.g. %2F) is not interpreted as a verb.
Auth providers captured the redirect URL as a string at build time, so
changing app.root_url in General settings left OIDC providers sending
the old redirect_uri until the process restarted. The identity provider
then rejected login with "redirect_uri is missing in the client
configuration" even though the database and the IdP client both held the
right value.
Make auth.Provider.RedirectURL a closure and resolve it in LoginURL and
ExchangeOIDCToken from the current root URL, matching the media store's
existing rootURL closure. The redirect URL is computed by a new
oidc.Manager.RedirectURL(id) helper so the path format stays in one
place. Other provider fields (client ID, provider URL) remain snapshotted
and are refreshed by reloadAuth on OIDC changes, as before.
This removes the reload requirement for Root URL entirely; the settings
handler no longer needs to know that auth depends on it.
NormalizeMessageID now returns empty for a value containing CR or LF.
A source_id with an embedded line break would render as a malformed
In-Reply-To/References header on outbound replies. The IMAP path never
sees such values; the HTTP API can, so the helper rejects them. An empty
result leaves the column NULL, so a bad value degrades threading rather
than producing invalid mail headers.
Add an optional source_id to createConversationRequest and messageReq and
store it on the created contact message, mirroring the IMAP ingestion path
which is the only place source_id is otherwise populated.
BuildEmailThreadingHeaders builds every outgoing reply's References and
In-Reply-To from prior messages' source_id. A desk fed over the API, as
ours is, therefore references only its own previous sends, so the customer's
mail client has nothing of theirs to thread on and every reply arrives
looking standalone. Letting the API set source_id fixes this for any API-fed
inbox without a direct database write.
The value is normalized (whitespace and angle brackets stripped) since
source_id is stored unbracketed and the brackets are re-added when composing
the threading headers. An empty value leaves the column NULL as before, so
existing callers are unaffected.
Found an issue with mentions in the reply editor where suggestions always returned empty results by design. However, the mentionSuggestions stills runs and shows the results pop up with empty list.
This fixes so that mention pop-up simply never appears for normal replies as expected.
Manually tested the behavior locally and no-longer seeing the mention pop-up.
This implementation uses already tracked contact_last_seen conversation field and compares the message createdAt value.
It's also confirmed that this field is only updated for live chat conversations so email conversations falls back to the single check mark.
Testing done on the embedded widget and working as expected.
Fixes#434
Adding `imaging.AutoOrientation(true)` ensures that the image is decoded in the correct orientation based on the EXIF data without making assumptions.
Manually verified the fix after the change and confirmed that it's working as expected
Fixes#538
Heap profiles on a large install showed three hot spots: image uploads
decoded the full bitmap twice, every i18n request rebuilt the language
pack, and the hourly time trigger loaded all conversations from the
last 30 days into one slice.
Restore upstream conversation create/transcript and mobile sidebar behavior that was accidentally overwritten during cherry-pick. Import onMounted in Sidebar.vue and bound view-count queries with a 10s timeout.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds badges with the number of open conversations next to My inbox,
Mentions, Unassigned, All and each personal or shared view. "Open" means
a conversation whose status category is open, so snoozed and resolved
conversations are not counted.
Backend:
- GET /api/v1/conversations/sidebar-counts returns the counts, gated by
the conversations:read permission. Each individual count is additionally
gated server-side by the permission for that list, so an agent never
receives a number for a list they cannot open.
- View counts are unioned into a single statement, so the number of views
does not drive the number of queries.
- The list-type conditions shared by the conversation list and count
queries are extracted into appendListTypeConditions, so both stay in
sync on what each list type means.
- An empty team list now renders as IN (NULL) instead of the invalid
IN (), which an agent holding a team permission without a team hit.
- The feature can be turned off via app.sidebar_counts_enabled, which
defaults to enabled when the setting is missing.
Frontend:
- Counts refresh when switching inboxes, after the agent's own status or
assignee changes, and from new_conversation and status-carrying
conversation_update websocket events.
- Refreshes are throttled and share a short TTL, so bursts of incoming
conversations collapse into a single request. Re-enabling the feature
bypasses the TTL, because being disabled leaves a zeroed result cached.
Refs #466
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>