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>
);
}
| Property | Value |
|---|---|
| Component | Button variant="ghost" size="icon" |
| Size | h-9 w-9 |
| Hover color | hover:bg-emerald-50 dark:hover:bg-emerald-950/30 |
| Icon | Anna shield logo image — logo-shield.svg at h-6 w-6 |
| Tooltip | i18n key: header.askAnna → "Ask Anna" |
| Behavior | Focus existing window if open; open new if not |
Popup Window Behavior
// 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);
| Property | Value |
|---|---|
| Route | /anna-popup |
| Window name | 'anna-popup' (prevents duplicate windows) |
| Default size | 900×700px |
| Re-open behavior | If popupWindow.closed === false, call focus() instead |
| Scrollable | Yes |
| Resizable | Yes |
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 │
└──────────────────────────────────────────────────────────────┘
Left Sidebar
| Element | Description |
|---|---|
| New Chat button | Creates a new conversation (pencil/edit icon) |
| Search | Search past conversations |
| Chat history list | Scrollable list of past conversations |
| Projects section | Groups conversations by project |
Right Chat Panel — Initial / Greeting State
Shown when no conversation is active:
| Element | Spec |
|---|---|
| Anna avatar | Shield logo image + Hi! I'm Anna heading |
| Subtitle | "Your AI tour operations assistant" |
| Suggestion chips | 4 quick-start chips (see below) |
| Input | "Ask Anna anything..." placeholder with attach (📎) and mic (🎤) icons |
Suggestion Chips (Initial State)
The 4 default chips are:
- "What can you help me with?"
- "Explain this data"
- "Generate a report"
- "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
| Indicator | Meaning |
|---|---|
● Live | Green dot — real-time connection active |
● Connected | Green dot — backend API connected |
| Model label | Shows 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 */
Popup vs Embedded
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:
| Reason | Benefit |
|---|---|
| Persistent window | User can reference Anna while navigating the main app |
| Separate window context | Anna's chat state persists independent of app navigation |
| Full-page layout | More space for long responses, attachments, and data views |
| Named window | Only 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.md → Anna 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-9withhover:bg-emerald-50 - Icon:
logo-shield.svgath-6 w-6withdrop-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
useAnnaTableContextoruseAnnaPageContext