Commit Graph

96 Commits

Author SHA1 Message Date
Abhinav Raut 102e9fc625 apply Geist to toasts, charts and form controls 2026-08-21 12:59:19 +05:30
Abhinav Raut a9867dce00 move public help center api under /api/v1 and add security headers 2026-08-21 04:55:07 +05:30
Abhinav Raut 6208b291ce fix cache misses, wrong view counts, and silent cache errors in the help center 2026-08-20 21:56:54 +05:30
Abhinav Raut c865748ca5 switch to Geist and cache help center pages
Geist replaces Instrument Sans and is served from static/ instead of Google
Fonts, so it works on installs with no internet access.

Public help center pages are cached in Redis via fastcache with ETags, and any
admin write clears the group so edits show up on the next load.
2026-08-20 18:01:42 +05:30
Abhinav Raut 0c99b015cd fix missing logo in emails on a fresh install 2026-08-17 18:10:13 +05:30
Abhinav Raut 1ac0c77b88 harden help center public routes and render a themed 500 page
Uptime checkers and link validators probe with HEAD, so register
HEAD on all public routes and treat HEAD as a crawler so probes
do not inflate view counts or write search logs.

The custom-domain host lookup hit the DB before any rate limit.
Both the not-found gate and the host-home redirect now pay the
"public" limit first, and the middleware skips double-charging.

DB errors on help center pages rendered the plain unthemed error
page. Reuse the themed notfound template for them: it now takes
code, title, and text, so 404 and 500 both render inside the theme.

Also cap the sitemap at the URL limit, skip re-embedding unchanged
content in the AI embed reconciler, and autofocus the login email.
2026-08-15 12:19:50 +05:30
Abhinav Raut 2183e8140d move help center branding columns into theme and ship Instrument Sans 2026-08-15 02:22:49 +05:30
Abhinav Raut 6e020dd694 improve help center tree and docs template hierarchy UX 2026-08-15 01:39:07 +05:30
Abhinav Raut 1feb3743f6 dedupe help center template partials and reuse the markdown link helper 2026-08-14 01:44:37 +05:30
Abhinav Raut 2476ee2685 center the article editor toolbar and stop the feedback box resizing 2026-08-14 00:55:28 +05:30
Abhinav Raut cc6193c564 harden media serving and fix a few help center review findings 2026-08-14 00:50:56 +05:30
Abhinav Raut 11611de2b8 fix the second round of help center review findings
Frontend:
- the basics schema broke after the locale refine was added, since
  .pick() does not exist on a refined schema. Split the base object
  schema out and refine only the full form schema.
- creating a collection no longer overwrites the parent picked in the
  sheet with the preset parent, so "None" and other choices stick
- the article editor toolbar is inert while the editor is disabled,
  since teleporting moved it out of the pointer-events-none wrapper
- stop stealing focus when a parent swaps the editor content
- a11y: label the link/youtube URL inputs and the link list remove
  button, and make the help center name cell a real button

Backend:
- reject a custom domain whose hostname another help center already
  uses. App-level check in validateCustomDomain, matching the
  case-insensitive hostname comparison routing does.

Static:
- docs template: convert the remaining left/right rules to logical
  properties and flip the row arrows in RTL
- fix currentcolor casing in article-content.css
2026-08-13 18:29:07 +05:30
Abhinav Raut 5027fe23a2 fix a batch of help center review findings
Backend:
- log LinkHelpArticleMedia errors on article create/update
- trim a trailing slash off the root URL in PublicURL
- run uniqueCollectionSlug inside the caller's transaction
- update-article sets author_id directly so clearing the author works
- apply the help_centers template CHECK as a named constraint so it
  also lands on installs that ran an earlier build of the migration

Frontend:
- new editor tables get the email-safe inline styles by default
- replace deprecated word-wrap/word-break with overflow-wrap
- reject a default locale that is not in the allowed locales
- show an error when the icon picker sprite fails to load
- name the locale remove buttons and keep classic-only header
  fields mounted with v-show

Docs template:
- move the collection link out of <summary>, toggle is its own button
- use logical CSS properties so the sidebar nav renders in RTL
- fix text-rendering keyword casing
2026-08-13 17:12:12 +05:30
Abhinav Raut c494904ecf use a lucide chevron for the collapsible marker 2026-08-13 16:10:18 +05:30
Abhinav Raut 57af9bc20b add syntax highlighting and a copy button to article code blocks
Article code blocks were plain text with no language. They now use tiptap's
lowlight code block, and the toolbar button turns into a language dropdown
while the cursor is inside a block. 16 languages are registered.

Lowlight only highlights the editor with decorations, which tiptap never
serializes, so the saved HTML would have stayed plain. The content is run
through highlight.js on save, which bakes the hljs spans in. The published
page then needs no JS for colors, only CSS. Reopening an article flattens
the spans back to text, so they do not pile up. Existing articles need one
re-save to pick this up.

The published article page also gets a copy button on every code block,
injected by the shared behavior template. That template now receives the
root context, which it needed for the icons and labels.

Also: the editor toolbar shrink-wraps its buttons instead of stretching the
full column, and each help center row now has an Articles item in its
dropdown, so the article tree is reachable without clicking the name.
2026-08-13 15:31:26 +05:30
Abhinav Raut 6083d2f098 serve help centers at the root of a custom domain
public_url is now custom_domain. It only accepts a bare origin, no path or query.

When a help center has a custom domain, its pages live at /{locale} on that domain. Requests on that host fall through to the router's not-found handler, which prepends /hc/{slug} and re-runs the router once, so nothing about the routes changes. All page links come from a new BasePath value, which is empty-prefixed on a custom domain and /hc/{slug} on the app host. Hits on the old /hc/{slug} paths 301 to the custom domain so there is one canonical address per page.

Redirects now send a path-only Location built through fasthttp's URI encoder. ctx.Redirect would turn it into an absolute http:// URL, since nginx forwards to the app over plain HTTP, and a raw path would let /\host in the URL read as another site.

robots.txt on a custom domain also disallows the search pages.
2026-08-13 01:27:22 +05:30
Abhinav Raut 8ca05b2460 share collection cards between help center templates 2026-08-09 22:23:39 +05:30
Abhinav Raut fbbef49bb3 fix help center feedback counts, empty state, mobile logo and a11y 2026-08-09 19:47:57 +05:30
Abhinav Raut 048eb1acd5 add page template setting for help center with docs and classic layouts
Help centers now have a template setting with two layouts. Classic is
the default: the existing hero banner, large search bar and collection
cards. Docs is new: sticky top bar, collections sidebar on every page,
command palette search (Ctrl+K) with a no-JS fallback form, and an
on-page table of contents.

Backend adds a template column (default 'classic', checked to the two
values), threads it through insert/update queries and the request
validator, and resolves page template names as '<template>-<page>' when
rendering public pages. Docs pages also get the published tree for the
sidebar.

Public templates are restructured so each layout is fully isolated and
a third template is just a new directory plus its CSS file:
web-templates/help/shared/ holds the head, announcement, lang switcher,
icons and shared JS; help/classic/ and help/docs/ each hold their own
layout and five pages. The classic stylesheet is renamed to
help-center-classic.css so the head links help-center-<template>.css
directly. Template parsing now globs the help/ subdirectories too.

The admin create and edit forms get a template picker, and classic-only
styling fields (header background, collection layout, card icons) are
hidden when docs is selected.
2026-08-09 17:40:52 +05:30
Abhinav Raut 353558ce9b add a public URL per help center and fix code review issues
Each help center gets an optional public_url. When set, canonical links, hreflang
alternates, og:url, the sitemap and robots.txt point at that host instead of the
app root URL. Stored media URLs are rewritten to root-relative paths so logos,
favicons, header images and article images resolve on whichever host serves the
page. The URL is validated on both the form and the backend, and trailing slashes
are trimmed.

Also fixes issues found in code review:

- the help center template used a locale link as the data root inside the
  alternates loop, which broke every public page on a help center with two or
  more locales
- creating an article posted the clicked collection id, so changing the
  collection in the sheet filed the article in the wrong place
- the collection field's error message was hidden when the locale had no
  collections, so Create did nothing with no error shown
- switching the header type or unticking popular articles dropped those theme
  values on save, because vee-validate unsets fields that unmount
- closing a new collection sheet with Esc left the parent id set, so the next
  collection you edited got re-parented
- a table row with th labels in the first column was treated as a header row
  when preparing article content for embeddings
- the img tag regex stopped at a > inside an attribute value and injected
  loading/decoding in the middle of an attribute
2026-08-05 01:10:31 +05:30
Abhinav Raut b4b4251ebd Merge remote-tracking branch 'origin/main' into help-center
# Conflicts:
#	internal/media/media.go
2026-08-04 21:57:26 +05:30
Abhinav Raut 901392879e fix editor, preview and public page issues from code review
Editor: Enter could not escape a collapsible or callout when its body was
one empty line. The old code deleted that line, but the body needs at
least one block so the delete was dropped and the new paragraph landed
inside the container. It now keeps the empty line and inserts after the
container. The reply editor also goes read-only for real when disabled,
CSS pointer-events alone still let a focused editor take keystrokes.

Help center: the article form treated an untouched editor as filled
because it serializes to <p></p>. Header text picked its color from the
first gradient stop only, so the other end could be unreadable, and the
light/dark cutoff did not match the dark color actually returned. The
customize preview could paint a slower older render over a newer one, and
kept the pre-save name and slug in the breadcrumb and live link.

Public pages: og:locale now uses the en_US form Open Graph expects, the
last updated date is formatted in the reader's locale, and a helpful vote
is only remembered once the server accepts it. Reading localStorage is
guarded so private mode does not break the page script.

AI: the orphan embedding sweep holds the commit lock, so a job that
passed its existence check cannot insert vectors for a deleted article
just after the sweep. The search tool description mentions help articles
too, and the search log cleaner's startup delay exits on shutdown.
2026-08-04 21:39:23 +05:30
Abhinav Raut d9ede860b6 hide the announcement banner when its text sanitizes to nothing 2026-08-04 18:24:40 +05:30
Abhinav Raut f7a662e17e allow basic html in help center tagline and announcement text
Taglines and the announcement banner were rendered as plain text, so an
admin could not add a link or bold a word. These now go through a new
inline sanitizer that keeps links and basic formatting tags and strips
everything else. The taglines are also trimmed on save, and the form
shows a hint saying basic HTML is allowed.

The preview URL bar in the customize view is now a link that opens the
live help center in a new tab. It uses the saved slug, not the form, so
it always points at a page that exists.

Also underline popular article links on hover instead of only changing
their color, and drop the extra padding that made the row taller than
the card.
2026-08-04 18:21:40 +05:30
Abhinav Raut 5e320b8e4c add article preview, announcement banner, and author selection to help center
The customize page preview now has a page selector (landing or a sample
article) and a device selector (desktop, tablet, mobile) with the frame
rendered at real device width and scaled to fit, wrapped in a browser
chrome mockup showing the live title, favicon and URL.

Themes gain an announcement banner shown above the header, a popular
articles section toggle with a custom label, and an icon tile option for
collection cards. Theme normalization now starts from defaults instead
of zero values.

Articles get a selectable author (validated as an agent or AI assistant)
separate from the new created_by column that records who created it. A
soft-deleted author no longer blocks saving an existing article.
2026-08-04 01:53:26 +05:30
Abhinav Raut 9cac787f30 fix preview sources and reindex articles when a collection changes
The assistant test tab looked up every search hit as a snippet, so a help
article showed an unrelated snippet's title or vanished from the list. Also
drops the help center view count, which nothing ever read.
2026-08-02 19:08:42 +05:30
Abhinav Raut e5bfe7932e keep tables and link urls in embedded article text 2026-08-02 17:57:17 +05:30
Abhinav Raut 3b92185788 tighten help center permissions and fix editor and public page issues
Permissions:

- the admin help center read routes only checked that you were logged in, so
  any agent could read draft articles and every help center's custom CSS and
  JS. They need help_center:manage now, like the write routes already did.

Article editor:

- adding a YouTube video made the whole page unclickable, because the iframe
  swallowed every mouse event. It ignores the pointer while editing now.
- the collapsible body looked editable but was not. A closed <details> is not
  clickable, so the editor marks it open instead of just showing the body.
- headings all rendered at the same size. Article styles moved into the shared
  article-content.css so the editor and the published page cannot drift.
- there was no way to type below a callout, table or video at the end of an
  article. There is always a line after the last block now.
- Enter on the empty last line of a collapsible gets you out of it.
- the toolbar sits above the article instead of floating at the bottom.
- videos can be selected and aligned, and show an outline when selected.
- H1 is gone from the toolbar. The article title is the page's H1.

Public pages:

- the excerpt shows under the article title, and is only what the author
  wrote. It used to be auto-filled from the article body.
- the table of contents is a sticky rail beside the article on desktop and
  collapses on small screens. It covers H2 to H4.
- breadcrumbs are a real list, so screen readers can count them and the "/"
  is not read out.
- sub collection titles on a collection page are headings now. That page had
  one heading before.
- added a skip link, nav labels, and a language switcher that no longer
  claims to be a menu it never implemented.
- image alt text with a "?" or ":" was being dropped on save.
- a failed search no longer leaves the old suggestions on screen.
- a dark header colour picks a readable text colour on its own.
2026-08-02 17:25:15 +05:30
Abhinav Raut 608910692a add live search suggestions and card icon layouts to help center
The search box now shows matching articles as you type, and you can pick
one with the arrow keys. Cards get an icon position setting (beside the
title, above it, or centered) and old search logs are cleaned up daily.
2026-08-02 13:07:29 +05:30
Abhinav Raut f861485382 add help center preview and fix locale and URL handling
Editing a help center already worked from a sheet on the list page. This
moves it to its own screen so the public home page can render beside the
form as a live preview, built from the unsaved values.

Fixes:

- The preview dropped the entire theme. The form sent the card column
  count as a string and the backend could not read the theme JSON, so it
  fell back to an empty theme. Header and footer colors, taglines,
  footer links and social links all disappeared from the preview.
- An article whose language did not match its collection stayed live on
  the public site and in the sitemap, but its breadcrumb pointed at a
  page that 404s, and it was not visible in either language's tree. The
  public queries now require the article and its collection to share a
  language.
- Collections could be created in a language the help center does not
  list. That content is unreachable on the site and in the admin tree,
  so it is rejected now. An unknown language in the tree URL falls back
  to the default instead of showing an empty tree.
- Saving a collection or article in another language looked like nothing
  happened, because the tree only shows one language. The tree now
  switches to the language you saved into.
- Link fields accepted a value with no scheme. A bare host renders as a
  relative link and navigates inside the help center. The form now
  validates them and the backend drops bad ones on save.
- Delete buttons in the help center used the primary style. They use the
  destructive style now, like the rest of admin.
- Collection cards left the icon out when none was set, so cards in a row
  did not line up. The icon slot always renders and the meta row sits at
  the bottom of the card.
- The public header stacked into a staircase on phones. It now wraps to
  two rows.
- Tree rows were mouse only. The row title is a button now, so it is
  reachable by keyboard.
- Sort order updates ran as separate statements and could half apply.
  They run in one transaction.
- x-default hreflang was emitted on single language help centers.
2026-08-02 10:47:02 +05:30
Abhinav Raut 1fc606b820 fix help center issues and reuse global i18n keys
Media:
- media rows now take the private flag from the caller instead of
  guessing it from the model type
- agent avatars upload as public so help center readers can load them,
  contact and visitor avatars stay private
- backfill existing agent avatars to public

Public pages:
- dedupe article feedback by reader IP for 24 hours
- keep the article locale when submitting feedback
- skip view count bumps from crawlers on the JSON API
- only log a search when the search itself worked

Admin and data:
- lock the help center row while creating an article, since slugs are
  unique per help center but the DB index is per collection
- block changing a collection language when it still has articles or
  sub collections
- validate article locale on every update, not only when the collection
  changes
- reject slugs with characters that break /hc/ URLs
- exclude the whole subtree from the parent picker so a collection
  cannot be moved into itself
- drop articles of a paused help center from the AI index
- take the embed generation at enqueue time so jobs commit in edit order

Editor and UI:
- show an error when a YouTube URL is not valid
- do not break the icon picker when the sprite fails to load
- ignore a slow article fetch after another article is opened

i18n: move generic strings like Published, Archived, Slug, Icon, Author,
Language, Text color and Platform to globals, and drop hint text that
just repeated the field label.
2026-08-01 22:34:12 +05:30
Abhinav Raut 1d3c56b88f Merge remote-tracking branch 'origin/main' into fix/widget-contrast-color-luminance 2026-08-01 19:57:42 +05:30
Abhinav Raut 7b69690394 add SEO, localization, and drag reordering to the help center
Public pages were missing most of what a search engine needs. They now serve
a sitemap index at /sitemap.xml plus a robots.txt, and every page carries a
canonical URL, hreflang alternates, an x-default, and JSON-LD. Article pages
also send published and modified times. Search pages and the markdown view
are marked noindex. Requests from bots no longer bump the view counters, so
the insights numbers reflect real readers. Offscreen images in articles are
lazy loaded.

Public page text is now translated per locale instead of always using the
desk language, and pages with more than one translation show a language
switcher.

In the admin, collections and articles can be dragged to reorder, and an
article can be dragged into a different collection. Both save through new
sort order endpoints. Collections can have a lucide icon, picked from a new
icon picker backed by a sprite sheet. The landing page has layout options
for card grid or one per row, cards per row, and which details to show.

The help center list is a data table now, so HelpCenterCard is gone. The
tree page moved into AdminSplitLayout, its actions sit on their own row
below the breadcrumb, and it gained visit site and expand/collapse all.
2026-08-01 10:32:52 +05:30
Abhinav Raut 76db5aa851 fix help center bugs and share the embedding and article-content code
Review pass over the help center commit. Bug fixes:

- Image-only or video-only articles were rejected on save because the
  editor blanked the content when it had no text.
- An article could be moved into a collection in another help center.
  Slugs are only unique per help center, so two articles could end up
  on the same public URL.
- The article editor fired the "agent is typing" indicator into
  whatever conversation was last open, which the contact could see.
- Unsupported locales like /hc/x/zz-ZZ returned an empty 200 page with
  a canonical URL, so crawlers saw unlimited URLs. They 404 now.
- Article feedback was accepted for paused help centers.
- French pages announced lang="en".
- Editing a help center popped validation errors mid-keystroke.

Shared code that was duplicated:

- Help articles had their own copy of the snippet embedding lifecycle.
  The copy was missing the wait group, the concurrency cap and the
  generation gating, so article embeds were killed at shutdown, all hit
  the provider at once, and a slow embed could overwrite a newer edit or
  bring back a deleted article. Both content types now go through one
  embedSource lifecycle. Fingerprints are unchanged, so nothing
  re-embeds on deploy.
- Callout and collapsible CSS was written twice, once for the editor and
  once for the public page. They now share article-content.css, so the
  editor stays WYSIWYG.
- The excerpt was computed three ways. The page uses the stored one.

Less work at runtime:

- The admin tree no longer sends every article's HTML. The edit sheet
  loads the one article it needs. Publishing an article used to
  re-download the whole help center.
- Embedding reconcile no longer reads every article body every minute.
- The tree view fetched the help center twice on load.

Cleanups: one update-article route instead of two, media privacy comes
from the model type instead of a bool threaded through the handler, a
LinkListField component replaces three copies of the same repeater,
TreeNode renders the article row once, and 12 duplicate or dead i18n
keys are gone.
2026-07-25 15:50:47 +05:30
Abhinav Raut baff30fc28 add help center with public pages, admin management and AI article indexing
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
2026-07-25 14:19:38 +05:30
Abhinav Raut 78b1c26e94 Update static/widget.js
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-07-19 18:51:24 +05:30
Abhinav Raut 52cebdf384 update livechat launcher logo 2026-07-19 10:57:23 +05:30
Anandumv b5228e6621 fix(widget): gamma-correct luminance in contrastColor
contrastColor() fed raw sRGB channel values straight into the WCAG
relative-luminance formula and compared the result against the 0.179
threshold. That threshold assumes gamma-corrected (linear) values, so
without the correction dark colors like #333333 were scored as bright
enough for black text, producing black-on-dark-background icons.

Fixes #351
2026-07-14 12:25:20 +05:30
Abhinav Raut 9c6a6c254d fix widget draft attachments, prechat phone default, and shadow tweaks
Draft attachments now keep url and disposition, so a restored draft can render its attachments correctly instead of losing them.

The prechat phone field now picks a country code from the browser locale when the field is required (falling back to US), so users are not forced to pick one manually. Non-required phone fields still start blank.

Also soften the widget iframe and launcher bubble box shadows to a lighter, tinted look.
2026-07-10 01:09:09 +05:30
Abhinav Raut 8f658f274d add livechat pre-chat phone field and fix default launcher logo 2026-07-09 01:49:38 +05:30
Abhinav Raut 62a1fc836e add livechat widget preview and misc improvements
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.
2026-07-03 02:34:42 +05:30
Abhinav Raut 49b42976f3 update remaining static templates to Instrument Sans
The font swap missed static surfaces outside frontend/. The CSAT widget,
the public page style.css, and the email base template still loaded Plus
Jakarta Sans. Update them so all surfaces use the same font.
2026-06-06 21:16:11 +05:30
Abhinav Raut c645df820d Fix widget auto-init race when script is served from memory cache 2026-04-17 18:39:50 +05:30
Abhinav Raut dfa9808168 Consolidate widget iframe layout and honor prefers-reduced-motion 2026-04-17 00:55:51 +05:30
Abhinav Raut 6ef17eeae6 Make widget /auth/exchange idempotent
Prevent tokens sent twice from widget.js, send only once.
Update doc strings & comments for clarity.
Fix error handling
2026-04-16 01:16:24 +05:30
Abhinav Raut de8c825c4f Increase widget iframe and launcher shadow depth 2026-04-15 19:52:41 +05:30
Abhinav Raut c61073750e Centralize widget 401 handling in axios response interceptor, clear session and visitor cookies on expiry 2026-04-15 01:10:04 +05:30
Abhinav Raut 28bdb89e8d Update csat request email template
Update csat webpage again..
2026-04-14 22:14:39 +05:30
Abhinav Raut 1cb6074b1c Sync widget isOpen state across all open/close paths 2026-04-13 23:16:24 +05:30
Abhinav Raut 0d9196b269 Add color customization for live chat launcher 2026-04-13 04:16:22 +05:30