CreateContact treated any SetExternalUserID failure as "ext_id taken" and
fell through to the upsert, so a transient DB error created a second
contact with the same email. Now only a unique violation or a contact
deleted mid-flight falls through, other errors are returned.
SetExternalUserID also reports whether a row was actually updated, and
dbutil error checks use errors.As so wrapped errors match. Widget JWTs
with no email now store NULL instead of an empty string.
Livechat:
- Add a live widget preview that renders in the inbox settings help rail.
- Warn when the primary or header colors are too close to the background.
- Require gradient/image background values and notice banner text when enabled.
- Show a default launcher logo in the widget when none is set, matching the preview.
- Drop the Beta badge from the livechat channel.
General settings:
- Add "set agents away on login". Agents log in as away and must go online manually.
- Add "show subject in conversation list" toggle.
Other:
- Add a keyboard shortcuts dialog from the user menu.
- Show a "snoozed until" badge on snoozed conversations.
- Contact search now matches emails partially instead of exact.
- Give outgoing message bubbles a secondary background.
Address CodeRabbit review: the query deleted by message uuid alone, so an agent
with access to one conversation could delete a private note from another by
pairing an accessible cuuid with a foreign message uuid. The delete is now
scoped via conversation_id = (SELECT id FROM conversations WHERE uuid = $2), and
DeletePrivateMessage / the handler pass the conversation uuid alongside the
message uuid.
[Used Claude Code 🤖]
Adds DELETE /api/v1/conversations/{cuuid}/messages/{uuid} for removing private
notes via the API. Uses a dedicated `delete-private-message` query/stmt
(separate from the generic `delete-message` used internally by continuity) so
only messages with private=true can be deleted — sent/incoming messages are
protected. Returns 404 if the message doesn't exist or isn't private.
Useful for API consumers that create private notes programmatically and need to
clean them up (e.g. replacing an outdated note before adding a new one).
[Used Claude Code 🤖]
- sanitize Content-Disposition filename via stringutil.SanitizeFilename to neutralize header injection
- use i18n.Tc for count-aware singular/plural attachment label
- append download anchor to DOM and defer URL.revokeObjectURL for cross-browser reliability
Adds a from_name_template field to email inboxes. When set and the sender is an agent, the template renders with agent and inbox variables and becomes the From display name; the inbox email address is unchanged. Any render failure, or a system-user sender, falls back to the plain inbox From so a misconfigured template never blocks delivery.
Variables: .Agent.FirstName, .Agent.LastName, .Agent.FullName, .Inbox.Name (the inbox name). Based on #249.
Co-authored-by: josephsellers <6892567+josephsellers@users.noreply.github.com>
Agents can download a full conversation as a .txt file from the
header menu. The conversation sidebar now shows the CSAT rating and
feedback once the customer responds.
Reject disabled agents in FilterAuthorizedListUUIDs, kick role members on
permission removal or role delete, and re-sub the list and open conv on
ws reconnect using local state. Also plug the app logger into the ws
package so kicks no longer log when no connections exist.
Convs and messages now push the full list-row payload to perm-authorized agents on create/assign/message, so livechat returning customers bubble up instantly without waiting for the timer refresh. Background list refresh bumped 30s -> 60s as a drift safety net.
Targets broadcasts via per-tab list and open subs (validated at subscribe time)
instead of broadcasting message uuid to all agents. Adds frontend list typing, throttled sound,
and many perf improvements to reduce cpu memory & database queries.
Other fixes: Enforce message-to-conversation binding in handleGetMessage