Skip to main content

UX Standards — 22: Anna AI Popup Window

Governs: The Anna AI persistent popup window (/anna-popup) triggered from the header. Parent rules: See 02-HEADER.md and 12-POPOUT-AND-SLIDE-OVER-WINDOWS.md first.


Overview

Anna is the Anshin AI assistant. She opens in a persistent separate browser window (not a Sheet, not an iframe, not a Dialog). The window persists across navigation — clicking Anna again focuses the existing window rather than opening a new one.

The pattern is window.open() to the /anna-popup route, managed by the useAnna() hook.


Header Trigger Button

packages/shared/components/layouts/header-anna-button.tsx
export function AnnaButton({ anna, t }: AnnaButtonProps) {
const handleClick = () => {
// If popup is already open and not closed, focus it
if (anna.isPopupMode && anna.popupWindow && !anna.popupWindow.closed) {
anna.popupWindow.focus();
return;
}
// Open Anna in a new popup window
anna.openInPopup();
};

return (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
className='relative h-9 w-9 hover:bg-emerald-50 dark:hover:bg-emerald-950/30'
onClick={handleClick}
>
<div className='relative h-6 w-6'>
<Image src="/images/logo-shield.svg" alt="Anna" width={24} height={24} className="drop-shadow-sm" />
</div>
<span className='sr-only'>{t('header.askAnna')}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t('header.askAnna')}</TooltipContent>
</Tooltip>
);
}
PropertyValue
ComponentButton variant="ghost" size="icon"
Sizeh-9 w-9
Hover colorhover:bg-emerald-50 dark:hover:bg-emerald-950/30
IconAnna shield logo image — logo-shield.svg at h-6 w-6
Tooltipi18n key: header.askAnna → "Ask Anna"
BehaviorFocus existing window if open; open new if not

// anna-provider.tsx — openInPopup()
const url = `${window.location.origin}/anna-popup`;
const features = 'width=900,height=700,scrollbars=yes,resizable=yes';
const popupWindow = window.open(url, 'anna-popup', features);
anna.setPopupWindow(popupWindow);
PropertyValue
Route/anna-popup
Window name'anna-popup' (prevents duplicate windows)
Default size900×700px
Re-open behaviorIf popupWindow.closed === false, call focus() instead
ScrollableYes
ResizableYes

The window.open(url, 'anna-popup', ...) named target ensures only one Anna window ever exists — calling window.open with the same window name focuses the existing window.


Anna Popup Window Layout

The /anna-popup route renders a full-page Anna interface (no app sidebar, no app header):

┌──────────────────────────────────────────────────────────────┐
│ 900 × 700 px │
│ ┌──────────────────┐ ┌──────────────────────────────────────┐│
│ │ LEFT SIDEBAR │ │ RIGHT CHAT PANEL ││
│ │ (width ~240px) │ │ ││
│ │ │ │ [Anna avatar / greeting area] ││
│ │ ✏ New Chat │ │ ││
│ │ 🔍 Search │ │ ┌──────────────────────────────┐ ││
│ │ │ │ │ 🛡 Anna │ ││
│ │ ───────────── │ │ │ Hi! I'm Anna │ ││
│ │ (chat history) │ │ │ Your AI tour operations │ ││
│ │ │ │ │ assistant │ ││
│ │ Projects │ │ └──────────────────────────────┘ ││
│ │ (section) │ │ ││
│ │ │ │ [Suggestion chips] ││
│ │ │ │ • What can you help me with? ││
│ │ │ │ • Explain this data ││
│ │ │ │ • Generate a report ││
│ │ │ │ • Help me build a query ││
│ │ │ │ ││
│ │ │ │ ────────────────────────────────── ││
│ │ │ │ [📎] Ask Anna anything... [🎤] ││
│ └──────────────────┘ └──────────────────────────────────────┘│
│ ● Live ● Connected | Model: Qwen 2.5 14B │
└──────────────────────────────────────────────────────────────┘

ElementDescription
New Chat buttonCreates a new conversation (pencil/edit icon)
SearchSearch past conversations
Chat history listScrollable list of past conversations
Projects sectionGroups conversations by project

Right Chat Panel — Initial / Greeting State

Shown when no conversation is active:

ElementSpec
Anna avatarShield logo image + Hi! I'm Anna heading
Subtitle"Your AI tour operations assistant"
Suggestion chips4 quick-start chips (see below)
Input"Ask Anna anything..." placeholder with attach (📎) and mic (🎤) icons

Suggestion Chips (Initial State)

The 4 default chips are:

  1. "What can you help me with?"
  2. "Explain this data"
  3. "Generate a report"
  4. "Help me build a query"

Chips are styled as pill buttons with border rounded-full styling.


Status Bar

At the bottom of the popup window:

● Live ● Connected | Model: Qwen 2.5 14B
IndicatorMeaning
● LiveGreen dot — real-time connection active
● ConnectedGreen dot — backend API connected
Model labelShows active AI model (Qwen 2.5 14B)

Anna Provider Hook

import { useAnna } from '@/components/anna/anna-provider';

const anna = useAnna();

// State
anna.isPopupMode // true when Anna window is open
anna.popupWindow // reference to the window.open() handle

// Actions
anna.openInPopup() // open or focus the Anna popup

Design Tokens for Anna

Anna uses brand-specific CSS variables defined in _variables.css:

--anna-gradient-start: 160 84% 39%; /* emerald-600 */
--anna-gradient-mid: 142 71% 45%; /* green-500 */
--anna-gradient-end: 174 84% 32%; /* teal-600 */
--anna-accent-start: 160 84% 60%; /* emerald-400 */
--anna-accent-end: 187 92% 69%; /* cyan-400 */
--anna-titlebar-bg: 222 47% 11%; /* slate-900 */
--anna-titlebar-text: 160 60% 80%; /* emerald-300 */
--anna-user-msg: 160 84% 39%; /* emerald-600 */
--anna-assistant-msg: 151 81% 96%; /* emerald-50 */
--anna-context-bar-bg: 220 14% 96%; /* slate-100 */

Dark mode overrides:

--anna-assistant-msg: 160 84% 8%; /* emerald-950/30 */
--anna-context-bar-bg: 240 6% 10%; /* slate-900 */

Anna is always opened as a popup window. She is never embedded in the app as a Sheet or sidebar panel. This is a deliberate product decision:

ReasonBenefit
Persistent windowUser can reference Anna while navigating the main app
Separate window contextAnna's chat state persists independent of app navigation
Full-page layoutMore space for long responses, attachments, and data views
Named windowOnly one Anna window ever exists — no duplicate instances

Context Wiring (MANDATORY)

Every app page must wire Anna context so she can see what the user is looking at. See CLAUDE.mdAnna AI Context Wiring section:

  • List pages: call useAnnaTableContext({ doctype, table })
  • Detail pages: call useAnnaPageContext({ entityType, entityId, page })

Anna's popup window receives this context via the useAnna() provider.


Violation Checklist

  • Anna opens via window.open(url, 'anna-popup', features) — NOT a Sheet, Dialog, or iframe
  • Window name is 'anna-popup' (ensures single instance)
  • If popup already open: call focus() instead of opening a new window
  • Header trigger: Button variant="ghost" h-9 w-9 with hover:bg-emerald-50
  • Icon: logo-shield.svg at h-6 w-6 with drop-shadow-sm
  • Tooltip: t('header.askAnna')
  • Anna popup route is /anna-popup (stripped layout — no sidebar/header)
  • Anna CSS tokens use --anna-* variables (not hardcoded colors)
  • Status bar shows Live + Connected indicators + model name
  • Every app page wires useAnnaTableContext or useAnnaPageContext