The fullscreen composer closed itself before the send checks ran, so a
blocked send (empty To, missing tags, contact email not in recipients)
collapsed the editor on a phone even though nothing was sent. The close
now happens after all the guards.
The hover rewrite dropped focus-within from the private note delete menu,
so it could not be reached by keyboard on desktop. Put it back.
The mobile contact sheet hides its own close button, so the collapse icon
was the only way out at 28px. It is 44px below md now and stays 28px above.
Also dropped `overflow-wrap: break-all` from .message-bubble. break-all is
not a valid overflow-wrap value, so that line never did anything.
The rest is comment cleanup and switching `../..` imports to `@main` in the
files already being touched.
Bumps taps to 44px across the sidebar, reply box, conversation header and
data tables, adds a `can-hover` Tailwind variant so hover-gated controls
work on touch, and makes the reports page stack on narrow screens.
The country code was guessed from navigator.language, which is the UI
language and often has no region, so most visitors got US. It now reads
the browser timezone first and falls back to language, then US.
`DialogContent` and `AlertDialogContent` are `w-full max-w-lg` with no side
inset, no max height and no vertical scrolling, and `sm:rounded-lg` only
applies from 640px. In a window narrower than that they render edge to
edge with square corners, and cannot be scrolled when taller than the
viewport.
Add a 1rem side inset, `max-h-[calc(100dvh-2rem)]` with `overflow-y-auto`,
and make the radius unconditional. At 640px and above `max-w-lg` still
governs the width, so nothing changes there.
Fixing the two primitives covers all 17 dialog call sites.
Lets an agent triage and reply to conversations from a phone. Admin and
reports are deliberately untouched, per the discussion in #470.
`apps/main/index.html` pinned `width=1280, initial-scale=0.29`, which makes
a phone lay the page out at 1280 CSS pixels and scale the document to 29
percent. Switching it to `width=device-width` requires the layout to cope
with a real viewport:
- Add `useIsMobile`, using `max-width: 767px` so it is the exact complement
of Tailwind's `md:`. Align the sidebar primitive's own query to match; at
exactly 768px it previously reported mobile while `md:` already applied.
- Fold the 3rem icon rail into the drawer. The mobile `Sheet` in
`ui/sidebar/Sidebar.vue` was already implemented but unreachable, since
the layout viewport could never match its media query. The rail is
`collapsible="none"`, which returns before the Sheet branch, so its
destinations move into the one drawer rather than nesting a second.
- Render the inbox one pane at a time below `md`. Both `ResizablePanelGroup`
minimums are percentages of the window, giving a ~205px thread beside a
~117px contact sidebar at 390px, which no class change can fix. The
routes are already nested, so list-to-detail is push navigation with a
back affordance; only the desktop branch reads the persisted panel sizes.
- Show the contact sidebar as a `Sheet`. It has no intrinsic width, being
sized entirely by its panel, and `collapse()`/`expand()` are splitter
specific, so this is a parallel path off the same emitter event.
- Collapse the composer to a tap target that opens the existing fullscreen
editor, and make that dialog full-bleed below `sm`; its `max-w-[60%]`
gave a 234px modal at 390px. Inline it takes ~280px, which with a soft
keyboard raised leaves the thread nothing.
- Widen message bubbles and trim the hardcoded 47px gutters on phones, and
use `overflow-wrap: break-word` so ordinary words stop splitting
mid-character. Reveal the private-note menu on touch via
`[@media(hover:hover)]`.
- Remove `SmallScreenOverlay`, whose tuned `scale(3)` existed only to
cancel out the old `initial-scale`.
Desktop is unchanged, verified by screenshot comparison at 1440px.
Agents can now publish a public knowledge base per help center.
Backend:
- new internal/helpcenter package for help centers, collections and articles
- admin JSON API behind a new help_center:manage permission
- public /hc/{slug} pages, JSON API, search and sitemap, all rate limited
- migration v2.7.0 for the new tables
AI:
- published articles with the AI flag on get embedded, so the agent answers
from them. Unpublishing or clearing the flag removes the embeddings.
Media:
- media rows now carry a private flag. Help article images are public so the
public pages can serve them without auth, and only agents with
help_center:manage can upload them.
Frontend:
- admin help center list, tree view and article editor
- the old TextEditor is split into ArticleEditor and ConversationEditor over a
shared useTextEditor composable, so article-only tools like callouts,
collapsibles and YouTube embeds stay out of the reply box
Colors: the brand color moves from indigo to green in both themes, and the
sidebar, tooltip, card and link styles follow it. Three new tokens replace
hardcoded values: foreground-lighter for idle sidebar items, warning-600 for
warning text that needs 4.5:1 contrast on light backgrounds, and link for
anchors inside rendered email content. DESIGN_SYSTEM.md now lists the real
values from main.scss instead of the old indigo ones.
Forms: every button inside a form that is not the submit button now has an
explicit type. Without it the browser treats it as a submit button, so
clicking Cancel on a contact note posted an empty note, "New holiday" saved
the whole business-hours form, and pressing Enter in an SLA field deleted the
first alert row. The login page also highlights an empty password field on a
failed submit, which a broken condition prevented before, and the two
password fields on the set-password page get their own show and hide toggles.
Search: conversation and contact search now drop responses from an older
query, so clearing the box no longer repopulates the list with stale results.
Permissions: /api/v1/ai/summarize now needs messages:write, since it writes a
private note, and the menu item is hidden for agents without it.
Design system:
- add semantic success and warning tokens (light + dark) and wire them into Tailwind
- move all hardcoded status colors (green/amber/red) onto tokens across main app and widget; keep file-type icons as identity colors
- give light mode real surface depth: gray chrome sidebars vs white content, deeper canvas gutter, crisper borders, wider gray spread so selected/hover states show
- unify radius (cards rounded-lg, controls rounded-md) and elevation (card shadow-sm, menu shadow-md)
- make reports overview colors uniform: neutral numbers, green met / red breached
- normalize the two page-title heading outliers to text-xl font-semibold
Form fixes:
- require content on AI snippets
- only include non-checkbox prechat fields when they have a value
AI context:
- skip continuity and CSAT messages in AI history, mining, and previous-conversation tools
Guard avatar edits while a save is in flight. The AvatarUpload now takes
a disabled prop that blocks the file picker and the remove button, and
the profile view ignores crop/remove events while saving. This stops a
second crop or a remove from racing an ongoing update.
Only clear the pending file after the delete request succeeds, so a
failed remove keeps the cropped preview and the Save button usable.
Do not mark failed report refreshes as fresh. The fetch helpers now
report success, and lastUpdate only advances when every section loads.
A fully failed refresh no longer labels stale data as just updated.
Add an aria-label to the custom range days input so assistive tech can
name it.
Reports overview now has a single range picker (7/30/90 days plus a
custom input) that drives every card at once, instead of a separate
date dropdown per card. Auto-refresh polls every 5 minutes, pauses
when the tab is hidden, and stops after an hour until you refresh.
Card titles are muted, numbers use tabular figures, and SLA colors use
theme tokens instead of raw green/red. The old DateFilter component is
gone.
Move avatar cropping into the shared AvatarUpload component and reuse
it on the profile page. Save is disabled until a new image is picked.
Conversation list emphasizes unread rows with bolder text and tighter
spacing. Empty state, MenuCard, and chart legend get small style
fixes. Consolidate several one-off i18n strings into shared keys.
Embedding:
- Persist embedding_max_tokens. It was dropped when saving the provider config
and always reset to the 8192 default, so self-hosted models with a smaller
limit kept getting rejected.
- Split embedding requests to stay under the provider's item and token caps. A
large snippet or URL import used to go in one request, hit the cap, fail, and
get retried every minute forever.
- Skip blank chunks. An empty input string makes the API reject the whole batch.
- Add the tiktoken tokenizer (cl100k_base) with tests for token counting.
Agent prompt:
- Move contact fields, the conversation subject, and custom attributes out of
the system prompt and into a delimited user-role block, so a crafted name or
subject can't act as an instruction.
Frontend:
- Switch the primary and sidebar colors to a green theme.
- Show a "summarizing" info toast while the AI summary runs, and add an info
toast variant.
- Make the snippet content box taller and the snippet dialog wider.
- Drop a hardcoded dark hover color on the scroll-to-bottom button.
Reasoning models like gpt-5 reject max_tokens and non-default temperature.
The client now reads the structured 400, renames max_tokens to
max_completion_tokens or drops the bad tuning param, and retries. This
removes the old rule that only sent max_completion_tokens when reasoning
effort was set.
Provider form gets a "Test connection" button that makes one live call
with the form values and shows the provider's real error. Embedding test
also checks the returned vector length against the Dimensions field.
Add an Anthropic preset (their OpenAI-compatible endpoint) and stop
pre-filling temperature since blank is safe on every model.
Fix the shared Button loader so the spinner is visible on non-filled
variants, and polish the copilot panel: bot avatars on messages, dot
loader while thinking, cleaner input box, and tab slide-in animation.
- Introduced a new SwitchField component for better UI consistency.
- Updated WebSocket client to remove unnecessary token and inbox_id from ping and typing messages.
- Added session duration configuration to the livechat channel settings in the backend.
feat: Add HTTP utility functions for trusted origin checks
feat: Implement typing status broadcasting for live chat clients and agents.
feat: Add support for signed URLs in media manager
fix: Update database migration to handle duplicate visitors with same email address.
feat: Add conversation subscription and typing message models for WebSocket communication
feat: Implement conversation subscription management in WebSocket hub this is used for broadcasting typing indicator.
feat: Revamp widget JavaScript to improve mobile responsiveness and show unread messages if any.
- new vue app for serving live chat widget, created subdirectories inside frontend dir `main` and `widget`
- vite changes for both main app and widget app.
- new backend live chat channel
- apis for live chat widget