Skip to main content

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 TypeHook
List pages (data tables)useAnnaTableContext
Detail pages, forms, settings pagesuseAnnaPageContext

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

DataDescription
doctypeDisplay name for Anna's context bar
Current filtersActive column filters + search term
Visible columnsWhich columns are shown/hidden
Selected rowsRows the user has checked
PaginationCurrent page, page size, total count
Column definitionsField 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

ArgumentTypeWhen to pass
entityTypestringAlways — DocType or page name
entityIdstring | nullFor records; null for list-level pages
pageobject | nullFull record data; pass null while loading

Common entityType Values

PageentityType
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: null passed while record is loading (not page: undefined)
  • Pre-commit grep check: zero pages/page.tsx missing Anna context