UX Standards — 38: Anna Context Wiring Guide
Governs: Mandatory Anna AI context wiring on every page.
Parent rules: See 22-ANNA-AI-POPUP.md first.
Why This Is Mandatory
Anna's answers are only useful when she knows what the user is looking at. Without context, Anna gives generic responses. With context, she can reference the actual data on screen — the current table's filters, selected rows, or the record being viewed.
Every page that renders without Anna context is a defect. Anna context is as mandatory as the page title.
Two Hooks — One Per Page Type
| Page Type | Hook |
|---|---|
| List pages (data tables) | useAnnaTableContext |
| Detail pages, forms, settings pages | useAnnaPageContext |
Hook 1: useAnnaTableContext — List Pages
import { useAnnaTableContext } from '@/components/anna/anna-provider';
export default function BookingsPage() {
const { table, data, isLoading } = useDocTypeTable({ doctype: 'Booking', columns });
// Wire Anna context — call at component top level, not inside JSX
useAnnaTableContext({
doctype: 'Booking', // Frappe DocType name (used in Anna's UI)
table, // TanStack table instance
});
return (
<DocTypeListPage
title={t('booking.title')}
table={table}
statCards={statCards}
/>
);
}
What Anna Receives from useAnnaTableContext
| Data | Description |
|---|---|
doctype | Display name for Anna's context bar |
| Current filters | Active column filters + search term |
| Visible columns | Which columns are shown/hidden |
| Selected rows | Rows the user has checked |
| Pagination | Current page, page size, total count |
| Column definitions | Field names and types for the table |
Hook 2: useAnnaPageContext — Detail / Form / Settings Pages
import { useAnnaPageContext } from '@/components/anna/anna-provider';
export default function BookingDetailPage({ params }: { params: { id: string } }) {
const { data: record, isLoading } = useBooking(params.id);
// Wire Anna context
useAnnaPageContext({
entityType: 'Booking', // Human-readable type name
entityId: params.id, // Record ID
page: record ?? null, // Full record data (null while loading)
});
return <BookingDetailView record={record} />;
}
useAnnaPageContext Arguments
| Argument | Type | When to pass |
|---|---|---|
entityType | string | Always — DocType or page name |
entityId | string | null | For records; null for list-level pages |
page | object | null | Full record data; pass null while loading |
Common entityType Values
| Page | entityType |
|---|---|
| Booking detail | 'Booking' |
| Create booking form | 'Booking' |
| Notifications page | 'Notification' |
| Settings → Notifications | 'Settings' |
| User profile | 'User' |
| Analytics dashboard | 'Dashboard' |
Placement Rules
✅ Correct — Component Top Level
// ✅ Called at the top of the component function
export default function BookingsPage() {
const { table } = useDocTypeTable({ ... });
useAnnaTableContext({ doctype: 'Booking', table }); // TOP LEVEL
return <DataTable table={table} />;
}
❌ Wrong — Inside JSX or Conditionals
// ❌ Never inside conditionals or nested components
export default function BookingsPage() {
if (someCondition) {
useAnnaTableContext(...); // ILLEGAL: hooks in conditionals
}
return (
<div>
{/* ❌ Never in JSX */}
{useAnnaTableContext({ ... })}
</div>
);
}
Tabbed Pages
For pages with tabs, wire context in the layout component, not individual tabs:
// ✅ In the page-level component that contains the tabs
export default function BookingDetailPage({ params }) {
const { data: record } = useBooking(params.id);
useAnnaPageContext({ // Wire at page level, not tab level
entityType: 'Booking',
entityId: params.id,
page: record ?? null,
});
return (
<Tabs defaultValue="details">
<TabsList>...</TabsList>
<TabsContent value="details"><DetailsTab /></TabsContent>
<TabsContent value="history"><HistoryTab /></TabsContent>
</Tabs>
);
}
Cleanup on Unmount
useAnnaPageContext automatically cleans up when the component unmounts. The hook handles:
// Internally handled by the hook — no manual cleanup needed
useEffect(() => {
setPageContext({ entityType, entityId, page });
return () => setPageContext(null); // cleanup on unmount
}, [entityType, entityId, page]);
Pre-Commit Verification
# Find pages missing Anna context wiring
# Every file in app/(dashboard)/**/page.tsx should have useAnna context
grep -rL "useAnnaTableContext\|useAnnaPageContext" \
src/app/\(dashboard\)/ --include="page.tsx"
# Expected: 0 results
Violation Checklist
- Every list page calls
useAnnaTableContext({ doctype, table }) - Every detail page calls
useAnnaPageContext({ entityType, entityId, page }) - Every form page calls
useAnnaPageContext({ entityType, entityId: null, page: null }) - Every settings section calls
useAnnaPageContext({ entityType: 'Settings', entityId: sectionName, page: null }) - Hook called at component TOP LEVEL — never in conditionals, loops, or JSX
- Tabbed pages wire context at the layout level, not individual tab components
-
page: nullpassed while record is loading (notpage: undefined) - Pre-commit grep check: zero pages/page.tsx missing Anna context